Skip to content

Add composite image config and display options - #222

Merged
d33bs merged 11 commits into
cytomining:mainfrom
d33bs:multi-channel
Aug 5, 2026
Merged

Add composite image config and display options#222
d33bs merged 11 commits into
cytomining:mainfrom
d33bs:multi-channel

Conversation

@d33bs

@d33bs d33bs commented Jul 17, 2026

Copy link
Copy Markdown
Member

Description

This adds the ability to view multiple independent single channel images as a composite through CytoDataFrame.

Closes #221

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 multi-channel single-cell composites with custom colors, legends, naming, and optional all-channel merging.
    • Composite images preserve segmentation outlines and center markers.
    • Added configurable contrast equalization with an optional clip-limit setting.
  • Improvements

    • Improved crop consistency, channel matching, thumbnail sizing, and image display widths.
    • Kept notebook table headers on one line.
  • Documentation

    • Updated the README and added examples for composite channels, color mappings, and overlays.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

CytoDataFrame now renders configurable multi-channel composite crops with color legends, overlays, equalization controls, shared crop geometry, and normalized notebook sizing. Documentation, tests, and the pyproject-fmt hook revision were also updated.

Changes

Composite rendering

Layer / File(s) Summary
Composite options and image composition
src/cytodataframe/frame.py, src/cytodataframe/image.py
Adds channel and color resolution, tinted blending, resizing, overlays, equalization overrides, cache updates, and composite HTML generation.
Notebook composite integration
src/cytodataframe/frame.py
Creates composite columns from shared row crop geometry and adds channel legends, minimum-width styling, and non-wrapping headers.
Composite examples and validation
tests/test_frame.py, README.md, docs/src/examples/cytodataframe_at_a_glance.py
Tests rendering, channel forms, colors, overlays, legends, equalization, caching, and sizing. Documentation adds composite feature details and examples.

Pre-commit hook maintenance

Layer / File(s) Summary
Pre-commit revision update
.pre-commit-config.yaml
Updates the pinned pyproject-fmt revision from v2.26.0 to v2.27.0.

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

Sequence Diagram(s)

sequenceDiagram
  participant CytoDataFrame
  participant NotebookRowRenderer
  participant process_channel_composite_as_html_display
  participant NotebookHTML
  CytoDataFrame->>NotebookRowRenderer: Share crop geometry
  NotebookRowRenderer->>process_channel_composite_as_html_display: Resolve channels, colors, and equalization
  process_channel_composite_as_html_display->>process_channel_composite_as_html_display: Fit, tint, blend, and restore overlays
  process_channel_composite_as_html_display-->>NotebookRowRenderer: Return composite HTML image
  NotebookRowRenderer->>NotebookHTML: Render composite column and legend
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 84.44% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary composite image configuration and display changes.
✨ 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 commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 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: 1

🧹 Nitpick comments (1)
src/cytodataframe/frame.py (1)

3504-3510: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: log the swallowed exception before returning "".

A failed composite render is silently converted to an empty cell with no diagnostic trail, and Ruff flags the blind except Exception (BLE001). A debug log keeps the current behavior while making silent-empty composites diagnosable.

♻️ Suggested change
         try:
             return self._image_array_to_html(composite_array)
-        except Exception:
+        except Exception as exc:
+            logger.debug("Failed to render composite image as HTML: %s", exc)
             return ""
🤖 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/cytodataframe/frame.py` around lines 3504 - 3510, Update the exception
handler around _image_array_to_html to log the caught exception at debug level
before returning an empty string. Preserve the existing fallback behavior while
replacing the blind exception handling with an explicitly bound exception so
Ruff BLE001 is satisfied.

Source: Linters/SAST tools

🤖 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 `@src/cytodataframe/frame.py`:
- Around line 3095-3101: Normalize numeric width values to CSS pixel strings
before applying the image minimum-width logic in the rendering method containing
_resolve_image_min_width. Ensure configured integer widths such as 300 become
“300px” for both the emitted width style and _resolve_image_min_width, while
preserving existing handling for non-numeric width values.

---

Nitpick comments:
In `@src/cytodataframe/frame.py`:
- Around line 3504-3510: Update the exception handler around
_image_array_to_html to log the caught exception at debug level before returning
an empty string. Preserve the existing fallback behavior while replacing the
blind exception handling with an explicitly bound exception so Ruff BLE001 is
satisfied.
🪄 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: af938fa7-6aea-417b-9f8c-a2d409fcd40f

📥 Commits

Reviewing files that changed from the base of the PR and between 16b7923 and 1a8f2c8.

📒 Files selected for processing (6)
  • .pre-commit-config.yaml
  • README.md
  • docs/src/examples/cytodataframe_at_a_glance.ipynb
  • docs/src/examples/cytodataframe_at_a_glance.py
  • src/cytodataframe/frame.py
  • tests/test_frame.py

Comment thread src/cytodataframe/frame.py
@d33bs
d33bs marked this pull request as ready for review July 20, 2026 17:41
@d33bs
d33bs requested a review from jenna-tomkinson as a code owner July 20, 2026 17:41
@jenna-tomkinson

Copy link
Copy Markdown
Member

@d33bs Lucky number 222!

@jenna-tomkinson jenna-tomkinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for adding this so quickly! Everything looks good but my main concern is the "all" functionality, as you don't need to call colors. Wouldn't a user need to know what channels are what colors in the composite to interpret the crop if they use this function? I would recommend just a simple print for what colors match which channels, as someone can specify colors with the other functionality.

Comment thread docs/src/examples/cytodataframe_at_a_glance.py Outdated
Comment thread docs/src/examples/cytodataframe_at_a_glance.py Outdated
Comment thread docs/src/examples/cytodataframe_at_a_glance.py
Comment thread docs/src/examples/cytodataframe_at_a_glance.py Outdated
Comment thread tests/test_frame.py
@d33bs

d33bs commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Thanks @jenna-tomkinson for the review! Before I make further changes could you help me address the questions I had based on your review to make sure this aligns with what you expect?

@d33bs
d33bs requested a review from jenna-tomkinson July 24, 2026 02:50

@jenna-tomkinson jenna-tomkinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is looking REALLY good but I think we need to make a change to the histogram equalization before we can merge this PR. In the current state, it would be difficult for a user to generate optimal composite images. As well, we should consider either updating or removing the "all" functionality.

Comment thread docs/src/examples/cytodataframe_at_a_glance.py
Comment thread docs/src/examples/cytodataframe_at_a_glance.py Outdated
Co-Authored-By: Jenna Tomkinson <107513215+jenna-tomkinson@users.noreply.github.com>

@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: 1

🧹 Nitpick comments (1)
docs/src/examples/cytodataframe_at_a_glance.ipynb (1)

1-2000: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider stripping volatile notebook outputs before committing.

This diff is entirely notebook outputs: base64-encoded PNG images, widget model_id values, and "CPU times"/"Wall time" stdout text. None of these come from cell source changes. Widget IDs and timing text are non-deterministic and will produce diff noise on every re-execution, even without logic changes. Large embedded base64 images also bloat the repository and make the notebook diff hard to review.

Strip notebook outputs before committing, for example with nbstripout or jupyter nbconvert --clear-output. This PR already updates .pre-commit-config.yaml; add an output-stripping hook there to enforce this automatically.

🤖 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/examples/cytodataframe_at_a_glance.ipynb` around lines 1 - 2000,
Strip all execution outputs from the notebook, including embedded base64 images,
widget model IDs, and timing text, while preserving every cell’s source and
metadata needed for the example. Update the existing pre-commit configuration to
add an nbstripout or equivalent clear-output hook so future notebook commits
enforce this automatically.
🤖 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 `@src/cytodataframe/frame.py`:
- Around line 2706-2710: Update the image-cache key in the frame rendering flow
around adjust_with_adaptive_histogram_equalization to include a normalized
equalize_clip_limit from the retained display_options mapping, alongside the
existing path and brightness components. Ensure changing this option on the same
frame produces a cache miss and recomputes pixels, and add a regression test
covering renders before and after modifying equalize_clip_limit.

---

Nitpick comments:
In `@docs/src/examples/cytodataframe_at_a_glance.ipynb`:
- Around line 1-2000: Strip all execution outputs from the notebook, including
embedded base64 images, widget model IDs, and timing text, while preserving
every cell’s source and metadata needed for the example. Update the existing
pre-commit configuration to add an nbstripout or equivalent clear-output hook so
future notebook commits enforce this automatically.
🪄 Autofix

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 Plus

Run ID: 6756db59-5212-4cae-8154-edd2a891589d

📥 Commits

Reviewing files that changed from the base of the PR and between 491e2e7 and 245ff1f.

📒 Files selected for processing (6)
  • README.md
  • docs/src/examples/cytodataframe_at_a_glance.ipynb
  • docs/src/examples/cytodataframe_at_a_glance.py
  • src/cytodataframe/frame.py
  • src/cytodataframe/image.py
  • tests/test_frame.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Comment thread src/cytodataframe/frame.py Outdated
d33bs and others added 2 commits August 4, 2026 15:37
Resolve .pre-commit-config.yaml conflict by taking upstream hook versions
(pyproject-fmt v2.26.0, ruff v0.16.1).

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/cytodataframe/frame.py (2)

3126-3138: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Normalize height and handle an explicit None width.

When display_options["width"] is None, Line 3128 passes None through and Line 3131 emits width:None. Numeric heights also emit values such as height:300, which is invalid CSS. Apply the same CSS normalization to height, and omit or map None to the documented automatic behavior.

🤖 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/cytodataframe/frame.py` around lines 3126 - 3138, Update the display
sizing logic around _normalize_css_width so height is normalized to a valid CSS
width-style value before adding it to html_style, and handle an explicit None
width by omitting the width declaration or using the documented automatic-width
behavior instead of emitting width:None. Preserve the existing min-width
resolution for valid widths.

5261-5272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject composite column name collisions.

When composite_column_name already exists, Lines 5271-5272 leave the column in place and Line 5284 overwrites its displayed rows. If the name is an image column, Lines 5288-5292 then read composite HTML instead of the original filename. If it is bounding-box or center metadata, crop geometry is corrupted. Reject existing names or generate a unique name before assignment.

🤖 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/cytodataframe/frame.py` around lines 5261 - 5272, Update the
composite-column setup around composite_column_name so an existing data column
name is never reused; reject the requested name or generate a unique alternative
before assigning composite output. Ensure subsequent rendered-row updates and
composite HTML reads use only this collision-free name, preserving image
filenames and bounding-box/center metadata.
🤖 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.

Outside diff comments:
In `@src/cytodataframe/frame.py`:
- Around line 3126-3138: Update the display sizing logic around
_normalize_css_width so height is normalized to a valid CSS width-style value
before adding it to html_style, and handle an explicit None width by omitting
the width declaration or using the documented automatic-width behavior instead
of emitting width:None. Preserve the existing min-width resolution for valid
widths.
- Around line 5261-5272: Update the composite-column setup around
composite_column_name so an existing data column name is never reused; reject
the requested name or generate a unique alternative before assigning composite
output. Ensure subsequent rendered-row updates and composite HTML reads use only
this collision-free name, preserving image filenames and bounding-box/center
metadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 007ee57d-6fff-48a5-b01f-3ba0d91e8131

📥 Commits

Reviewing files that changed from the base of the PR and between 245ff1f and 97acbd2.

📒 Files selected for processing (3)
  • .pre-commit-config.yaml
  • src/cytodataframe/frame.py
  • tests/test_frame.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_frame.py

@d33bs
d33bs requested a review from jenna-tomkinson August 5, 2026 14:05
@d33bs

d33bs commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Thanks @jenna-tomkinson - this is ready for a re-review when ready, I think I've addressed all your comments but please let me know if anything's missing.

@jenna-tomkinson jenna-tomkinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM! I left some more major comments regarding the example notebook and the chosen colors and channels, as these examples are currently non-standard and might not best reflect how the crops will look to someone in reality.

Comment thread docs/src/examples/cytodataframe_at_a_glance.py Outdated
Comment thread docs/src/examples/cytodataframe_at_a_glance.py Outdated
Comment thread docs/src/examples/cytodataframe_at_a_glance.py
Comment thread README.md Outdated
Co-Authored-By: Jenna Tomkinson <107513215+jenna-tomkinson@users.noreply.github.com>
@d33bs

d33bs commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Thank you @jenna-tomkinson !

@d33bs
d33bs merged commit 894f6d2 into cytomining:main Aug 5, 2026
9 checks passed
@d33bs
d33bs deleted the multi-channel branch August 5, 2026 20:18
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.

Function to merge all or some channels into one single-cell crop composite

2 participants