# color_features_tiles.py - color features tiled data from images # ImageDB2 - Tools for analysis, indexing, search, and creative use of image data """ ColorFeaturesTiles Part of ImageDB2 - Tools for analysis, indexing, search, and creative use of image data Overview ======== Pyramid down-sampling + color-feature analysis. Saves downsampled data to NAME.COLORSPACE.TILE{SIZE}.OLAP{SIZE} data files mem-mapped to np arrays. Also retrieves and displays tiled image data. """ import os import math import cv2 as cv import numpy as np from dataclasses import dataclass from avdb.config import ( check_params, NUM_HISTO_BINS, LEVELS_CONFIG ) class ColorFeaturesTile(object): """ """ def __init__(self, **params) -> None: self.uuid = check_params(params, 'uuid', 'TESTIMG') self.root_path = check_params(params, 'root_path', '/home/kfl/dev/git/avdb-py-v1') self.level = check_params(params, 'level', 0) self.colorspace = check_params(params, 'color_space', 'lab') self.write_flag = True self.debug = True print(f"CREATING TILE -- LEVEL: {self.level} - UUID: {self.uuid} color space: {self.colorspace} - write_flag: {self.write_flag}") def analyze_img_tiles_postnorm(self, lvl=None): """ """ if lvl is None: imgpath = f"{self.root_path}/img/{self.uuid}/{self.uuid}.jpeg" else: imgpath = f"{self.root_path}/img/{self.uuid}/{self.uuid}.{self.colorspace}.pyr_{lvl}.jpeg" print(imgpath) img = cv.imread(imgpath, cv.IMREAD_UNCHANGED) imgw = img.shape[1] imgh = img.shape[0] # iterate over the tiles and unroll into linear chunks def _aux(): for top_crnr in range(imgh): for lft_crnr in range(imgw): # print(img[top_crnr, lft_crnr]) yield np.r_[ img[top_crnr,lft_crnr], ] total_tiles = np.c_[list(_aux())] if self.write_flag is True: print(imgpath) mmappath = f"{imgpath}.tiles.mmap" fp = np.memmap( mmappath, dtype=np.uint16, mode="w+", shape=total_tiles.shape ) print(f"all tiles : {total_tiles.shape}") fp[:] = total_tiles fp.flush() return total_tiles, (imgw, imgh, 1, 1) def analyze_img_blocks_postnorm(self, lvl=None): """ """ if lvl is None: imgpath = f"{self.root_path}/img/{self.uuid}/{self.uuid}.jpeg" else: imgpath = f"{self.root_path}/img/{self.uuid}/{self.uuid}.{self.colorspace}.pyr_{lvl}.jpeg" print(imgpath) img = cv.imread(imgpath, cv.IMREAD_UNCHANGED) imgw = img.shape[1] imgh = img.shape[0] target_blocks = LEVELS_CONFIG[lvl]["targetblocks"] if target_blocks is None: raise ValueError(f"target_blocks is None for level {self.level}") # calculate gutters imgw -= 2 imgh -= 2 # iterate over the blocks and unroll into linear chunks def _aux(): for top_crnr in range(imgh): for lft_crnr in range(imgw): yield np.r_[ img[top_crnr,lft_crnr:lft_crnr+3], img[top_crnr+1,lft_crnr:lft_crnr+3], img[top_crnr+2,lft_crnr:lft_crnr+3] ].flatten() total_blocks = np.c_[list(_aux())] if self.write_flag is True: print(imgpath) mmappath = f"{imgpath}.blocks.mmap" fp = np.memmap( mmappath, dtype=np.uint16, mode="w+", shape=total_blocks.shape ) print(f"all histograms : {total_blocks.shape}") fp[:] = total_blocks fp.flush() return total_blocks, (imgw, imgh, 1, 1) def analyze_img_histograms_postnorm(self, lvl=None): """ overlapping tiles analysis + width in tiles must be a multiple of 2 """ if lvl is None: imgpath = f"{self.root_path}/img/{self.uuid}/{self.uuid}.jpeg" else: imgpath = f"{self.root_path}/img/{self.uuid}/{self.uuid}.{self.colorspace}.pyr_{lvl}.jpeg" print(imgpath) img = cv.imread(imgpath, cv.IMREAD_UNCHANGED) imgw = img.shape[1] imgh = img.shape[0] # / by 1 = whole dimension gridsplit = LEVELS_CONFIG[lvl]["gridsplit"] tilesw = (gridsplit + 1) if gridsplit is not None else 1 tilesh = (gridsplit + 1) if gridsplit is not None else 1 tilew = int(imgw / tilesw) tileh = int(imgh / tilesh) # img = self.convert_colorspace(img) mask = np.zeros(img.shape[:2], np.uint8) all_histograms = np.array([], dtype=np.float32) all_histograms.shape = (0, 1) lastx, lasty = 0, 0 print(f"dims: {(tilesw,tilesh)}") for y in range(tilesh): for x in range(tilesw): if (lastx, lasty) != (x, y): mask[ int(tilew/2 * lastx) : int((tilew/2 * lastx) + tilew), int(tileh/2 * lasty) : int((tileh/2 * lasty) + tileh), ] = 0 lastx = x lasty = y mask[ int(tilew/2 * x) : int((tilew/2 * x) + tilew), int(tileh/2 * y) : int((tileh/2 * y) + tileh), ] = 255 # calculate 3 channels' histograms hist0_masked = cv.calcHist( [img], [0], mask, [NUM_HISTO_BINS], [0, 65536] ) hist0_masked.shape = (NUM_HISTO_BINS, 1) row0 = hist0_masked hist1_masked = cv.calcHist( [img], [1], mask, [NUM_HISTO_BINS], [0, 256] ) hist1_masked.shape = (NUM_HISTO_BINS, 1) row1 = hist1_masked hist2_masked = cv.calcHist( [img], [2], mask, [NUM_HISTO_BINS], [0, 256] ) hist2_masked.shape = (NUM_HISTO_BINS, 1) row2 = hist2_masked # print(f"row0: {row0} - {row0.shape}") # print(f"row1: {row1} - {row1.shape}") # print(f"row2: {row2} - {row2.shape}") # L [bin0,bin1,..bin15], r=0, c=0 # A [bin0,bin1,..bin15] # B [bin0,bin1,..bin15] # L [bin0,bin1,..bin15], r=0, c=1 # A [bin0,bin1,..bin15] # B [bin0,bin1,..bin15] # etc. # top left to bottom right all_histograms: np.array = np.r_[ all_histograms, row0, row1, row2 ] # print(f"ALL HISTOGRAMS: {all_histograms.shape}") # print(all_histograms[-48:,:]) total_hist = np.reshape(all_histograms[:], (-1,48)) sz = tilesh * tilesw cv.normalize( total_hist, total_hist, norm_type=cv.NORM_MINMAX ) # total_hist *= 255. print(f"total HISTOGRAM: {total_hist.shape} {total_hist.sum()}") print(total_hist[:2,:]) print(np.max(total_hist, 0)[:24]) print(np.max(total_hist, 1)[:24]) print(np.min(total_hist, 0)[:24]) print(np.min(total_hist, 1)[:24]) print(np.mean(total_hist, 0)[:24]) print(np.mean(total_hist, 1)[:24]) if self.write_flag is True: print(f"writing to: {imgpath}.histos.mmap") mmappath = f"{imgpath}.histos.mmap" fp = np.memmap( mmappath, dtype="float32", mode="w+", shape=total_hist.shape ) print(f"all histograms : {total_hist.shape}") fp[:] = total_hist # print(f"CHECK: {fp[:2,:]} - {np.max(fp)} - {np.min(fp)}") fp.flush() return total_hist, (imgw, imgh, tilesw, tilesh)