initial commit of new version

This commit is contained in:
tms
2026-10-04 18:24:18 -04:00
parent 994fac96e0
commit 57220375a3
43 changed files with 3429 additions and 1 deletions
+230
View File
@@ -0,0 +1,230 @@
"""
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)
)