Skip to content

Add label and shape capabilities#91

Open
d33bs wants to merge 6 commits into
WayScience:mainfrom
d33bs:shapes-and-labels
Open

Add label and shape capabilities#91
d33bs wants to merge 6 commits into
WayScience:mainfrom
d33bs:shapes-and-labels

Conversation

@d33bs

@d33bs d33bs commented Jul 5, 2026

Copy link
Copy Markdown
Member

Description

This PR adds shapes and labels capabilities alongside the existing OME-based image capabilities of OME-Arrow. These features are typically used for segmentations of objects to label - distinguishing what pixels are a part of an object from the image, or define shapes - the geometry of particular objects.

What kind of change(s) are included?

  • Documentation (changes docs or other related content)
  • Bug fix (fixes an issue).
  • Enhancement (adds functionality).
  • Breaking change (these changes would cause existing functionality to not work as expected).

Checklist

Please ensure that all boxes are checked before indicating that this pull request is ready for review.

  • I have read and followed the CONTRIBUTING.md guidelines.
  • I have searched for existing content to ensure this is not a duplicate.
  • I have performed a self-review of these additions (including spelling, grammar, and related).
  • These changes pass all pre-commit checks.
  • I have added comments to my code to help provide understanding
  • I have added a test which covers the code changes found within this PR
  • I have deleted all non-relevant text in this pull request template.

Summary by CodeRabbit

  • New Features

    • Added OME-Arrow Shapes support for object tables, relationships, geometry encodings, and Parquet read/write workflows (including image-based filtering).
    • Added a benchmark to measure Parquet read/write performance for shape tables.
  • Documentation

    • Added a Shapes guide and a new README section detailing the shape table model, schema, geometry encodings, and relationships.
    • Updated the Python API docs and documentation navigation; added a generated schema diagram.
  • Tests

    • Added comprehensive Shapes test coverage, plus performance canary tests for construction, filtering, and Parquet column projection.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 029af690-a289-4dd1-b5a0-62dee75ff684

📥 Commits

Reviewing files that changed from the base of the PR and between b7da633 and 82fe400.

📒 Files selected for processing (4)
  • docs/src/_static/generate_schema_image.py
  • src/ome_arrow/shapes.py
  • tests/test_shapes.py
  • tests/test_shapes_performance.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/src/_static/generate_schema_image.py
  • tests/test_shapes.py
  • src/ome_arrow/shapes.py

📝 Walkthrough

Walkthrough

This PR adds an Arrow-native ome_arrow.shapes module with shape and relationship tables, validation, Parquet IO, wrapper access, tests, benchmarks, and docs. It also adds a README schema illustration generator and wires the new Shapes docs into the site.

Changes

OME-Arrow Shapes feature

Layer / File(s) Summary
Core geometry and schema metadata
src/ome_arrow/shapes.py
Defines supported geometry encodings, geometry storage typing, shapes metadata, and the canonical shape schema.
Shape table construction and validation
src/ome_arrow/shapes.py
Infers measurement fields, builds shape tables from row dictionaries, and validates geometry, centroid, bbox, and identity constraints.
Parquet IO and wrapper
src/ome_arrow/shapes.py, src/ome_arrow/__init__.py
Adds shape-table Parquet read/write helpers, the OMEArrowShapes wrapper, and package-level exports.
Relationship tables
src/ome_arrow/shapes.py
Defines relationship metadata and schema helpers, table construction, and relationship validation.
Functional tests
tests/test_shapes.py
Covers shape schema and validation errors, wrapper access, relationship tables, and Parquet roundtrips and projections.
Performance tests and benchmark CLI
tests/test_shapes_performance.py, benchmarks/benchmark_shapes_parquet.py
Adds performance canaries for large shape tables and a Parquet benchmark CLI with synthetic point and label-mask fixtures.
Documentation updates
docs/src/shapes.md, README.md, docs/src/index.md, docs/src/python-api.md
Adds the Shapes docs page, README section, docs index entry, API doc updates, and local schema/benchmark documentation.

README schema image generator

Layer / File(s) Summary
Schema image generation
docs/src/_static/generate_schema_image.py
Implements font loading, drawing helpers, icon renderers, and the main() composition that writes the schema PNG.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: gwaybio, MikeLippincott

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the core change: adding label and shape support to OME-Arrow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@d33bs d33bs changed the title Add shapes and labels capabilities Add label and shape capabilities Jul 5, 2026
@d33bs

d33bs commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
docs/src/_static/generate_schema_image.py (1)

14-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent font type annotations.

_font() is typed to return ImageFont.FreeTypeFont, but the fallback path returns ImageFont.load_default(...), whose signature is FreeTypeFont | ImageFont — not guaranteed to be FreeTypeFont. Meanwhile _text_center()'s font parameter is typed as the (unrelated) base ImageFont.ImageFont class. These annotations don't align with each other or with the actual return type, which would surface as type-checker errors if mypy/pyright is run on this file.

♻️ Suggested fix
-def _font(size: int, *, bold: bool = False) -> ImageFont.FreeTypeFont:
+def _font(size: int, *, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:

Also applies to: 29-49

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/_static/generate_schema_image.py` around lines 14 - 26, The font
annotations in _font and _text_center are inconsistent with the actual return
types, so align them to match Pillow’s types. Update _font so its return
annotation covers both the truetype path and the load_default fallback, and make
_text_center accept the same font type that _font returns. Use the unique
symbols _font and _text_center to keep the signatures consistent and avoid
mypy/pyright errors.
tests/test_shapes.py (2)

93-107: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Second branch doesn't test the intended "missing column" path.

The row omits object_id, but since shape_schema always includes object_id as a schema field, pa.Table.from_pylist fills it with null rather than omitting the column. So validate_shape_table never hits the "object_id" not in table.column_names" branch here — it instead fails on the later null-check ("object_id values must not be null"), which happens to also match the "object_id" regex. The test still passes, but it isn't exercising the code path its docstring implies.

♻️ Suggested fix to actually cover the missing-column branch
-    table = make_shape_table(
-        [{"geometry": [0.0, 1.0]}],
-        geometry_encoding="geoarrow.point",
-        validate=False,
-    )
-
-    with pytest.raises(ValueError, match="object_id"):
-        validate_shape_table(table)
+    table = make_shape_table(
+        [{"geometry": [0.0, 1.0]}],
+        geometry_encoding="geoarrow.point",
+        validate=False,
+    ).drop_columns(["object_id"])
+
+    with pytest.raises(ValueError, match="must contain an object_id column"):
+        validate_shape_table(table)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_shapes.py` around lines 93 - 107, The second assertion in
test_validate_shape_table currently exercises the null-value path instead of the
missing-column branch in validate_shape_table. Update the test setup so the
table truly lacks object_id in table.column_names, using make_shape_table or a
direct pyarrow table definition that omits the column, and keep the assertion
targeting the missing-column error path in validate_shape_table.

139-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No negative-path coverage for validate_relationship_table.

Only the happy path is tested; the missing-column, null-value, and unsupported-relationship_type branches shown in validate_relationship_table (src/ome_arrow/shapes.py:467-497) are untested here. Consider adding cases similar to the shape-table validation tests. As per relevant code snippets, validate_relationship_table raises ValueError for missing schema metadata, missing/null parent_id/child_id/relationship_type, and unsupported relationship types — none of these branches currently have test coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_shapes.py` around lines 139 - 186, Add negative-path tests for
validate_relationship_table in the existing relationship-table test module: keep
the current happy-path test, and add focused cases that exercise the missing
schema metadata, missing or null parent_id/child_id/relationship_type fields,
and unsupported relationship_type branches. Use make_relationship_table and
validate_relationship_table to build each scenario, and assert the expected
ValueError is raised for each invalid input, mirroring the style of the
shape-table validation tests.
tests/test_shapes_performance.py (1)

34-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hard-coded wall-clock thresholds risk flaky CI failures.

All three tests assert absolute elapsed-time bounds (< 1.0, < 0.25, < 1.5, < 0.5) with a single untimed run. On shared/loaded CI runners these thresholds can be exceeded without any actual regression, causing intermittent, hard-to-reproduce failures that aren't tied to code changes.

Consider one of:

  • Using a proper benchmarking tool (e.g. pytest-benchmark) that accounts for variance/warm-up.
  • Running multiple iterations and asserting on a median/percentile rather than a single timing.
  • Marking these as an opt-in/slow test group excluded from default CI runs (matching the separate benchmarks/ script already added for this purpose).
  • Using more generous thresholds with headroom for CI variance if these must stay in the default suite.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_shapes_performance.py` around lines 34 - 81, The performance tests
in test_shapes_performance.py use single-run wall-clock cutoffs that can be
flaky on busy CI runners. Update the checks around make_shape_table,
OMEArrowShapes.for_image, and write_shape_parquet/read_shape_parquet to avoid
strict absolute timing assertions; prefer a benchmark-style approach, multiple
iterations with a median/percentile, or move them behind a slow/opt-in test
marker. If they remain in the default suite, relax the thresholds with enough CI
headroom.
src/ome_arrow/shapes.py (1)

16-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive SUPPORTED_GEOMETRY_ENCODINGS from GeometryEncoding to avoid drift.

The frozenset duplicates the Literal values verbatim. If a new encoding is added to one but not the other, the type hint and the runtime allow-list silently diverge.

♻️ Proposed fix
+from typing import get_args
+
 GeometryEncoding = Literal[
     "geoarrow.point",
     "geoarrow.linestring",
     "geoarrow.polygon",
     "geoarrow.multipolygon",
     "ome.mesh3d",
     "ome.labelmask",
     "ome.pointcloud",
     "ome.boundingbox",
 ]
 
-SUPPORTED_GEOMETRY_ENCODINGS: frozenset[str] = frozenset(
-    {
-        "geoarrow.point",
-        "geoarrow.linestring",
-        "geoarrow.polygon",
-        "geoarrow.multipolygon",
-        "ome.mesh3d",
-        "ome.labelmask",
-        "ome.pointcloud",
-        "ome.boundingbox",
-    }
-)
+SUPPORTED_GEOMETRY_ENCODINGS: frozenset[str] = frozenset(get_args(GeometryEncoding))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ome_arrow/shapes.py` around lines 16 - 38, Keep GeometryEncoding and
SUPPORTED_GEOMETRY_ENCODINGS in sync by deriving the runtime allow-list from the
GeometryEncoding literal instead of duplicating the values in both places.
Update the symbols GeometryEncoding and SUPPORTED_GEOMETRY_ENCODINGS in
shapes.py so there is a single source of truth for the supported geometry
strings, preventing the type hint and allow-list from drifting apart when new
encodings are added.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/src/shapes.md`:
- Around line 87-101: The canonical column table in shapes.md incorrectly lists
`class` and `confidence` as reserved schema columns, which conflicts with
`make_shape_table` treating them as non-core measurements. Update the
canonical-columns list near the shape schema description to keep only the true
core fields (`object_id`, `image_id`, `label_image_id`, `label_value`,
`geometry`, `centroid`, `bbox`) and move `class` and `confidence` into the
measurements/examples discussion so the schema contract stays aligned with
`make_shape_table`.

In `@src/ome_arrow/shapes.py`:
- Around line 76-80: The _coordinate_type() helper currently ignores the
dimensions argument, so shape_schema() and validate_shape_table() accept
arbitrary coordinate lengths for geometry, centroid, and bbox. Update
_coordinate_type() to enforce the expected arity using dimensions (or add an
explicit per-row length validation if mixed dimensionality is intended), and
make sure shape_schema() and validate_shape_table() rely on that fixed shape.
Also adjust the related schema expectation in tests/test_shapes.py to match the
new coordinate type.
- Around line 253-303: The custom geometry column is still being treated as a
measurement field, which creates duplicate columns and breaks validation. Update
make_shape_table to pass geometry_column into _infer_measurement_fields, and
change _infer_measurement_fields to exclude that column name along with
DEFAULT_SHAPE_COLUMNS. Use the existing symbols make_shape_table and
_infer_measurement_fields to keep the geometry column reserved and ensure
validate_shape_table can resolve it correctly.

---

Nitpick comments:
In `@docs/src/_static/generate_schema_image.py`:
- Around line 14-26: The font annotations in _font and _text_center are
inconsistent with the actual return types, so align them to match Pillow’s
types. Update _font so its return annotation covers both the truetype path and
the load_default fallback, and make _text_center accept the same font type that
_font returns. Use the unique symbols _font and _text_center to keep the
signatures consistent and avoid mypy/pyright errors.

In `@src/ome_arrow/shapes.py`:
- Around line 16-38: Keep GeometryEncoding and SUPPORTED_GEOMETRY_ENCODINGS in
sync by deriving the runtime allow-list from the GeometryEncoding literal
instead of duplicating the values in both places. Update the symbols
GeometryEncoding and SUPPORTED_GEOMETRY_ENCODINGS in shapes.py so there is a
single source of truth for the supported geometry strings, preventing the type
hint and allow-list from drifting apart when new encodings are added.

In `@tests/test_shapes_performance.py`:
- Around line 34-81: The performance tests in test_shapes_performance.py use
single-run wall-clock cutoffs that can be flaky on busy CI runners. Update the
checks around make_shape_table, OMEArrowShapes.for_image, and
write_shape_parquet/read_shape_parquet to avoid strict absolute timing
assertions; prefer a benchmark-style approach, multiple iterations with a
median/percentile, or move them behind a slow/opt-in test marker. If they remain
in the default suite, relax the thresholds with enough CI headroom.

In `@tests/test_shapes.py`:
- Around line 93-107: The second assertion in test_validate_shape_table
currently exercises the null-value path instead of the missing-column branch in
validate_shape_table. Update the test setup so the table truly lacks object_id
in table.column_names, using make_shape_table or a direct pyarrow table
definition that omits the column, and keep the assertion targeting the
missing-column error path in validate_shape_table.
- Around line 139-186: Add negative-path tests for validate_relationship_table
in the existing relationship-table test module: keep the current happy-path
test, and add focused cases that exercise the missing schema metadata, missing
or null parent_id/child_id/relationship_type fields, and unsupported
relationship_type branches. Use make_relationship_table and
validate_relationship_table to build each scenario, and assert the expected
ValueError is raised for each invalid input, mirroring the style of the
shape-table validation tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0e3c69d-85a8-4da4-9a1d-982ca80e00fc

📥 Commits

Reviewing files that changed from the base of the PR and between d97529e and b7da633.

⛔ Files ignored due to path filters (1)
  • docs/src/_static/various_ome_arrow_schema.png is excluded by !**/*.png
📒 Files selected for processing (10)
  • README.md
  • benchmarks/benchmark_shapes_parquet.py
  • docs/src/_static/generate_schema_image.py
  • docs/src/index.md
  • docs/src/python-api.md
  • docs/src/shapes.md
  • src/ome_arrow/__init__.py
  • src/ome_arrow/shapes.py
  • tests/test_shapes.py
  • tests/test_shapes_performance.py

Comment thread docs/src/shapes.md
Comment on lines +87 to +101
The canonical columns are:

| Column | Purpose |
| ---------------- | --------------------------------------- |
| `object_id` | stable object identifier |
| `image_id` | source image identifier |
| `label_image_id` | source label image identifier |
| `label_value` | integer label value in the label raster |
| `geometry` | one logical geometry value |
| `centroid` | coordinate vector for object center |
| `bbox` | min/max coordinate bounds |
| `class` | object class or annotation category |
| `confidence` | detection or classification confidence |

Any additional columns are measurements.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move class and confidence out of the canonical-column list.

make_shape_table infers non-core keys as measurement fields, so this table currently presents example measurements as reserved schema columns. That contradicts the sentence below and blurs the schema contract.

♻️ Proposed fix
-| `class`          | object class or annotation category     |
-| `confidence`     | detection or classification confidence  |
+Any additional columns, including `class` and `confidence`, are measurements.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The canonical columns are:
| Column | Purpose |
| ---------------- | --------------------------------------- |
| `object_id` | stable object identifier |
| `image_id` | source image identifier |
| `label_image_id` | source label image identifier |
| `label_value` | integer label value in the label raster |
| `geometry` | one logical geometry value |
| `centroid` | coordinate vector for object center |
| `bbox` | min/max coordinate bounds |
| `class` | object class or annotation category |
| `confidence` | detection or classification confidence |
Any additional columns are measurements.
The canonical columns are:
| Column | Purpose |
| ---------------- | --------------------------------------- |
| `object_id` | stable object identifier |
| `image_id` | source image identifier |
| `label_image_id` | source label image identifier |
| `label_value` | integer label value in the label raster |
| `geometry` | one logical geometry value |
| `centroid` | coordinate vector for object center |
| `bbox` | min/max coordinate bounds |
Any additional columns, including `class` and `confidence`, are measurements.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/shapes.md` around lines 87 - 101, The canonical column table in
shapes.md incorrectly lists `class` and `confidence` as reserved schema columns,
which conflicts with `make_shape_table` treating them as non-core measurements.
Update the canonical-columns list near the shape schema description to keep only
the true core fields (`object_id`, `image_id`, `label_image_id`, `label_value`,
`geometry`, `centroid`, `bbox`) and move `class` and `confidence` into the
measurements/examples discussion so the schema contract stays aligned with
`make_shape_table`.

Comment thread src/ome_arrow/shapes.py
Comment thread src/ome_arrow/shapes.py Outdated
@d33bs d33bs marked this pull request as ready for review July 8, 2026 13:07
@d33bs d33bs requested a review from gwaybio July 8, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant