231 lines
7.8 KiB
Python
231 lines
7.8 KiB
Python
"""
|
|||
|
|
Color Features Pyramid
|
||
|
|
Part of [imgdb] - Tools for analysis, indexing, search, and creative use of image data
|
||
|
|
|
||
|
|
Overview
|
||
|
|
========
|
||
|
|
Pyramid down-sampling + color-feature analysis.
|
||
|
|
Saves downsampled data to NAME.COLORSPACE.PYR{DOWNSAMPLING} data files mem-mapped to np arrays.
|
||
|
|
Also retrieves and displays down-sampled image data.
|
||
|
|
|
||
|
|
ds res(w*h) gridpx(w,h) grid()
|
||
|
|
|
||
|
|
1 1920 * 1600 30, 20 64, 80
|
||
|
|
2 32, 40
|
||
|
|
3 480 * 400 16, 20
|
||
|
|
4 240 * 200 15, 10 16, 20
|
||
|
|
5 8, 10
|
||
|
|
6 4, 5
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
Cols, Rows (Many are in portrait, same but with vertical window rects)
|
||
|
|
Resolution 1280, 960
|
||
|
|
No. Tiles 40, 40
|
||
|
|
|
||
|
|
Resolution 960, 1280
|
||
|
|
No. Tiles 40, 40
|
||
|
|
|
||
|
|
|
||
|
|
1280 960 0 1228800
|
||
|
|
|
||
|
|
640 480 1 307200
|
||
|
|
320 240 2 76800
|
||
|
|
160 120 3 19200 + HISTO
|
||
|
|
80 60 4 4800 + HISTO
|
||
|
|
40 30 5 1200 + HISTO
|
||
|
|
20 15 6 300
|
||
|
|
10 8 7 80
|
||
|
|
5 4 8 20
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
import math
|
||
|
|
import cv2 as cv
|
||
|
|
import numpy as np
|
||
|
|
# import matplotlib.pyplot as plt
|
||
|
|
from config import (
|
||
|
|
check_params,
|
||
|
|
NUM_LEVELS,
|
||
|
|
NUM_HISTO_BINS,
|
||
|
|
LEVELS_CONFIG
|
||
|
|
)
|
||
|
|
import color_features_tile as cft
|
||
|
|
|
||
|
|
class ColorFeaturesPyramid(object):
|
||
|
|
"""
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, **params) -> None:
|
||
|
|
self.uuid = check_params(params, 'uuid', 'IMG_1001')
|
||
|
|
# print(f"UUID: {self.uuid} -- {params}")
|
||
|
|
self.full_tile = cft.ColorFeaturesTile(**{"uuid": self.uuid})
|
||
|
|
self.down_sampling_levels = check_params(params, 'down_sampling', NUM_LEVELS)
|
||
|
|
self.colorspace = check_params(params, 'colorspace', 'lab')
|
||
|
|
self.write_flag = True
|
||
|
|
self.debug = True
|
||
|
|
|
||
|
|
def _determine_num_bins(self, sz, lvl):
|
||
|
|
return int(sz / math.pow(2, (lvl + 1)))
|
||
|
|
|
||
|
|
def _zeros_for_shape_level(self, sz, lvl):
|
||
|
|
"""
|
||
|
|
lists of np arrays of the correct size to hold our gradually shrinking arrays
|
||
|
|
"""
|
||
|
|
imgw, imgh = sz
|
||
|
|
return np.zeros(
|
||
|
|
(
|
||
|
|
self._determine_num_bins(imgh, lvl),
|
||
|
|
self._determine_num_bins(imgw, lvl),
|
||
|
|
3,
|
||
|
|
),
|
||
|
|
dtype=np.uint16,
|
||
|
|
)
|
||
|
|
|
||
|
|
def _down_sample_in_place(self, in_mtrx, pwr, out_shape): # pwr: 0 1 2 3 4 ...
|
||
|
|
print(f"in_mtrx.shape {in_mtrx.shape}")
|
||
|
|
print(f"out_shape {out_shape}")
|
||
|
|
(a, b, _) = in_mtrx.shape
|
||
|
|
out_mtrx = np.zeros(out_shape, dtype=in_mtrx.dtype)
|
||
|
|
mult = 2 ** pwr # 1 2 4 8 16 ...
|
||
|
|
# same shape
|
||
|
|
|
||
|
|
# for i in range(a):
|
||
|
|
# for j in range(b):
|
||
|
|
# print(i, j, a, b, in_mtrx[i,j,:])
|
||
|
|
|
||
|
|
for i in range(a):
|
||
|
|
for j in range(b):
|
||
|
|
# print(
|
||
|
|
# (i * mult),
|
||
|
|
# ((i + 1) * mult),
|
||
|
|
# (j * mult),
|
||
|
|
# ((j + 1) * mult),
|
||
|
|
# i,
|
||
|
|
# j,
|
||
|
|
# a,
|
||
|
|
# b,
|
||
|
|
# in_mtrx[i, j, :],
|
||
|
|
# )
|
||
|
|
out_mtrx[
|
||
|
|
(i * mult) : ((i + 1) * mult), (j * mult) : ((j + 1) * mult)
|
||
|
|
] = in_mtrx[i,j,:]
|
||
|
|
|
||
|
|
return out_mtrx
|
||
|
|
|
||
|
|
def _down_sample_and_analyze(self):
|
||
|
|
"""
|
||
|
|
Reads and image, performs a series of down-samplings, writes each to disk
|
||
|
|
"""
|
||
|
|
# Validate colorspace
|
||
|
|
valid_colorspaces = ["rgb", "lab", "lab"]
|
||
|
|
if self.colorspace not in valid_colorspaces:
|
||
|
|
raise ValueError(f"Invalid colorspace: {self.colorspace}. Valid options are: {valid_colorspaces}")
|
||
|
|
|
||
|
|
# dataroot = f'{self.full_tile.root_path}/img/'
|
||
|
|
datapath = f'{self.full_tile.root_path}/img/{self.full_tile.uuid}/{self.full_tile.uuid}.jpeg'
|
||
|
|
print(f'Down-sample + analyze: {datapath} - ({str(self.down_sampling_levels)})')
|
||
|
|
|
||
|
|
img = cv.imread(datapath)
|
||
|
|
|
||
|
|
print(img.shape)
|
||
|
|
# print(img[500:520,600:620,:])
|
||
|
|
|
||
|
|
imgw = img.shape[1]
|
||
|
|
imgh = img.shape[0]
|
||
|
|
ds_levels = int(self.down_sampling_levels)
|
||
|
|
|
||
|
|
# convert color space once
|
||
|
|
if self.colorspace == "rgb":
|
||
|
|
img_ = cv.cvtColor(img, cv.COLOR_BGR2RGB)
|
||
|
|
elif self.colorspace == "hsv":
|
||
|
|
img_ = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||
|
|
else:
|
||
|
|
img_ = cv.cvtColor(img, cv.COLOR_BGR2Lab)
|
||
|
|
|
||
|
|
# add 1 to # of levels so we can write to the Nth + 1 level, properly sized
|
||
|
|
# 0th level is image itself in imgs_ds, concat shifts zeros arrays to start
|
||
|
|
# at index 1
|
||
|
|
imgs_ds = [img_] + [
|
||
|
|
self._zeros_for_shape_level((imgw, imgh), level)
|
||
|
|
for level in range(ds_levels)
|
||
|
|
]
|
||
|
|
print(f"imgs_ds len: {len(imgs_ds)}\n")
|
||
|
|
for i,img in enumerate(imgs_ds):
|
||
|
|
print(f"{i}: {img.shape}\n")
|
||
|
|
|
||
|
|
base_shape = imgs_ds[0].shape
|
||
|
|
|
||
|
|
try:
|
||
|
|
for level in range(ds_levels - 1):
|
||
|
|
imgs_ds[(level + 1)] = cv.pyrDown(imgs_ds[level], imgs_ds[(level + 1)])
|
||
|
|
|
||
|
|
# display copy
|
||
|
|
img_a = self._down_sample_in_place(
|
||
|
|
imgs_ds[level],
|
||
|
|
level,
|
||
|
|
base_shape
|
||
|
|
)
|
||
|
|
print(f"img_a shape: {img_a.shape} - imgs_ds shape: {imgs_ds[(level + 1)].shape} - level+1:{level+1}\n")
|
||
|
|
print(f"writing {datapath[:-5]}.pyr_{str(level + 1)}_display.jpeg\n")
|
||
|
|
dsimg_filename = f"{datapath[:-5]}.{self.colorspace}.pyr_{str(level)}_display.jpeg"
|
||
|
|
if True or not os.path.exists(dsimg_filename):
|
||
|
|
cv.imwrite(dsimg_filename, img_a)
|
||
|
|
|
||
|
|
# real copy
|
||
|
|
img_b = self._down_sample_in_place(
|
||
|
|
imgs_ds[level],
|
||
|
|
0,
|
||
|
|
imgs_ds[level].shape
|
||
|
|
)
|
||
|
|
print(f"img_b shape: {img_b.shape} - imgs_ds shape: {imgs_ds[(level + 1)].shape} - level+1:{level+1}\n")
|
||
|
|
print(f"writing {datapath[:-5]}.pyr_{str(level + 1)}.jpeg\n")
|
||
|
|
dsimg_filename = f"{datapath[:-5]}.{self.colorspace}.pyr_{str(level)}.jpeg"
|
||
|
|
if True or not os.path.exists(dsimg_filename):
|
||
|
|
cv.imwrite(dsimg_filename, img_b)
|
||
|
|
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
print(str(e))
|
||
|
|
pass
|
||
|
|
|
||
|
|
# TODO: split out and refactor to allow different data outputs
|
||
|
|
# for level in range(ds_levels):
|
||
|
|
# fp = np.memmap((datapath + str(level+1)), dtype='uint8', mode='w+',
|
||
|
|
# shape=(imgs_ds[level+1].shape[0], imgs_ds[level+1].shape[1], 3))
|
||
|
|
# fp[:] = np.copy(imgs_ds[level+1])
|
||
|
|
# fp.flush()
|
||
|
|
|
||
|
|
# for now we need the 0th, original img AND
|
||
|
|
# all the ds_levels recursively down-sampled image files
|
||
|
|
# + 1 to include the 0th level
|
||
|
|
for level in range(ds_levels - 1):
|
||
|
|
# sub_img = imgs_ds[level]
|
||
|
|
|
||
|
|
feat_tiles = cft.ColorFeaturesTile(
|
||
|
|
**{
|
||
|
|
"uuid": self.uuid,
|
||
|
|
"level": int(level),
|
||
|
|
"root_path": self.full_tile.root_path
|
||
|
|
}
|
||
|
|
)
|
||
|
|
if LEVELS_CONFIG[level]["gridsplit"] is not None:
|
||
|
|
gridsplit = LEVELS_CONFIG[level]["gridsplit"]
|
||
|
|
yield (
|
||
|
|
str(level),
|
||
|
|
feat_tiles.analyze_img_histograms_postnorm(level)
|
||
|
|
)
|
||
|
|
if LEVELS_CONFIG[level]["tiles"]:
|
||
|
|
yield (
|
||
|
|
str(level),
|
||
|
|
feat_tiles.analyze_img_tiles_postnorm(level)
|
||
|
|
)
|
||
|
|
if LEVELS_CONFIG[level]["target"]:
|
||
|
|
targetblocks = LEVELS_CONFIG[level]["targetblocks"]
|
||
|
|
yield (
|
||
|
|
str(level),
|
||
|
|
feat_tiles.analyze_img_blocks_postnorm(level)
|
||
|
|
)
|