262 lines
8.1 KiB
Python
262 lines
8.1 KiB
Python
# 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)
|