initial commit of new version
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# src/__init__.py
|
||||
|
||||
# Import key modules to make them available at the package level
|
||||
from .db import DBOps
|
||||
from .analysis import convert_mmap_to_csv
|
||||
from .config import LEVELS_CONFIG
|
||||
|
||||
__all__ = ['DBOps', 'convert_mmap_to_csv', 'LEVELS_CONFIG']
|
||||
@@ -0,0 +1,187 @@
|
||||
import os
|
||||
import sys
|
||||
import numpy as np
|
||||
import csv
|
||||
import uuid
|
||||
from typing import List, Tuple
|
||||
|
||||
from .db import DBConnection, DBOps
|
||||
|
||||
BATCH_SIZE = 250
|
||||
def run_analysis():
|
||||
"""
|
||||
Iterate over all images in the 'img/' directory and process them using the DBOps class.
|
||||
|
||||
This function processes each image in the 'img/' directory, initializes the DBOps,
|
||||
and performs the analysis for each image.
|
||||
"""
|
||||
img_folder = "img"
|
||||
if not os.path.exists(img_folder):
|
||||
print(f"Error: The folder '{img_folder}' does not exist.")
|
||||
return
|
||||
|
||||
# Get all subdirectories in the 'img/' folder
|
||||
subdirectories = [
|
||||
d for d in os.listdir(img_folder)
|
||||
if os.path.isdir(os.path.join(img_folder, d))
|
||||
]
|
||||
|
||||
if not subdirectories:
|
||||
print(f"No subdirectories found in the folder '{img_folder}'.")
|
||||
return
|
||||
|
||||
print(f"Found {len(subdirectories)} subdirectories in the folder '{img_folder}'.")
|
||||
|
||||
# Initialize the database connection and operations
|
||||
db_conn = DBConnection('testdb')
|
||||
db_ops = DBOps(db_conn)
|
||||
|
||||
for subdir in subdirectories:
|
||||
# subdirs = ["TESTIMG", "IMG_9997", "IMG_9998", "IMG_9999", "IMG_2327"]
|
||||
# for subdir in subdirs:
|
||||
subdir_path = os.path.join(img_folder, subdir)
|
||||
# Look for the image file in the subdirectory
|
||||
|
||||
img_file = f"{subdir}.jpeg"
|
||||
img_path = os.path.join(subdir_path, img_file)
|
||||
print(f"\nProcessing image: {img_path}")
|
||||
|
||||
try:
|
||||
# Process the image analysis
|
||||
db_ops.process_image_analysis(img_path)
|
||||
print(f"Successfully processed image: {img_path}")
|
||||
except Exception as e:
|
||||
print(f"Error processing image {img_path}: {e}")
|
||||
|
||||
print("\nAnalysis of all images completed successfully.")
|
||||
|
||||
def convert_mmap_to_csv(mmap_file_path: str, csv_file_path: str, rows: int = 81) -> None:
|
||||
"""
|
||||
Convert a memory-mapped file to a CSV file.
|
||||
|
||||
Args:
|
||||
mmap_file_path (str): Path to the memory-mapped file.
|
||||
csv_file_path (str): Path to the output CSV file.
|
||||
rows (int): Number of rows in the memory-mapped data.
|
||||
"""
|
||||
# Load the memory-mapped data
|
||||
mmap_data = np.memmap(mmap_file_path, mode='r', dtype='float32', shape=(rows, 48))
|
||||
|
||||
# Convert the data to a list of lists for CSV writing
|
||||
data_for_csv = mmap_data.tolist()
|
||||
|
||||
# Write the data to a CSV file
|
||||
with open(csv_file_path, 'w', newline='') as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
writer.writerows(data_for_csv)
|
||||
|
||||
def insert_tile_data(self, rows: List[Tuple]) -> None:
|
||||
"""
|
||||
Insert tile data into the pyramiddata table using batch insertion.
|
||||
|
||||
Args:
|
||||
rows (List[Tuple]): List of tuples containing tile data to insert.
|
||||
"""
|
||||
query_str = """
|
||||
INSERT INTO pyramiddata (
|
||||
img_id, level, tilex, tiley, x, y, x0, y0, w, h, hash, tile_data
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
print(len(rows))
|
||||
|
||||
with self._connection.get_cursor() as cursor:
|
||||
# Use execute_batch for efficient batch insertion
|
||||
extras.execute_batch(cursor, query_str, rows, page_size=BATCH_SIZE)
|
||||
extras.commit()
|
||||
|
||||
# def process_and_insert_tile_data_batched(
|
||||
# self,
|
||||
# img_id: int,
|
||||
# img_dims: Tuple[int, int],
|
||||
# level: int,
|
||||
# tile_file: str
|
||||
# ) -> None:
|
||||
# """
|
||||
# Process and insert tile data for a specific level of an image.
|
||||
# Args:
|
||||
# img_id (int): ID of the image.
|
||||
# img_dims (Tuple[int, int]): Dimensions of the image (width, height).
|
||||
# level (int): Pyramid level.
|
||||
# tile_file (str): The memory-mapped tile file.
|
||||
# """
|
||||
# # Calculate the downsampled resolution for this level
|
||||
# resolution = 2 ** level
|
||||
# img_w, img_h = img_dims
|
||||
|
||||
# # Load the memory-mapped tile data
|
||||
# tile_data = self.load_memmapped_tile_data(tile_file)
|
||||
|
||||
# print(f"img dims: {img_dims}")
|
||||
# print(f"tile data shape: {tile_data.shape}")
|
||||
|
||||
# tilew = img_w // resolution
|
||||
# tileh = img_h // resolution
|
||||
# width_t = img_w // tilew
|
||||
# height_t = img_h // tileh
|
||||
|
||||
# grid_size = 1
|
||||
|
||||
# def gen_tiles():
|
||||
# counter = 0
|
||||
# # Loop through each tile
|
||||
# for tiley in range(tileh):
|
||||
# for tilex in range(tilew):
|
||||
# x0 = tilex * width_t
|
||||
# y0 = tiley * height_t
|
||||
# w = grid_size
|
||||
# h = 1
|
||||
|
||||
# try:
|
||||
# tiledata = list(
|
||||
# (
|
||||
# np.r_[
|
||||
# tile_data[
|
||||
# (counter * 3):
|
||||
# ((counter + 1) * 3)
|
||||
# ]
|
||||
# ]
|
||||
# ).flatten().tolist()
|
||||
# )
|
||||
# counter += 1
|
||||
# yield (
|
||||
# img_id,
|
||||
# level,
|
||||
# tilex,
|
||||
# tiley,
|
||||
# width_t,
|
||||
# height_t,
|
||||
# x0,
|
||||
# y0,
|
||||
# w,
|
||||
# h,
|
||||
# str(uuid.uuid4()),
|
||||
# list(map(self.to_unsigned, tiledata))
|
||||
# )
|
||||
# except Exception as e:
|
||||
# print(f"ERROR: {e}")
|
||||
# counter += 1
|
||||
|
||||
# all_for_insertion = list(gen_tiles())
|
||||
# print(f"Inserting {len(all_for_insertion)} tiles")
|
||||
|
||||
# # # Batch the data for insertion
|
||||
# # batched = [
|
||||
# # all_for_insertion[BATCH_SIZE * i: BATCH_SIZE * (i + 1)]
|
||||
# # for i in range(int(len(all_for_insertion) / BATCH_SIZE) + 1)
|
||||
# # ]
|
||||
# # print(f"Inserting {len(batched)} batches")
|
||||
|
||||
# # for batch in batched:
|
||||
# # if batch: # Ensure the batch is not empty
|
||||
# # self.insert_tile_data(batch)
|
||||
|
||||
# self.insert_tile_data(all_for_insertion)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_analysis()
|
||||
@@ -0,0 +1,23 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: avdb-py-v1
|
||||
Version: 0.1.0
|
||||
Summary: A Python package for image analysis and database operations
|
||||
Home-page: https://github.com/yourusername/avdb-py-v1
|
||||
Author: Your Name
|
||||
Author-email: your.email@example.com
|
||||
Requires-Python: >=3.8
|
||||
Requires-Dist: psycopg2-binary>=2.9.6
|
||||
Requires-Dist: numpy>=1.21.0
|
||||
Requires-Dist: opencv-python>=4.5.5
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
||||
Requires-Dist: black>=22.0.0; extra == "dev"
|
||||
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
||||
Requires-Dist: mypy>=0.900; extra == "dev"
|
||||
Dynamic: author
|
||||
Dynamic: author-email
|
||||
Dynamic: home-page
|
||||
Dynamic: provides-extra
|
||||
Dynamic: requires-dist
|
||||
Dynamic: requires-python
|
||||
Dynamic: summary
|
||||
@@ -0,0 +1,10 @@
|
||||
README.md
|
||||
setup.py
|
||||
avdb/avdb_py_v1.egg-info/PKG-INFO
|
||||
avdb/avdb_py_v1.egg-info/SOURCES.txt
|
||||
avdb/avdb_py_v1.egg-info/dependency_links.txt
|
||||
avdb/avdb_py_v1.egg-info/requires.txt
|
||||
avdb/avdb_py_v1.egg-info/top_level.txt
|
||||
tests/test_alembic_migration.py
|
||||
tests/test_color_features_pyramid.py
|
||||
tests/test_db.py
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
psycopg2-binary>=2.9.6
|
||||
numpy>=1.21.0
|
||||
opencv-python>=4.5.5
|
||||
|
||||
[dev]
|
||||
pytest>=7.0.0
|
||||
black>=22.0.0
|
||||
flake8>=4.0.0
|
||||
mypy>=0.900
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
@@ -0,0 +1,261 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
NUM_LEVELS = 9
|
||||
NUM_HISTO_BINS = 16
|
||||
NUM_ID_BINS = 5 # id, x, y, tilew, tileh
|
||||
|
||||
|
||||
def check_params(params, k, defaultval):
|
||||
if k in params.keys():
|
||||
return params[k]
|
||||
else:
|
||||
return defaultval
|
||||
|
||||
|
||||
LEVELS_CONFIG = [
|
||||
{
|
||||
"level": 0,
|
||||
"tiles": False,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 1,
|
||||
"tiles": False,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"tiles": False,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"tiles": True,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"tiles": True,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"tiles": True,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"tiles": True,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"tiles": True,
|
||||
"gridsplit": None,
|
||||
"target": False,
|
||||
"targetblocks": None
|
||||
}
|
||||
]
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import uuid
|
||||
import csv
|
||||
from typing import List, Tuple
|
||||
import psycopg2
|
||||
from psycopg2 import extras
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from avdb.config import LEVELS_CONFIG
|
||||
|
||||
BATCH_SIZE=200
|
||||
|
||||
class DBConnection:
|
||||
def __init__(self, dbname='testdb'):
|
||||
try:
|
||||
self._connection = psycopg2.connect(
|
||||
user="postgres",
|
||||
password='pgRocks',
|
||||
host="127.0.0.1",
|
||||
port="5432",
|
||||
database=dbname
|
||||
)
|
||||
print(f"\nconnection: {self._connection}\n\n")
|
||||
self._connection.autocommit = True
|
||||
self.dbname = dbname
|
||||
# Create a cursor to execute queries
|
||||
self.cursor = self._connection.cursor()
|
||||
|
||||
# Print PostgreSQL details
|
||||
print("PostgreSQL server information")
|
||||
print(self._connection.get_dsn_parameters(), "\n")
|
||||
|
||||
# Executing a SQL query
|
||||
self.cursor.execute("SELECT version();")
|
||||
|
||||
# Fetch result
|
||||
record = self.cursor.fetchone()
|
||||
print("You are connected to - ", record, "\n")
|
||||
|
||||
except (Exception, psycopg2.Error) as error:
|
||||
print("Error while connecting to PostgreSQL", error)
|
||||
|
||||
def get_cursor(self):
|
||||
return self._connection.cursor()
|
||||
|
||||
def get_connection(self):
|
||||
return self._connection
|
||||
|
||||
def test_db_connection():
|
||||
"""
|
||||
Test if the database is active and available by attempting a connection and a simple query.
|
||||
|
||||
Returns:
|
||||
bool: True if the database connection is successful, False otherwise.
|
||||
"""
|
||||
try:
|
||||
# Attempt to establish a connection
|
||||
conn = DBConnection('testdb')
|
||||
cursor = conn.get_cursor()
|
||||
|
||||
# Execute a simple query to verify the connection
|
||||
cursor.execute("SELECT 1;")
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result[0] == 1:
|
||||
print("Database connection test successful!")
|
||||
return True
|
||||
else:
|
||||
print("Database connection test failed.")
|
||||
return False
|
||||
|
||||
except (Exception, psycopg2.Error) as error:
|
||||
print(f"Error while testing database connection: {error}")
|
||||
return False
|
||||
|
||||
class DBOps:
|
||||
def __init__(self, connection):
|
||||
# self._dbname = connection.dbname
|
||||
self._connection = connection
|
||||
|
||||
def insert_image(
|
||||
self,
|
||||
img_path: str,
|
||||
img_name: str,
|
||||
width: int,
|
||||
height: int,
|
||||
img_id= None
|
||||
) -> int:
|
||||
"""
|
||||
Insert a new image record into the images table.
|
||||
|
||||
Args:
|
||||
img_path (str): Path to the image file.
|
||||
img_name (str): Name of the image file.
|
||||
width (int): Width of the image in pixels.
|
||||
height (int): Height of the image in pixels.
|
||||
|
||||
Returns:
|
||||
int: The ID of the newly inserted image record.
|
||||
"""
|
||||
print(f"Inserting image: {img_path},{img_id}")
|
||||
query_str = """
|
||||
INSERT INTO avdb_image (img_path, img_name, width, height)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
RETURNING img_id;
|
||||
"""
|
||||
with self._connection.get_cursor() as cursor:
|
||||
cursor.execute(query_str, (img_path, img_name, width, height))
|
||||
img_id_out = cursor.fetchone()[0]
|
||||
print(f"Warning if true: {img_id != img_id_out}")
|
||||
return img_id_out
|
||||
|
||||
def insert_histogram_data(
|
||||
self,
|
||||
img_id: int,
|
||||
level: int,
|
||||
tilex: int,
|
||||
tiley: int,
|
||||
x: int,
|
||||
y: int,
|
||||
x0: int,
|
||||
y0: int,
|
||||
w: int,
|
||||
h: int,
|
||||
histogram_data: List[float]
|
||||
) -> int:
|
||||
"""
|
||||
Insert histogram data into the histogramfeatures table.
|
||||
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
level (int): Pyramid level.
|
||||
tilex (int): Logical tile x-coordinate.
|
||||
tiley (int): Logical tile y-coordinate.
|
||||
x (int): Upper left x-coordinate (in original resolution).
|
||||
y (int): Upper left y-coordinate (in original resolution).
|
||||
x0 (int): Upper left x-coordinate (in down-sampled resolution).
|
||||
y0 (int): Upper left y-coordinate (in down-sampled resolution).
|
||||
w (int): Width of the histogram region.
|
||||
h (int): Height of the histogram region.
|
||||
histogram_data (np.ndarray): Histogram data as a numpy array.
|
||||
[tile_data: NULL.]
|
||||
|
||||
Returns:
|
||||
int: The ID of the newly inserted histogram record.
|
||||
"""
|
||||
query_str = """
|
||||
INSERT INTO pyramiddata (
|
||||
img_id, level, tilex, tiley, x, y, x0, y0, w, h, hash, histogram
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING pyrdata_id;
|
||||
"""
|
||||
hsh = uuid.uuid4()
|
||||
with self._connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
query_str,
|
||||
(
|
||||
img_id,
|
||||
level,
|
||||
tilex,
|
||||
tiley,
|
||||
x,
|
||||
y,
|
||||
x0,
|
||||
y0,
|
||||
w,
|
||||
h,
|
||||
str(hsh),
|
||||
histogram_data
|
||||
)
|
||||
)
|
||||
try:
|
||||
pyrdata_id = cursor.fetchone()[0]
|
||||
except Exception as e:
|
||||
print(f"EXEPTION: {e}")
|
||||
return pyrdata_id
|
||||
|
||||
def insert_block_data(
|
||||
self,
|
||||
img_id: int,
|
||||
level: int,
|
||||
tilex: int,
|
||||
tiley: int,
|
||||
x: int,
|
||||
y: int,
|
||||
x0: int,
|
||||
y0: int,
|
||||
w: int,
|
||||
h: int,
|
||||
block_data: np.ndarray
|
||||
) -> int:
|
||||
"""
|
||||
Insert block data into the tilefeatures table.
|
||||
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
level (int): Pyramid level.
|
||||
tilex (int): Logical tile x-coordinate.
|
||||
tiley (int): Logical tile y-coordinate.
|
||||
x (int): Center x-coordinate (in original resolution).
|
||||
y (int): Center y-coordinate (in original resolution).
|
||||
x0 (int): Center x-coordinate (in down-sampled resolution).
|
||||
y0 (int): Center y-coordinate (in down-sampled resolution).
|
||||
w (int): Width of the tile.
|
||||
h (int): Height of the tile.
|
||||
block_data (np.ndarray): Tile data as a numpy array.
|
||||
[histogram_data: NULL.]
|
||||
|
||||
Returns:
|
||||
int: The ID of the newly inserted tile record.
|
||||
"""
|
||||
query_str = """
|
||||
INSERT INTO pyramiddata (
|
||||
img_id, level, tilex, tiley, x, y, x0, y0, w, h, hash, block_data
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING pyrdata_id;
|
||||
|
||||
"""
|
||||
hsh = uuid.uuid4()
|
||||
with self._connection.get_cursor() as cursor:
|
||||
cursor.execute(
|
||||
query_str,
|
||||
(
|
||||
img_id,
|
||||
level,
|
||||
tilex,
|
||||
tiley,
|
||||
x,
|
||||
y,
|
||||
x0,
|
||||
y0,
|
||||
w,
|
||||
h,
|
||||
str(hsh),
|
||||
block_data
|
||||
)
|
||||
)
|
||||
pyrdata_id = cursor.fetchone()[0]
|
||||
return pyrdata_id
|
||||
|
||||
def insert_tile_data(self, rows: List[Tuple]) -> None:
|
||||
"""
|
||||
Insert tile data into the pyramiddata table using batch insertion.
|
||||
|
||||
Args:
|
||||
rows (List[Tuple]): List of tuples containing tile data to insert.
|
||||
"""
|
||||
query_str = """
|
||||
INSERT INTO pyramiddata (
|
||||
img_id, level, tilex, tiley, x, y, x0, y0, w, h, hash, tile_data
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
with self._connection.cursor() as cursor:
|
||||
# Use execute_batch for efficient batch insertion
|
||||
print(f"Inserting {len(rows)} rows into pyramid {rows[:1]}")
|
||||
extras.execute_batch(cursor, query_str, rows)
|
||||
self._connection.commit()
|
||||
|
||||
def load_memmapped_histogram_data(self, file_path: str, rows=81):
|
||||
"""
|
||||
Load memory-mapped histogram data from a file.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the file containing the histogram data.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The loaded histogram data as a numpy array.
|
||||
"""
|
||||
return np.memmap(file_path, mode='r+', dtype='float32', shape=(rows,48))
|
||||
|
||||
def load_memmapped_tile_data(self, file_path: str):
|
||||
"""
|
||||
Load memory-mapped tile data from a file.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the file containing the tile data.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The loaded tile data as a numpy array.
|
||||
"""
|
||||
return np.memmap(file_path, mode='r+', dtype=np.uint16)
|
||||
|
||||
def load_memmapped_block_data(self, file_path: str):
|
||||
"""
|
||||
Load memory-mapped tile data from a file.
|
||||
|
||||
Args:
|
||||
file_path (str): Path to the file containing the tile data.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The loaded tile data as a numpy array.
|
||||
"""
|
||||
return np.memmap(file_path, mode='r+', dtype=np.uint16)
|
||||
|
||||
def get_image_by_id(self, img_id: int) -> Tuple:
|
||||
"""
|
||||
Retrieve an image record by its ID.
|
||||
|
||||
Args:
|
||||
img_id (int): ID of the image to retrieve.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple representing the image record.
|
||||
"""
|
||||
query_str = "SELECT * FROM images WHERE img_id = %s;"
|
||||
with self._connection.cursor() as cursor:
|
||||
cursor.execute(query_str, (img_id,))
|
||||
return cursor.fetchone()
|
||||
|
||||
def get_pyramid_metadata_by_image_id(self, img_id: int) -> List[Tuple]:
|
||||
"""
|
||||
Retrieve all pyramid metadata records associated with an image.
|
||||
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
|
||||
Returns:
|
||||
list: A list of tuples representing the pyramid metadata records.
|
||||
"""
|
||||
query_str = "SELECT * FROM pyramidmetadata WHERE img_id = %s;"
|
||||
with self._connection.cursor() as cursor:
|
||||
cursor.execute(query_str, (img_id,))
|
||||
return cursor.fetchall()
|
||||
|
||||
def get_pyramid_metadata_by_id(self, pyramid_id: int) -> Tuple:
|
||||
"""
|
||||
Retrieve a pyramid metadata record by its ID.
|
||||
|
||||
Args:
|
||||
pyramid_id (int): ID of the pyramid metadata record.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple representing the pyramid metadata record.
|
||||
"""
|
||||
query_str = "SELECT * FROM pyramidmetadata WHERE pyramid_id = %s;"
|
||||
with self._connection.cursor() as cursor:
|
||||
cursor.execute(query_str, (pyramid_id,))
|
||||
return cursor.fetchone()
|
||||
|
||||
def get_image_id_by_img_name(self, img_name: str) -> int:
|
||||
"""
|
||||
Retrieve the ID of an image by its name.
|
||||
Args:
|
||||
img_name (str): Name of the image file.
|
||||
|
||||
Returns:
|
||||
int: The ID of the image, or None if not found.
|
||||
"""
|
||||
query_str = "SELECT img_id FROM avdb_image WHERE img_name = %s;"
|
||||
with self._connection.cursor() as cursor:
|
||||
cursor.execute(query_str, (img_name,))
|
||||
result = cursor.fetchone()
|
||||
return result[0] if result else None
|
||||
|
||||
def process_and_insert_histogram_data(
|
||||
self,
|
||||
img_id: int,
|
||||
img_dims: Tuple[int, int],
|
||||
level: int,
|
||||
histogram_file: str,
|
||||
grid_size: int = 9,
|
||||
bin_size: int = 48
|
||||
) -> None:
|
||||
"""
|
||||
Process and insert histogram data for a specific level of an image.
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
img_dims (tuple): Dimensions of the image (width, height).
|
||||
level (int): Pyramid level.
|
||||
histogram_file (str): The memory-mapped histogram file.
|
||||
grid_size (int): Size of the grid (e.g., 9 for 9x9 grid).
|
||||
bin_size (int): Number of bins in the histogram (e.g., 48 for Lab histogram).
|
||||
"""
|
||||
if level<1:
|
||||
raise ValueError("Level must be greater than 0")
|
||||
# Calculate the downsampled resolution for this level
|
||||
resolution = 2 ** level
|
||||
|
||||
# Load the memory-mapped histogram data
|
||||
histogram_data = self.load_memmapped_histogram_data(histogram_file, grid_size**2)
|
||||
|
||||
# print(type(histogram_data))
|
||||
|
||||
print(f"histogram_data shape: {histogram_data.shape}")
|
||||
|
||||
img_w,img_h = img_dims
|
||||
|
||||
# print(list((histogram_data[0,:]).flatten()))
|
||||
counter = 0
|
||||
# Loop through each grid cell
|
||||
for tilex in range(grid_size):
|
||||
for tiley in range(grid_size):
|
||||
# Calculate spatial coordinates (simplified for now)
|
||||
# These calculations can be adjusted based on your specific requirements
|
||||
x = tilex * (img_w // resolution)
|
||||
y = tiley * (img_h // resolution)
|
||||
x0 = tilex * (img_w // (resolution * grid_size ))
|
||||
y0 = tiley * (img_h // (resolution * grid_size))
|
||||
w = img_w // (resolution / 2)
|
||||
h = img_h // (resolution / 2)
|
||||
# Insert the histogram data into the database
|
||||
# print(f"Inserting histogram data for tilex: {tilex}, tiley: {tiley}")
|
||||
# print(f"x={x}, y={y}, x0={x0}, y0={y0}, w={w}, h={h}")
|
||||
# print(type(list((histogram_data[counter,:]).flatten().tolist())[0]))
|
||||
self.insert_histogram_data(
|
||||
img_id=img_id,
|
||||
level=level,
|
||||
tilex=tilex,
|
||||
tiley=tiley,
|
||||
x=x,
|
||||
y=y,
|
||||
x0=x0,
|
||||
y0=y0,
|
||||
w=w,
|
||||
h=h,
|
||||
histogram_data=list((histogram_data[counter,:]).flatten().tolist())
|
||||
)
|
||||
counter += 1
|
||||
|
||||
def to_unsigned(self, signed_int):
|
||||
return signed_int + (1 << 8) if signed_int < 0 else signed_int
|
||||
|
||||
def process_and_insert_tile_data_batched(
|
||||
self,
|
||||
img_id: int,
|
||||
img_dims: Tuple[int, int],
|
||||
level: int,
|
||||
tile_file: str
|
||||
) -> None:
|
||||
"""
|
||||
Process and insert tile data for a specific level of an image.
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
img_dims (Tuple[int, int]): Dimensions of the image (width, height).
|
||||
level (int): Pyramid level.
|
||||
tile_file (str): The memory-mapped tile file.
|
||||
grid_size (int): Size of the grid.
|
||||
"""
|
||||
# Calculate the downsampled resolution for this level
|
||||
resolution = 2 ** level
|
||||
img_w, img_h = img_dims
|
||||
# Load the memory-mapped tile data
|
||||
print(tile_file)
|
||||
tile_data = self.load_memmapped_tile_data(tile_file)
|
||||
|
||||
print(f"img dims: {img_dims}")
|
||||
print(f"tile data shape:{tile_data.shape}")
|
||||
|
||||
tilew = img_w // resolution
|
||||
tileh = img_h // resolution
|
||||
width_t = img_w // tilew
|
||||
height_t = img_h // tileh
|
||||
|
||||
grid_size = 1
|
||||
|
||||
def gen_tiles():
|
||||
counter = 0
|
||||
# Loop through each tile
|
||||
for tiley in range(tileh):
|
||||
for tilex in range(tilew):
|
||||
|
||||
x0 = tilex * width_t
|
||||
y0 = tiley * height_t
|
||||
w = grid_size
|
||||
h = 1
|
||||
|
||||
# print(f"{counter} - {level} - {tilex} - {tiley} - {tilew} - {tileh} - {width_t} - {height_t} - {x0} - {y0} - {w} - {h} -- {tile_data[(counter * 3):((counter+1) * 3)]}")
|
||||
# Insert the tile data into the database
|
||||
try:
|
||||
tiledata = list(
|
||||
(
|
||||
np.r_[
|
||||
tile_data[
|
||||
(counter * 3):
|
||||
((counter+1) * 3)
|
||||
]
|
||||
]
|
||||
).flatten().tolist()
|
||||
)
|
||||
counter += 1
|
||||
yield (
|
||||
img_id,
|
||||
level,
|
||||
tilex,
|
||||
tiley,
|
||||
width_t,
|
||||
height_t,
|
||||
x0,
|
||||
y0,
|
||||
w,
|
||||
h,
|
||||
str(uuid.uuid4()),
|
||||
list(map(self.to_unsigned,tiledata))
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
|
||||
all_for_insertion = list(gen_tiles())
|
||||
print(f"Inserting {len(all_for_insertion)} tiles")
|
||||
|
||||
self.insert_tile_data(all_for_insertion)
|
||||
|
||||
|
||||
def process_and_insert_block_data(
|
||||
self,
|
||||
img_id: int,
|
||||
img_dims: Tuple[int, int],
|
||||
level: int,
|
||||
block_file: str,
|
||||
grid_size: int = 9
|
||||
) -> None:
|
||||
"""
|
||||
Process and insert block data for a specific level of an image.
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
img_dims (Tuple[int, int]): Dimensions of the image (width, height).
|
||||
level (int): Pyramid level.
|
||||
block_file (str): The memory-mapped block file.
|
||||
grid_size (int): Size of the grid.
|
||||
"""
|
||||
# Calculate the downsampled resolution for this level
|
||||
resolution = 2 ** level
|
||||
img_w, img_h = img_dims
|
||||
|
||||
# if level == 6:
|
||||
# row_size = 9
|
||||
# elif level == 7:
|
||||
# row_size = 5
|
||||
# else:
|
||||
|
||||
if level not in (6, 7):
|
||||
raise ValueError(f"Unsupported level ({level}). Level must be 6 or 7.")
|
||||
|
||||
# Load the memory-mapped block data
|
||||
block_data = self.load_memmapped_block_data(block_file)
|
||||
|
||||
# print(f"img dims: {img_dims}")
|
||||
# print(f"block data shape:{block_data.shape}")
|
||||
|
||||
tilew = img_w // resolution
|
||||
tileh = img_h // resolution
|
||||
width_t = img_w // tilew
|
||||
height_t = img_h // tileh
|
||||
|
||||
# print(f"tilew: {tilew}, tileh: {tileh}, width_t: {width_t}, height_t: {height_t}")
|
||||
|
||||
counter = 0
|
||||
# Loop through each block
|
||||
for tilex in range(tilew-2):
|
||||
for tiley in range(tileh-2):
|
||||
|
||||
x0 = tilex * width_t
|
||||
y0 = tiley * height_t
|
||||
w = grid_size
|
||||
h = 1
|
||||
|
||||
# print(f"{counter} - {level} - {tilex} - {tiley} - {tilew} - {tileh} - {width_t} - {height_t} - {x0} - {y0} - {w} - {h}")
|
||||
# Insert the block data into the database
|
||||
# print(f"{counter * grid_size * 3} - {(counter+1) * grid_size * 3}")
|
||||
|
||||
try:
|
||||
self.insert_block_data(
|
||||
img_id=img_id,
|
||||
level=level,
|
||||
tilex=tilex,
|
||||
tiley=tiley,
|
||||
x=w,
|
||||
y=h,
|
||||
x0=x0,
|
||||
y0=y0,
|
||||
w=w,
|
||||
h=h,
|
||||
block_data=list(
|
||||
(
|
||||
np.r_[
|
||||
block_data[
|
||||
(counter * grid_size * 3):
|
||||
((counter+1) * grid_size * 3)
|
||||
]
|
||||
# np.zeros((9 - grid_size) * 3)
|
||||
]
|
||||
).flatten().tolist()
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
pass
|
||||
counter += 1
|
||||
|
||||
def extract_image_base(self, fullpath: str) -> str:
|
||||
|
||||
term = r'.*(IMG_\d{4}|TESTIMG).*'
|
||||
m = re.match(term, fullpath)
|
||||
if m is not None:
|
||||
tkn = m.group(1)
|
||||
return f"img/{tkn}/{tkn}.jpeg", tkn
|
||||
raise ValueError(f"Could not extract image base from {fullpath}")
|
||||
|
||||
def process_image_analysis(
|
||||
self,
|
||||
img_path_in: str,
|
||||
max_level: int = 8
|
||||
) -> None:
|
||||
"""
|
||||
Process and insert all analysis data for an image across multiple levels.
|
||||
|
||||
Args:
|
||||
img_id (int): ID of the image.
|
||||
img_path (str): Full path.
|
||||
max_level (int): max exponent to pyr down splits
|
||||
"""
|
||||
img_path, img_tkn = self.extract_image_base(img_path_in)
|
||||
print(img_path)
|
||||
# assumes image has been inserted into image table
|
||||
img_id = self.get_image_id_by_img_name(os.path.basename(img_path))
|
||||
|
||||
print(f"IMAGE ID: {img_id}")
|
||||
|
||||
# insert image metadata
|
||||
if not os.path.exists(img_path):
|
||||
raise FileNotFoundError(f"{img_path} does not exist")
|
||||
|
||||
img = cv.imread(img_path)
|
||||
|
||||
width = img.shape[1]
|
||||
height = img.shape[0]
|
||||
|
||||
print(f"Processing image {img_path} {width}x{height} {img_path}")
|
||||
|
||||
for level in range(max_level):
|
||||
print(LEVELS_CONFIG[level])
|
||||
|
||||
if (LEVELS_CONFIG[level]["gridsplit"]) is not None:
|
||||
data_path = f"{os.path.dirname(img_path)}/{img_path}.histos.mmap"
|
||||
grid_size = LEVELS_CONFIG[level]["gridsplit"] + 1
|
||||
# Process histogram data for this level
|
||||
self.process_and_insert_histogram_data(
|
||||
img_id=img_id,
|
||||
img_dims=(width, height),
|
||||
level=level,
|
||||
histogram_file=data_path,
|
||||
grid_size=grid_size
|
||||
)
|
||||
if (LEVELS_CONFIG[level]["tiles"]):
|
||||
data_path = f"img/{img_tkn}/{img_tkn}.lab.pyr_{level}.jpeg.tiles.mmap"
|
||||
print(data_path)
|
||||
print("INSERTING TILE DATA")
|
||||
# Process tile data for this level
|
||||
self.process_and_insert_tile_data_batched(
|
||||
img_id=img_id,
|
||||
img_dims=(width, height),
|
||||
level=level,
|
||||
tile_file=data_path
|
||||
)
|
||||
|
||||
if (LEVELS_CONFIG[level]["targetblocks"]) is not None:
|
||||
data_path = f"{os.path.dirname(img_path)}/{img_path}.blocks.mmap"
|
||||
print("INSERTING BLOCK DATA")
|
||||
# Process block data for this level
|
||||
self.process_and_insert_block_data(
|
||||
img_id=img_id,
|
||||
img_dims=(width, height),
|
||||
level=level,
|
||||
block_file=data_path
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with DBConnection('testdb').get_connection() as db_conn:
|
||||
db_ops = DBOps(db_conn)
|
||||
db_ops.process_image_analysis('home/kfl/dev/git/avdb-py-v1/img/TESTIMG/TESTIMG.jpeg')
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import os
|
||||
import time
|
||||
from random import randint
|
||||
from ast import literal_eval
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import pymemcache
|
||||
|
||||
from avdb.db import DBConnection, DBOps
|
||||
|
||||
LIMIT=100
|
||||
LEVEL=randint(3,7)
|
||||
SEARCH_TILE_OFFSET=-2
|
||||
|
||||
class ImagePyramidState:
|
||||
def __init__(self, levels=8):
|
||||
self._connection = DBConnection('testdb')
|
||||
self._memcache = pymemcache.Client("127.0.0.1:11211")
|
||||
self.image_width = 1280
|
||||
self.image_height = 960
|
||||
self.levels = levels
|
||||
self.colorspace = 'lab'
|
||||
self.state_map = {}
|
||||
self.img_map = {}
|
||||
self.src_img_id, self.xlimit, self.ylimit, _ = self.select_initial_tile(src_img_id=None,level=LEVEL, inception=True)
|
||||
filename = self.get_imagefile_token_from_id(self.src_img_id)
|
||||
self.image_filetoken = os.path.basename(filename).removesuffix(".jpeg")
|
||||
print(self.image_filetoken)
|
||||
self.initialize_image_map()
|
||||
self.initialize_state_map()
|
||||
|
||||
|
||||
def initialize_state_map(self):
|
||||
"""
|
||||
Initialize state arrays with 0s (0 = original/no change)
|
||||
"""
|
||||
for lvl in range(1,8):
|
||||
self.state_map[lvl] = np.zeros(
|
||||
(
|
||||
# int(self.image_height/(2**lvl)),
|
||||
# int(self.image_width/(2**lvl)),
|
||||
int(self.xlimit),
|
||||
int(self.ylimit),
|
||||
3
|
||||
),
|
||||
dtype=np.uint16
|
||||
)
|
||||
|
||||
def initialize_image_map(self):
|
||||
"""
|
||||
Initialize image arrays with downsampled images
|
||||
"""
|
||||
for lvl in range(1,8):
|
||||
|
||||
imgpath = f"/home/kfl/dev/git/avdb-py-v1/img/{self.image_filetoken}/{self.image_filetoken}.{self.colorspace}.pyr_{lvl}_display.jpeg"
|
||||
|
||||
img = cv.imread(imgpath)
|
||||
print(f"imgpath:{imgpath} (level:) SHAPE: {img.shape}")
|
||||
self.img_map[lvl] = img
|
||||
|
||||
|
||||
def match_tile(self, src_img_id, src_tilex, src_tiley, search_tile, level=3, limit=100):
|
||||
"""
|
||||
pyrdata_id, img_id, level, tilex_a, tiley_a, x_a, y_a, x0_a, y0_a, w_a, h_a,
|
||||
tilex_b, tiley_b, x_b, y_b, x0_b, y0_b, w_b, h_b, hash, tile_data
|
||||
-- and tilex_a = {src_tilex}
|
||||
-- and tiley_a = {src_tiley}
|
||||
"""
|
||||
|
||||
search_query = f"""
|
||||
select *, (tile_data <-> '{search_tile}')
|
||||
from pyramiddata_2tiles
|
||||
where level = {level}
|
||||
and img_id <> {src_img_id}
|
||||
order by tile_data <-> '{search_tile}' asc
|
||||
limit {limit};
|
||||
"""
|
||||
psqldata = self._memcache.get(f"{str(search_tile).replace(', ', ',')}_{src_img_id}_{level}_{limit}")
|
||||
|
||||
if not psqldata:
|
||||
with self._connection.get_cursor() as cursor:
|
||||
|
||||
cursor.execute(search_query, (search_tile, level, src_img_id, LIMIT))
|
||||
rows = cursor.fetchall()
|
||||
self._memcache.set(f"{str(search_tile).replace(', ', ',')}_{src_img_id}_{level}_{limit}", rows)
|
||||
return rows
|
||||
|
||||
else:
|
||||
print ("Loaded data from memcached")
|
||||
return tuple(literal_eval(psqldata.decode("utf-8")))
|
||||
|
||||
def get_imagefile_token_from_id(self, src_img_id):
|
||||
"""
|
||||
"""
|
||||
if src_img_id is not None:
|
||||
search_q = f"""
|
||||
select *
|
||||
from avdb_image
|
||||
where img_id = {src_img_id};
|
||||
"""
|
||||
# print(search_q)
|
||||
with self._connection.get_cursor() as cursor:
|
||||
cursor.execute(search_q, src_img_id)
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
raise ValueError("No image found with the given img_id.")
|
||||
return row[2]
|
||||
|
||||
|
||||
def select_initial_tile(self, src_img_id=None, level=LEVEL, src_tilex=None, src_tiley=None, inception=False):
|
||||
"""
|
||||
"""
|
||||
if src_img_id is None:
|
||||
src_img_id = randint(1, 5) # 845)
|
||||
if src_tilex is None:
|
||||
src_tilex = randint(0, (1280 // (2**level)) - 5)
|
||||
if src_tiley is None:
|
||||
src_tiley = randint(0, (960 // (2**level)) - 5)
|
||||
|
||||
search_query = f"""
|
||||
select *
|
||||
from pyramiddata_2tiles
|
||||
where level = {level}
|
||||
and img_id = {src_img_id}
|
||||
and tilex_a = {src_tilex}
|
||||
and tiley_a = {src_tiley}
|
||||
limit {LIMIT};
|
||||
"""
|
||||
# print(search_query)
|
||||
with self._connection.get_cursor() as cursor:
|
||||
cursor.execute(search_query, (level, src_img_id, src_tilex, src_tiley))
|
||||
row = cursor.fetchone()
|
||||
if inception:
|
||||
return src_img_id, (1280 // (2**level)) - 1, (960 // (2**level)) - 1, row
|
||||
return src_img_id, src_tilex, src_tiley, row
|
||||
|
||||
|
||||
def run_generation(self):
|
||||
"""
|
||||
"""
|
||||
# choose random tile to update
|
||||
level = LEVEL
|
||||
generation_counter = 0
|
||||
|
||||
# grid = []
|
||||
# for y in range(0, self.ylimit - 6, 3):
|
||||
# for x in range(0, self.xlimit - 6, 3):
|
||||
# grid += [(y,x)]
|
||||
|
||||
# print(f"limits: {(self.ylimit, self.xlimit)}")
|
||||
|
||||
# for (y,x) in grid:
|
||||
while True:
|
||||
|
||||
lvl = level
|
||||
# _, tilex, tiley, init_tile = self.select_initial_tile(src_img_id=self.src_img_id, level=lvl, src_tilex=x, src_tiley=y, inception=False)
|
||||
_, x, y, init_tile = self.select_initial_tile(src_img_id=self.src_img_id, level=lvl, src_tilex=None, src_tiley=None, inception=False)
|
||||
|
||||
if init_tile is not None:
|
||||
|
||||
# print(f"INIT: {init_tile}")
|
||||
# find similar tiles to swap in
|
||||
found_tiles = self.match_tile(
|
||||
src_img_id=self.src_img_id,
|
||||
src_tilex=x,
|
||||
src_tiley=y,
|
||||
search_tile=init_tile[-1],
|
||||
level=lvl
|
||||
)
|
||||
# print(found_tiles[:3])
|
||||
|
||||
next_tile = found_tiles[randint(0, len(found_tiles)-1)]
|
||||
|
||||
# update img + state map
|
||||
sz = 2**lvl
|
||||
# print(next_tile)
|
||||
next_tile_ = list(map(int, next_tile[SEARCH_TILE_OFFSET].replace('[','').replace(']','').split(',')))
|
||||
|
||||
# print(f"next_tile_: {next_tile_}")
|
||||
# print(f"{next_tile[1]}, {init_tile[3]}, {init_tile[4]}")
|
||||
|
||||
self.img_map[lvl][y*sz:(y+1)*sz,x*sz:(x+1)*sz] = [
|
||||
next_tile_[0],
|
||||
next_tile_[1],
|
||||
next_tile_[2]
|
||||
]
|
||||
if generation_counter % 100 == 0:
|
||||
shape = self.state_map[lvl].shape
|
||||
print(shape)
|
||||
for x in range(shape[0]):
|
||||
for y in range(shape[1]):
|
||||
st = self.state_map[lvl][x,y]
|
||||
if st[0] > 0 or st[1] > 0.0 or st[2] > 0.0:
|
||||
print(
|
||||
f"x: {x}, y: {y} st: {st}"
|
||||
)
|
||||
|
||||
self.state_map[lvl][x, y] = [next_tile[1], next_tile[3], next_tile[4]]
|
||||
|
||||
cv.namedWindow("Image", cv.WINDOW_NORMAL)
|
||||
cv.resizeWindow("Image", 1280, 960)
|
||||
cv.imshow("Image", cv.cvtColor(self.img_map[lvl], cv.COLOR_Lab2RGB))
|
||||
time.sleep(0.2)
|
||||
cv.waitKey(1)
|
||||
generation_counter += 1
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ips = ImagePyramidState()
|
||||
for i in range(100):
|
||||
ips.run_generation()
|
||||
|
||||
Reference in New Issue
Block a user