Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
5aceeb7
feat: Add transformers for videos and bounding boxes
ad-claw000 May 20, 2026
7c2d8ce
Fix pre-commit failures and remove optional dependencies from transfo…
ad-claw000 May 20, 2026
6278058
Fix: Address transformer PR review comments
ad-claw000 May 21, 2026
d0d7eb7
fix(transformers): evaluate annotation counts dynamically per transac…
ad-claw000 May 21, 2026
6e8010b
Fix pre-commit issues
ad-claw000 May 21, 2026
ab6958d
fix(transformers): evaluate image and video counts dynamically per tr…
ad-claw000 May 21, 2026
cfc08ad
Fix: Address review comments on transformers
ad-claw000 May 21, 2026
5c3f442
fix(transformers): remove unused _add_image_index and compute dynamic…
ad-claw000 May 21, 2026
30721e5
fix: apply autopep8 formatting
May 21, 2026
d28a4fd
Fix remaining unused variable and unused index review comments
ad-claw000 May 21, 2026
32f1400
Merge branch 'develop' into fix/issue-335
luisremis May 22, 2026
871c955
Merge remote-tracking branch 'origin/develop' into fix/issue-335
ad-claw000 May 23, 2026
1e2f6a7
fix: lazily initialize descriptor sets on first AddImage in getitem
ad-claw000 May 23, 2026
cf75c40
fix: lazily initialize descriptor sets on first AddImage in getitem f…
ad-claw000 May 23, 2026
8d37ab8
fix(transformers): avoid duplicate embedding generation for the first…
ad-claw000 May 23, 2026
02b8fa2
style: fix autopep8 formatting in facenet_pytorch_embeddings.py
ad-claw000 May 23, 2026
f68a079
fix(transformers): restore _add_image_index for backward compat and a…
ad-claw000 May 24, 2026
65e6c26
Merge branch 'develop' into fix/issue-335
luisremis May 24, 2026
1f30391
Merge remote-tracking branch 'origin/develop' into fix/issue-335
ad-claw000 May 24, 2026
f845763
test: add coverage for ImageProperties, clip, and facenet transformers
ad-claw000 May 24, 2026
f5b565e
test: remove unused sys import in test_Transformers.py
ad-claw000 May 24, 2026
b59526f
fix: set _descriptorset_initialized only on success or if exists
ad-claw000 May 25, 2026
f202783
test: add more unit tests for new transformer features
ad-claw000 May 25, 2026
3bb502a
fix: avoid mutating traceback in logger.exception
ad-claw000 May 25, 2026
cd2cd16
fix: run pre-commit autopep8 to resolve CI failure
ad-claw000 May 25, 2026
00cb81d
test: mock clip.generate_embedding to return float32 bytes
ad-claw000 May 25, 2026
d95e292
test: add placeholder blobs for image/video commands in test data
ad-claw000 May 25, 2026
340b007
fix: address pre-commit formatting issues in test_Transformers.py
ad-claw000 May 25, 2026
56ae22b
Merge remote-tracking branch 'origin/develop' into fix/issue-335
ad-claw000 May 25, 2026
339e593
test: add tests for descriptor initialization retries
ad-claw000 May 25, 2026
c7f5266
test: update mocked descriptor set name to match default in FacenetPy…
ad-claw000 May 25, 2026
0a49c19
Merge branch 'develop' into fix/issue-335
luisremis May 26, 2026
8789927
fix: make common_properties and bounding_box_properties true no-ops w…
ad-claw000 May 26, 2026
28c8592
fix(ci): fix EACCES permission denied by making teardown chmod robust
ad-claw000 May 27, 2026
b5df355
fix: address review comments on clip/facenet embeddings and run_test_…
ad-claw000 May 27, 2026
a5df6fd
test: assert AddDescriptor is omitted on initialization failure
ad-claw000 May 27, 2026
38b625b
test: add tests for early return and exception handling
ad-claw000 May 28, 2026
7658971
test: add exception handling and explicit id tests for transformers
ad-claw000 May 29, 2026
30ba71e
fix: address review comments on PR #720
ad-claw000 May 29, 2026
5379c92
test: update image properties exception handling test
ad-claw000 May 29, 2026
52ea1e1
fix: address review comments on exception handling in transformers
ad-claw000 May 29, 2026
da2a251
fix: address review comments on PR #720
ad-claw000 May 31, 2026
45acbd6
fix: address review comments on PR #720
ad-claw000 May 31, 2026
af3ed1a
fix: address review comments on PR #720
ad-claw000 Jun 1, 2026
ee420a9
test: add explicit validation for auto-generated uuid fields
Jun 4, 2026
bf5525b
Address review comments regarding exception handling and lazy imports
Jun 5, 2026
3eeeea4
fix: address review comments on PR #720
Jun 6, 2026
7cff2ca
fix(transformers): avoid computing embeddings before descriptorset in…
Jun 6, 2026
0ef0f7e
fix: address review comments on PR #720
Jun 8, 2026
6dfdbed
test: add coverage for transformers __init__.py
Jun 8, 2026
0ac0af5
style: apply autopep8 formatting
Jun 8, 2026
7ceee2e
fix(transformers): handle missing _ref and remove duplicate pytest im…
Jun 9, 2026
5f83f8a
test: add backend error simulation for descriptorset initialization
Jun 10, 2026
8c9555e
fix: address review comments on transformers init and properties
Jun 11, 2026
1b4ebeb
fix: address review comments on PR #720
Jun 13, 2026
4f2fd11
Merge branch 'develop' into fix/issue-335
luisremis Jun 15, 2026
4457d67
Merge branch 'develop' into fix/issue-335
luisremis Jun 24, 2026
ee0284d
fix: address review comments on PR #720
ad-claw000 Jun 27, 2026
5005f1c
Merge branch 'develop' into fix/issue-335
luisremis Jun 29, 2026
b839442
Merge branch 'develop' into fix/issue-335
luisremis Jul 1, 2026
fb6fde7
fix: log exception in get_descriptorset_list instead of silent pass
Jul 2, 2026
03b02e6
fix: address review comments and format files
Jul 2, 2026
c567638
fix: generate UUID when id is missing or empty
Jul 2, 2026
ef39a8f
style: address reviewer comments on exceptions and bash quoting
Jul 2, 2026
d3dec0f
fix(ci): disable jupyterlab-dash to prevent build failure on Node 24
Jul 2, 2026
621bee7
fix: extract cmd_name before try block, derive clip dim dynamically
Jul 2, 2026
7fd67e5
fix(ci): remove jupyterlab-dash to fix notebook image build
Jul 2, 2026
6a29057
fix: address review comments on transformers
Jul 2, 2026
ef3d484
fix(ci): fix root-owned directories left behind by test container
Jul 2, 2026
99e2fe7
fix(ci): remove jupyter lab build which fails in CI
Jul 2, 2026
0b07538
fix: address review comments on exception handling and bash syntax
Jul 2, 2026
2ffeb3b
test: add exhaustive tests for backend initialization failures in cli…
Jul 2, 2026
9cc4f1d
fix: address review comments on test patching and permissions
Jul 3, 2026
27cd97b
fix(ci): fix permission errors during cp by using rsync with excludes
Jul 3, 2026
7d197e2
fix: address review comments on logging and imports
Jul 3, 2026
e22f9fb
style: fix autopep8 formatting in test_Transformers.py to pass pre-co…
Jul 3, 2026
980b3a1
fix: address review comments for transformers
Jul 3, 2026
1c7172b
test: add tests for clip/facenet initialization and backend failure s…
Jul 6, 2026
c3728be
fix: move get_utils inside try block to make index creation best-effort
Jul 6, 2026
ab114dc
ci: fix permission denied on checkout by cleaning up properly
Jul 6, 2026
859b825
fix: address review comments on PR 720
Jul 6, 2026
4ec687f
fix: safely handle missing blobs in CLIP and Facenet transformers
Jul 10, 2026
c0a1e08
fix(transformers): increment blob_index when skipping missing blobs
Jul 10, 2026
14de714
test: restrict permissions on CA directories to the current user
Jul 10, 2026
e3cbc24
fix: address remaining review comments on PR 720
Jul 10, 2026
7169c52
test: add tests for clip/facenet initialization and backend failure s…
Jul 12, 2026
a5b3e0d
fix: address review comments on transformers and fix failing test
Jul 12, 2026
1bb5c4b
Address review comments on transformer tests and IndexError handling
Jul 12, 2026
fd80d9c
fix: resolve pre-commit failure in test_Transformers.py
Jul 12, 2026
8541cde
Use 'is not None' for property validation in BoundingBoxProperties an…
Jul 12, 2026
65450a2
build(docker): remove redundant numpy installation
Jul 12, 2026
edc1e34
fix: address review comments on PR #720
ad-claw000 Jul 12, 2026
a2f41e2
fix: use context manager for Image.open to avoid resource leaks
Jul 13, 2026
158142b
test: add edge-case testing for transformer init and malformed commands
Jul 13, 2026
8ff8b6a
test: add tests for missing blob handling
Jul 14, 2026
2995fa1
style: address review comments on formatting and test runner permissions
Jul 14, 2026
9c8ae4d
fix: skip descriptor creation unless _ref is present in transformers
Jul 14, 2026
40416a7
fix(ci): remove jupyter lab build which fails in CI
Jul 14, 2026
a7d0b3f
ci: ignore test/aperturedb on cp to pytorch image
Jul 14, 2026
61dadbf
ci: kill orphaned containers during cleanup
Jul 14, 2026
2c4be3e
fix: guard AddImage payload with isinstance to avoid TypeError on mal…
Jul 14, 2026
cbe1a2e
test: Fix NameError in malformed payload tests by adding dynamic imports
Jul 14, 2026
6c4355d
ci: fix container cleanup and permission restoration for cert artifacts
Jul 14, 2026
3b38daf
style: wrap long conditionals and patch paths to satisfy autopep8
Jul 14, 2026
4a08ec9
fix: address review comments on PR #720
Jul 17, 2026
405f40f
fix: remove redundant numpy installation in Dockerfile
Jul 17, 2026
e8ddce6
Address review comments: update __all__ and fix autopep8 line length
Jul 17, 2026
dffee15
fix: refine CI cleanup filter and remove optional transformers from _…
Jul 17, 2026
3b1ce14
test: verify initialization failure scenarios in clip/facenet embeddings
Jul 17, 2026
1cd8201
test: separate import flags for clip and facenet dependencies to avoi…
Jul 17, 2026
81e6ba3
test: add edge cases for missing and empty IDs in video and image pro…
Jul 17, 2026
2a6f1c4
fix: address review comments on PR #720
Jul 20, 2026
d988a72
fix(test): re-add pytest import to fix test_Transformers CI failure
ad-claw000 Jul 20, 2026
dc80504
fix: address review comments on PR #720
ad-claw000 Jul 23, 2026
cbea972
test: verify clip/facenet initialization failure scenarios
ad-claw000 Jul 24, 2026
6a5cb32
fix: address review comments on transformers and run_test_container
ad-claw000 Jul 25, 2026
6967b6a
fix(transformers): break on missing blobs instead of continuing
ad-claw000 Aug 1, 2026
ed39f8a
fix: address review comments on PR #720
ad-claw000 Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ jobs:
steps:

- name: Cleanup previous run
run: docker run --rm -v ${{ github.workspace }}:/workspace alpine sh -c "rm -rf /workspace/test/aperturedb"
run: |
docker ps -q -a --filter "name=${{ runner.name }}_pr" | xargs -r docker rm -f || true
docker run --rm -v ${{ github.workspace }}:/workspace alpine sh -c "rm -rf /workspace/test/aperturedb" || true
Comment thread
ad-claw000 marked this conversation as resolved.
continue-on-error: true
Comment thread
ad-claw000 marked this conversation as resolved.

- uses: actions/checkout@v3
Expand Down Expand Up @@ -80,7 +82,9 @@ jobs:
steps:

- name: Cleanup previous run
run: docker run --rm -v ${{ github.workspace }}:/workspace alpine sh -c "rm -rf /workspace/test/aperturedb"
run: |
docker ps -q -a --filter "name=${{ runner.name }}_${{ github.run_id }}" | xargs -r docker rm -f || true
docker run --rm -v ${{ github.workspace }}:/workspace alpine sh -c "rm -rf /workspace/test/aperturedb" || true
continue-on-error: true
Comment thread
ad-claw000 marked this conversation as resolved.

- uses: actions/checkout@v3
Expand All @@ -101,6 +105,7 @@ jobs:
uses: google-github-actions/setup-gcloud@v2
- name: Build tests on pytorch GPU image
run: |
docker run --rm -v ${{ github.workspace }}:/workspace alpine sh -c "rm -rf /workspace/test/aperturedb" || true
rm -rf docker/pytorch-gpu/aperturedata
mkdir -p docker/pytorch-gpu/aperturedata
cp -r aperturedb pyproject.toml README.md test docker/pytorch-gpu/aperturedata
Expand Down
36 changes: 36 additions & 0 deletions aperturedb/transformers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
def __getattr__(name):
if name == "Transformer":
from .transformer import Transformer
val = Transformer
elif name == "CommonProperties":
from .common_properties import CommonProperties
val = CommonProperties
elif name == "ImageProperties":
from .image_properties import ImageProperties
val = ImageProperties
elif name == "VideoProperties":
from .video_properties import VideoProperties
val = VideoProperties
elif name == "BoundingBoxProperties":
from .bounding_box_properties import BoundingBoxProperties
val = BoundingBoxProperties
elif name == "FacenetPyTorchEmbeddings":
from .facenet_pytorch_embeddings import FacenetPyTorchEmbeddings
val = FacenetPyTorchEmbeddings
elif name == "CLIPPyTorchEmbeddings":
from .clip_pytorch_embeddings import CLIPPyTorchEmbeddings
val = CLIPPyTorchEmbeddings
else:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

globals()[name] = val
return val


__all__ = [
"Transformer",
"CommonProperties",
"ImageProperties",
"VideoProperties",
"BoundingBoxProperties",
]
Comment thread
ad-claw000 marked this conversation as resolved.
Comment thread
ad-claw000 marked this conversation as resolved.
Comment thread
ad-claw000 marked this conversation as resolved.
Comment thread
ad-claw000 marked this conversation as resolved.
Comment thread
ad-claw000 marked this conversation as resolved.
Comment thread
ad-claw000 marked this conversation as resolved.
41 changes: 41 additions & 0 deletions aperturedb/transformers/bounding_box_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from aperturedb.transformers.transformer import Transformer
from aperturedb.Subscriptable import Subscriptable
import logging

logger = logging.getLogger(__name__)


class BoundingBoxProperties(Transformer):
"""
This transformer applies static annotation metadata (like annotation_source
and annotation_mode) to bounding boxes and polygons.
"""

def __init__(self, data: Subscriptable, **kwargs) -> None:
super().__init__(data, **kwargs)
self.annotation_source = kwargs.get("annotation_source", None)
self.annotation_mode = kwargs.get("annotation_mode", None)

def getitem(self, subscript):
if self.annotation_source is None and self.annotation_mode is None:
return self.data[subscript]

x = self.data[subscript]
# Iterate over current transaction commands to handle variable annotation counts
for cmd_dict in x[0]:
try:
if not isinstance(cmd_dict, dict):
continue
for cmd_name in ["AddBoundingBox", "AddPolygon"]:
if cmd_name in cmd_dict:
src_properties = cmd_dict[cmd_name].setdefault(
"properties", {})
if self.annotation_source is not None:
src_properties["annotation_source"] = self.annotation_source
if self.annotation_mode is not None:
src_properties["annotation_mode"] = self.annotation_mode
except Exception:
logger.exception(
"Error applying bounding box properties", stack_info=True)

return x
Comment thread
ad-claw000 marked this conversation as resolved.
8 changes: 4 additions & 4 deletions aperturedb/transformers/clip.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@

import logging
import numpy as np
from PIL import Image

logger = logging.getLogger(__name__)

Expand All @@ -13,12 +11,14 @@
"""

try:
import numpy as np
from PIL import Image
import clip
import torch
import cv2
except ImportError:
except ImportError as e:
logger.critical(error_message)
exit(1)
raise ImportError(error_message) from e
Comment thread
ad-claw000 marked this conversation as resolved.

descriptor_set = "ViT-B/16"
device = "cuda" if torch.cuda.is_available() else "cpu"
Expand Down
105 changes: 76 additions & 29 deletions aperturedb/transformers/clip_pytorch_embeddings.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import hashlib
import logging
from aperturedb.Subscriptable import Subscriptable
from aperturedb.transformers.transformer import Transformer
from .clip import generate_embedding, descriptor_set

logger = logging.getLogger(__name__)


class CLIPPyTorchEmbeddings(Transformer):
"""
Expand All @@ -19,38 +22,82 @@ def __init__(self, data: Subscriptable, **kwargs) -> None:
self.search_set_name = kwargs.pop(
"search_set_name", descriptor_set)
super().__init__(data, **kwargs)

# Let's sample some data to figure out the descriptorset we need.
if len(self._add_image_index) > 0:
sample = generate_embedding(self.data[0][1][0])
utils = self.get_utils()
utils.add_descriptorset(
self.search_set_name, dim=len(sample) // 4, metric=["CS"])
self._descriptorset_initialized = False

def getitem(self, subscript):
x = self.data[subscript]

for ic in self._add_image_index:
serialized = generate_embedding(x[1][ic])
# If the image already has an image_sha256, we use it.
image_sha256 = x[0][ic]["AddImage"].get("properties", {}).get(
"adb_image_sha256", None)
if not image_sha256:
image_sha256 = hashlib.sha256(x[1][ic]).hexdigest()
x[1].append(serialized)
x[0].append(
{
"AddDescriptor": {
"set": self.search_set_name,
"properties": {
"image_sha256": image_sha256,
},
"if_not_found": {
"image_sha256": ["==", image_sha256],
},
"connect": {
"ref": x[0][ic]["AddImage"]["_ref"]
blob_index = 0
new_descriptors = []
new_blobs = []

for cmd_dict in x[0]:
cmd_name = None
if isinstance(cmd_dict, dict) and len(cmd_dict) > 0:
cmd_name = next(iter(cmd_dict.keys()))

if cmd_name == "AddImage":
try:
blob = x[1][blob_index]
except IndexError:
logger.warning(
f"Missing blob for AddImage at index {blob_index}. Stopping blob processing for this transaction.")
break

serialized = None

if (
not getattr(self, "_descriptorset_initialized", False)
and isinstance(cmd_dict["AddImage"], dict)
and "_ref" in cmd_dict["AddImage"]
):
try:
serialized = generate_embedding(blob)
dim = len(serialized) // 4
utils = self.get_utils()
success = utils.add_descriptorset(
self.search_set_name, dim=dim, metric=["CS"])
if success or self.search_set_name in utils.get_descriptorset_list():
self._descriptorset_initialized = True
except Exception as e:
logger.warning(
f"Failed to initialize descriptorset: {e}", exc_info=True)

# If the image already has an image_sha256, we use it.
if (
getattr(self, "_descriptorset_initialized", False)
and isinstance(cmd_dict["AddImage"], dict)
and "_ref" in cmd_dict["AddImage"]
):
try:
if serialized is None:
serialized = generate_embedding(blob)
image_sha256 = cmd_dict["AddImage"].get("properties", {}).get(
"adb_image_sha256", None)
if not image_sha256:
image_sha256 = hashlib.sha256(blob).hexdigest()
new_blobs.append(serialized)
desc_cmd = {
"AddDescriptor": {
"set": self.search_set_name,
"properties": {
"image_sha256": image_sha256,
},
"if_not_found": {
"image_sha256": ["==", image_sha256],
},
"connect": {
"ref": cmd_dict["AddImage"]["_ref"]
}
}
}
}
})
new_descriptors.append(desc_cmd)
except Exception as e:
logger.warning(
f"Failed to generate embedding or descriptor: {e}", exc_info=True)
if cmd_name in ["AddImage", "AddDescriptor", "AddVideo", "AddBlob"]:
blob_index += 1

x[0].extend(new_descriptors)
x[1].extend(new_blobs)
return x
40 changes: 27 additions & 13 deletions aperturedb/transformers/common_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,34 @@ def __init__(self, data: Subscriptable, **kwargs) -> None:
self.adb_timestamp = kwargs.get("adb_timestamp", None)
self.adb_main_object = kwargs.get("adb_main_object", None)

def _apply_common_properties(self, properties: dict):
if self.adb_data_source is not None:
properties["adb_data_source"] = self.adb_data_source
if self.adb_timestamp is not None:
properties["adb_timestamp"] = self.adb_timestamp
if self.adb_main_object is not None:
properties["adb_main_object"] = self.adb_main_object

def getitem(self, subscript):
if (
self.adb_data_source is None and
self.adb_timestamp is None and
self.adb_main_object is None
):
return self.data[subscript]

x = self.data[subscript]
try:
# x is a transaction that has an add_image command and a blob
for ic in self._add_image_index:
src_properties = x[0][ic]["AddImage"]["properties"]
# Set the static properties, if explicitly set
if self.adb_data_source:
src_properties["adb_data_source"] = self.adb_data_source
if self.adb_timestamp:
src_properties["adb_timestamp"] = self.adb_timestamp
if self.adb_main_object:
src_properties["adb_main_object"] = self.adb_main_object
except Exception as e:
logger.exception(e.with_traceback(), stack_info=True)
for cmd_dict in x[0]:
try:
if not isinstance(cmd_dict, dict):
continue
for cmd_name in ["AddImage", "AddVideo", "AddBoundingBox", "AddPolygon"]:
if cmd_name in cmd_dict:
src_properties = cmd_dict[cmd_name].setdefault(
"properties", {})
self._apply_common_properties(src_properties)
except Exception:
logger.exception(
"Error applying common properties", stack_info=True)

return x
4 changes: 2 additions & 2 deletions aperturedb/transformers/facenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
try:
from facenet_pytorch import MTCNN, InceptionResnetV1
import torch
except ImportError:
except ImportError as e:
logger.critical(error_message)
exit(1)
raise ImportError(error_message) from e

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

Expand Down
Loading
Loading