""" 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) )