feat: Add CSV ingestion support for Frames - #745
Conversation
Closes #70 This adds which subclasses and registers it with the CLI so can use .
There was a problem hiding this comment.
Pull request overview
This PR extends the Python SDK’s CSV ingestion pipeline to support Frame objects by introducing a FrameDataCSV adapter over the existing image ingestion logic, and wiring the new ingest type into the CLI.
Changes:
- Allow
ImageDataCSVsubclasses to override the command used in CSVParser-generated queries (AddImagevsAddFrame). - Add
FrameDataCSVfor frame ingestion via CSV. - Register
IngestType.FRAMEin theadb ingest from-csvCLI mapping.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| aperturedb/ImageDataCSV.py | Makes the ingestion command overrideable (enables reuse for Frames). |
| aperturedb/FrameDataCSV.py | Adds a new CSV ingester for Frames (needs _Frame index override). |
| aperturedb/cli/ingest.py | Registers FRAME ingest type in CLI (currently missing BBoxDataCSV import). |
Suppressed comments (1)
aperturedb/cli/ingest.py:205
BBoxDataCSVis referenced iningest_typesbut is no longer imported in this function, which will raiseNameErrorwhenfrom_csvruns (even for non-bounding-box ingest types, since the dict is constructed unconditionally). Re-add the missing import.
from aperturedb.ImageDataCSV import ImageDataCSV
from aperturedb.FrameDataCSV import FrameDataCSV
from aperturedb.EntityDataCSV import EntityDataCSV
from aperturedb.BlobDataCSV import BlobDataCSV
from aperturedb.ConnectionDataCSV import ConnectionDataCSV
from aperturedb.PolygonDataCSV import PolygonDataCSV
from aperturedb.VideoDataCSV import VideoDataCSV
from aperturedb.DescriptorDataCSV import DescriptorDataCSV
from aperturedb.DescriptorSetDataCSV import DescriptorSetDataCSV
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
luisremis
left a comment
There was a problem hiding this comment.
add testing to the new FrameDataCSV.
add Frame to the OM, analogous to how Image and Video work.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
aperturedb/cli/ingest.py:199
from_csv()builds theingest_typesdict usingBBoxDataCSV(forIngestType.BOUNDING_BOX) but the import was removed. This will raise aNameErrorwhen invokingadb ingest from-csvwith--ingest-type BOUNDING_BOX. Re-add the missing import alongside the other CSV parsers.
from aperturedb.ImageDataCSV import ImageDataCSV
from aperturedb.FrameDataCSV import FrameDataCSV
from aperturedb.EntityDataCSV import EntityDataCSV
from aperturedb.BlobDataCSV import BlobDataCSV
aperturedb/FrameDataCSV.py:14
- New
FrameDataCSVbehavior (overriddencommand+_Frameindices) is not covered by tests. There are existing tests exercising other CSV ingesters (e.g.,test/test_SPARQL.pyusesImageDataCSVandEntityDataCSV), so adding at least a small unit test forFrameDataCSVwould help prevent regressions (e.g., verifyingcommand == "AddFrame"andget_indices()targets_Frame).
class FrameDataCSV(ImageDataCSV):
def __init__(self, *args, **kwargs):
self.command = "AddFrame"
super().__init__(*args, **kwargs)
def get_indices(self):
return {
"entity": {
"_Frame": self.get_indexed_properties()
}
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
aperturedb/Images.py:1004
Framesused to be defined inaperturedb/Images.py; removing it from this module is a breaking change for any code that doesfrom aperturedb.Images import Frames. If the intent is just to move the implementation, consider adding a backwards-compatible shim (e.g., module__getattr__) that resolvesFrameslazily fromaperturedb.Frames.
return return_dictionary
test/test_FrameDataCSV.py:6
pytestandpandasare imported but never used in this test, which adds unnecessary dependencies and can trigger unused-import lint failures.
import pytest
import pandas as pd
import tempfile
import os
from aperturedb.FrameDataCSV import FrameDataCSV
aperturedb/FrameDataCSV.py:8
FrameDataCSVonly needs to override the command string; defining a variadic__init__drops the base-class signature and makes introspection/type checking harder. WithImageDataCSVnow usinggetattr(self, "command", ...), you can setcommandas a class attribute and remove the custom initializer.
class FrameDataCSV(ImageDataCSV):
def __init__(self, *args, **kwargs):
self.command = "AddFrame"
super().__init__(*args, **kwargs)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (3)
aperturedb/Images.py:1004
Framesused to be defined in this module; removing it breaks existing user code that importsFramesviafrom aperturedb.Images import Frames. Consider re-exporting the new implementation from this module to preserve backward compatibility while keeping the real class inaperturedb/Frames.py.
return return_dictionary
aperturedb/FrameDataCSV.py:8
FrameDataCSVis a new public CSV-ingestion helper but it lacks the class-level docstring that other*DataCSVclasses provide (e.g.BBoxDataCSV). Adding a short docstring helps generated docs and keeps the module consistent.
class FrameDataCSV(ImageDataCSV):
def __init__(self, *args, **kwargs):
self.command = "AddFrame"
super().__init__(*args, **kwargs)
test/test_FrameDataCSV.py:5
pytestandpandasare imported but never used in this test module; removing unused imports keeps the test lightweight and avoids unnecessary dependency coupling.
import pytest
import pandas as pd
import tempfile
import os
from aperturedb.FrameDataCSV import FrameDataCSV
…s, backward-compat shim)
|
Addressed the latest Copilot reviewer comments in commit cce1bc7 (added backward-compatibility shim for |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (3)
test/test_FrameDataCSV.py:7
- This line is long enough that autopep8 will likely reformat it; in this repo CI enforces autopep8 formatting, so it’s better to wrap it now to avoid formatting-only CI failures.
with tempfile.NamedTemporaryFile(suffix=".csv", mode="w", delete=False) as f:
aperturedb/Frames.py:21
- This
super().__init__call exceeds typical autopep8 line wrapping; wrapping it avoids formatting-only CI failures and keeps it consistent with the rest of the codebase’s style.
def __init__(self, client, batch_size=100, response=None, **kwargs):
super().__init__(client, batch_size=batch_size, response=response, **kwargs)
aperturedb/DataModels.py:76
- Changing
FrameDataModelfromIdentityDataModeltoBlobDataModelmakesurla required field (viaBlobDataModel). If any callers constructFrameDataModelwithout a URL today, this is a breaking API change. If this is intended, it should be called out in the PR description/changelog; if not intended, consider keepingIdentityDataModelor makingurloptional onFrameDataModel.
class FrameDataModel(BlobDataModel):
"""Frame data model for ApertureDB.
"""
type = ObjectType.FRAME
5faa0c7 to
0b77b04
Compare
- Add `PROPERTY_ADD_COMMANDS` to `aperturedb/Constants.py` and use it in `aperturedb/transformers/common_properties.py` instead of a hardcoded list. - Use `BLOB_ADD_COMMANDS` in `aperturedb/MLCroissant.py` instead of a hardcoded list. - Rename `side_effect` to `execute_query_side_effect` in `test/test_torch_connector.py` and `test/test_tf_connector.py` to improve debugging readability.
|
Addressed the latest suppressed Copilot review comments in commit ec043dc. Replaced hardcoded command lists with shared constants ( |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (6)
test/test_torch_connector.py:200
- The test uses an all-zero image, which cannot validate that
FindFramedecoding + BGR→RGB conversion is actually happening (the output will look identical even if channel order is wrong). Use a non-symmetric pixel pattern (e.g., set one pixel to a known BGR value) and assert the decoded numpy array matches the expected RGB value to meaningfully cover the new decode path.
img = np.zeros((10, 10, 3), dtype=np.uint8)
is_success, buffer = cv2.imencode(".jpg", img)
assert is_success, "Failed to encode image"
b = [buffer.tobytes()]
test/test_torch_connector.py:210
- The test uses an all-zero image, which cannot validate that
FindFramedecoding + BGR→RGB conversion is actually happening (the output will look identical even if channel order is wrong). Use a non-symmetric pixel pattern (e.g., set one pixel to a known BGR value) and assert the decoded numpy array matches the expected RGB value to meaningfully cover the new decode path.
assert isinstance(blob, np.ndarray)
assert blob.shape == (10, 10, 3)
test/test_tf_connector.py:228
- Similar to the PyTorch test, using an all-zero image doesn’t verify that the OpenCV decode + color conversion branch is correct for
FindFrame(BGR vs RGB won’t be detectable). Consider encoding an image with a distinctive channel value and asserting a specific pixel’s RGB values in the produced tensor/array.
img = np.zeros((10, 10, 3), dtype=np.uint8)
is_success, b_img = cv2.imencode('.jpg', img)
assert is_success, "Failed to encode image"
b = [b_img.tobytes()]
test/test_tf_connector.py:242
- Similar to the PyTorch test, using an all-zero image doesn’t verify that the OpenCV decode + color conversion branch is correct for
FindFrame(BGR vs RGB won’t be detectable). Consider encoding an image with a distinctive channel value and asserting a specific pixel’s RGB values in the produced tensor/array.
assert data.shape == (10, 10, 3)
aperturedb/MLCroissant.py:262
- Importing
BLOB_ADD_COMMANDSinsidegetitemadds per-call overhead on a hot path (even though the module import is cached). Prefer a module-level import for consistency with the other transformers, unless there is a concrete circular-import constraint that requires the local import.
from aperturedb.Constants import BLOB_ADD_COMMANDS
aperturedb/MLCroissant.py:277
- Importing
BLOB_ADD_COMMANDSinsidegetitemadds per-call overhead on a hot path (even though the module import is cached). Prefer a module-level import for consistency with the other transformers, unless there is a concrete circular-import constraint that requires the local import.
if cmd in BLOB_ADD_COMMANDS:
- Fix test_find_frame_mocked in torch and tf connectors to verify RGB color conversion. - Move BLOB_ADD_COMMANDS import to module level in MLCroissant.py to avoid per-call overhead.
|
Addressed the latest suppressed Copilot review comments in commit e78cb1d.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aperturedb/MLCroissant.py:1
- Switching from an explicit list (
[\"AddImage\", \"AddBlob\", \"AddVideo\"]) toBLOB_ADD_COMMANDSbroadens the skip behavior (notably it now also skipsAddDescriptorandAddFrame). IfMLCroissant.getitem()is intended to skip only a subset of blob add commands (as the previous code implied), introduce a more specific constant for this context (e.g., a dedicated set for 'commands excluded from index creation') or keep a local explicit set that preserves the prior semantics and addAddFrameonly if desired.
import dataclasses
|
Addressed the final suppressed Copilot review comment in commit ed0c692:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aperturedb/MLCroissant.py:24
- This introduces yet another hardcoded command-name set that substantially overlaps with the new shared constants in
aperturedb.Constants(e.g.,BLOB_ADD_COMMANDS). To prevent future drift, consider deriving this from a shared constant (or moving a Croissant-specific constant intoConstants.py). IfAddDescriptoris intentionally excluded here, adding a brief comment explaining why would also help keep the intent clear.
# Commands for which we do not want to automatically create entity indices
_CROISSANT_SKIP_INDEX_COMMANDS = frozenset([
"AddImage",
"AddBlob",
"AddVideo",
"AddFrame"
])
|
Addressed the latest suppressed Copilot review comment in commit c5bdd66:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (3)
aperturedb/Images.py:1019
- The lazy
__getattr__shim preservesfrom aperturedb.Images import Frames, but it no longer preservesfrom aperturedb.Images import *behavior becauseFramesis not present inmodule.__dict__at import time. To keep backward compatibility, define__all__to includeFrames(so star-import will callgetattrfor it), or eagerly populateFramesinglobals()at import time (trading away lazy import).
# Shim for backward compatibility
def __getattr__(name: str):
if name == "Frames":
from aperturedb.Frames import Frames
globals()[name] = Frames
return Frames
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __dir__():
return sorted(set(list(globals().keys()) + ["Frames"]))
test/test_tf_connector.py:243
- In TensorFlow,
data.shapeis aTensorShape, and comparing it directly to a tuple can be brittle across TF versions/graph/eager contexts. Prefer assertingtuple(data.shape) == (10, 10, 3)ordata.shape.as_list() == [10, 10, 3]to make the test robust.
for data, label in dataset:
assert data.shape == (10, 10, 3)
assert np.array_equal(data[0, 0].numpy(), [
test/test_cli_ingest.py:20
- This test verifies that
FrameDataCSVis constructed, but it doesn't verify that the constructed object is passed into_process_data. Strengthen the assertion by checking_process_datawas called with the mockeddatainstance (and optionally that key parameters likesample_countare forwarded as expected).
from_csv(filepath="dummy.csv",
ingest_type=IngestType.FRAME, sample_count=5)
mock_csv_class.assert_called_once_with(
"dummy.csv", use_dask=False, blobs_relative_to_csv=True)
mock_process_data.assert_called_once()
- Images.py: Replaced the dynamic __getattr__ and __dir__ with an eager import of Frames at the end of the file. This fixes the circular import while completely restoring the behavior of 'from aperturedb.Images import *'. - test_tf_connector.py: Converted data.shape to tuple before asserting to avoid brittleness with TensorShape. - test_cli_ingest.py: Strengthened assertion to verify all kwargs passed to _process_data.
|
Addressed the latest suppressed Copilot review comments in commit 93ec0c5:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
aperturedb/DataModels.py:7
Optionalis imported but unused in this module, which adds noise and can trigger unused-import checks in some environments.
from typing import ClassVar, Optional
Addresses PR review comment to lazy load Frames from Images.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aperturedb/DataModels.py:7
Optionalis imported but not used in this module, which adds noise and can confuse future edits. Remove it from the import list (or start using it if needed).
from typing import ClassVar, Optional
|
Addressed the latest suppressed Copilot review comment in commit ab160d8:
|
Closes #70
This PR adds a
FrameDataCSVclass which simply inherits fromImageDataCSVand overrides the command toAddFrame. It also registersIngestType.FRAMEin the CLI, closing the loop on Frame OM and ingestion support without duplicating the complex loading/validation logic of Images.