Skip to content

feat: update displayCTSegOverlay with axis inputs - #140

Merged
katy-scott merged 11 commits into
mainfrom
katy/update-displayCTSegOverlay
Apr 15, 2026
Merged

katy-scott merged 11 commits into
mainfrom
katy/update-displayCTSegOverlay

Conversation

@katy-scott

@katy-scott katy-scott commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Also clean formatting of image_processing with ruff.

Summary by CodeRabbit

  • Bug Fixes

    • Error messages enhanced with contextual details for easier troubleshooting.
  • Documentation

    • Docstrings refined for improved clarity and consistency.
  • Refactor

    • Public image-processing APIs now have stricter type annotations and updated signatures.
    • One image-padding utility was removed from the public surface.
  • Chores

    • Project config section renamed for workspace-level configuration.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@katy-scott has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 16 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 28 minutes and 16 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d8886121-7f63-4ef4-b129-036ba7360867

📥 Commits

Reviewing files that changed from the base of the PR and between 02417e1 and 984fb4c.

⛔ Files ignored due to path filters (1)
  • pixi.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .github/workflows/ci-cd.yml
  • pyproject.toml
  • src/readii/analyze/__init__.py
  • src/readii/analyze/correlation.py
  • src/readii/analyze/plot_correlation.py
  • src/readii/feature_extraction.py
  • src/readii/io/__init__.py
  • src/readii/io/loaders/__init__.py
  • src/readii/io/loaders/features.py
  • src/readii/io/loaders/general.py
  • src/readii/io/loaders/images.py
  • src/readii/io/utils/__init__.py
  • src/readii/io/utils/pattern_resolver.py
  • src/readii/io/writers/correlation_writer.py
  • src/readii/io/writers/nifti_writer.py
  • src/readii/io/writers/plot_writer.py
  • src/readii/loaders.py
  • src/readii/process/__init__.py
  • src/readii/process/config/__init__.py
  • src/readii/process/config/dataset_config.py
  • src/readii/process/images/__init__.py
  • src/readii/process/images/crop.py
  • src/readii/process/label.py
  • src/readii/process/split.py
  • src/readii/process/subset.py
  • tests/process/images/test_crop.py
  • tests/test_negative_controls.py

Walkthrough

The PR renames the Pixi config section from [tool.pixi.project] to [tool.pixi.workspace] in pyproject.toml, and updates src/readii/image_processing.py with new imports, added type annotations, improved error messages/docstrings, and removal/commenting-out of padSegToMatchCT and related loader usage.

Changes

Cohort / File(s) Summary
Configuration
pyproject.toml
Renamed Pixi configuration section from [tool.pixi.project] to [tool.pixi.workspace]; channels and platforms preserved.
Image processing module
src/readii/image_processing.py
Added from typing import Optional and import matplotlib.colors as mcolors; strengthened type annotations on public APIs (displayImageSlice, displayCTSegOverlay, getROICenterCoords, getROIVoxelLabel, getCroppedImages); improved error messages to include contextual values; refined docstrings; commented-out/removed padSegToMatchCT(...) and disabled related loader imports (loadDicomSITK, loadSegmentation).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through configs, tidy and bright,

Types snug in functions, errors now right.
One function I hid to take a nap,
While colors and hints set out the map.
A little rabbit cheers this patch with a clap!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title focuses on 'axis inputs' but the actual changes include broad type annotations, validation improvements, function deprecation, and configuration updates with only partial alignment to axis-related changes. Consider a more comprehensive title such as 'refactor: add type annotations and improve validation in image_processing' or clarify what 'axis inputs' specifically refers to.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch katy/update-displayCTSegOverlay

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (3)
src/readii/image_processing.py (3)

215-265: ⚠️ Potential issue | 🟠 Major

np.ndarray inputs break the documented default paths.

The signature now says ctImage/segImage can be np.ndarray, but Line 261 calls getCroppedImages(...) and Line 265 calls getROICenterCoords(segImage) before any normalization. Both helpers only handle sitk.Image, so displayCTSegOverlay(array_ct, array_seg) will fail for the default sliceIdx=-1 case and for crop=True. Please either normalize arrays up front or narrow the public type/doc contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` around lines 215 - 265, displayCTSegOverlay
accepts np.ndarray but calls getCroppedImages and getROICenterCoords which only
handle sitk.Image, causing failures when sliceIdx==-1 or crop=True; fix by
normalizing inputs at the top of displayCTSegOverlay: detect np.ndarray for
ctImage/segImage and convert them to sitk.Image (e.g., via
sitk.GetImageFromArray) before any calls to getCroppedImages or
getROICenterCoords so downstream helpers always receive sitk.Image, and ensure
the converted images preserve expected axis order and spacing/origin metadata if
required.

274-315: ⚠️ Potential issue | 🔴 Critical

Resolve the duplicated overlay block before merge.

This section currently has duplicated dispMin/dispMax initialization, two if ax is None blocks, duplicated imshow calls, and unmatched parentheses around the CT overlay. In its current state, the module will not import.

Suggested cleanup
-    if dispMin == None:
-        dispMin = ctImage.min()
-    if dispMax == None:
-        dispMax = ctImage.max()
-
-    if dispMin == None:
-        dispMin = ctImage.min()
-    if dispMax == None:
-        dispMax = ctImage.max()
+    if dispMin is None:
+        dispMin = ctImage.min()
+    if dispMax is None:
+        dispMax = ctImage.max()

     # Make mask of ROI to ignore background in overlaid plot
     maskSeg = np.ma.masked_where(segImage == 0, segImage)

-    if ax is None:
-        # Create a new axis
-        fig, ax = plt.subplots()
-
     if ax is None:
-        # Create a new axis
-        fig, ax = plt.subplots()
+        _, ax = plt.subplots()

     # Plot slice of CT
-    ax.imshow(
-        ctImage[sliceIdx, :, :], cmap=cmapCT, vmin=dispMin, vmax=dispMax
-    ax.imshow(
-        ctImage[sliceIdx, :, :], cmap=cmapCT, vmin=dispMin, vmax=dispMax
-    )
+    ax.imshow(ctImage[sliceIdx, :, :], cmap=cmapCT, vmin=dispMin, vmax=dispMax)
     # Plot mask of ROI overtop
-    ax.imshow(
-    ax.imshow(
+    ax.imshow(
         maskSeg[sliceIdx, :, :],
         cmap=cmapSeg,
         vmin=segImage.min(),
@@
     )
     ax.axis("off")

     return ax
-    ax.axis("off")
-
-    return ax
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` around lines 274 - 315, The code contains
duplicated initialization and plotting blocks causing syntax errors and
incorrect flow: remove the repeated dispMin/dispMax checks (keep one each), keep
a single "if ax is None: fig, ax = plt.subplots()" block, fix the CT imshow call
to have matching parentheses and only one call (ax.imshow(ctImage[sliceIdx,:,:],
cmap=cmapCT, vmin=dispMin, vmax=dispMax)), then render the mask once with
ax.imshow(maskSeg[sliceIdx,:,:], cmap=cmapSeg, vmin=segImage.min(),
vmax=segImage.max(), alpha=alpha), ensure ax.axis("off") appears before the
single return ax, and delete any duplicated lines referencing maskSeg, segImage,
or return.

104-116: ⚠️ Potential issue | 🟠 Major

alignedSegImage is not actually a standalone alternative here.

The new validation allows padSegToMatchCT(..., alignedSegImage=...) without segImagePath, but Line 116 still unconditionally calls pydicom.dcmread(segImagePath, ...). That makes the advertised optional path fail at runtime. Either keep segImagePath required, or accept the SEG metadata needed for slice mapping as a separate argument.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` around lines 104 - 116, The code
unconditionally calls pydicom.dcmread(segImagePath, ...) even when segImagePath
can be None and an alignedSegImage is provided to padSegToMatchCT; update
padSegToMatchCT signature/usage to accept a segmentation header object (e.g.,
segHeader) or ensure segWithHeader is obtained from alignedSegImage metadata,
and change this block to: if segImagePath is provided use
pydicom.dcmread(segImagePath, stop_before_pixels=True) to set segWithHeader,
else require/consume a passed-in segHeader (or extract header from
alignedSegImage) before proceeding; adjust callers to pass the new segHeader
argument when they supply alignedSegImage instead of a path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/readii/image_processing.py`:
- Around line 215-265: displayCTSegOverlay accepts np.ndarray but calls
getCroppedImages and getROICenterCoords which only handle sitk.Image, causing
failures when sliceIdx==-1 or crop=True; fix by normalizing inputs at the top of
displayCTSegOverlay: detect np.ndarray for ctImage/segImage and convert them to
sitk.Image (e.g., via sitk.GetImageFromArray) before any calls to
getCroppedImages or getROICenterCoords so downstream helpers always receive
sitk.Image, and ensure the converted images preserve expected axis order and
spacing/origin metadata if required.
- Around line 274-315: The code contains duplicated initialization and plotting
blocks causing syntax errors and incorrect flow: remove the repeated
dispMin/dispMax checks (keep one each), keep a single "if ax is None: fig, ax =
plt.subplots()" block, fix the CT imshow call to have matching parentheses and
only one call (ax.imshow(ctImage[sliceIdx,:,:], cmap=cmapCT, vmin=dispMin,
vmax=dispMax)), then render the mask once with ax.imshow(maskSeg[sliceIdx,:,:],
cmap=cmapSeg, vmin=segImage.min(), vmax=segImage.max(), alpha=alpha), ensure
ax.axis("off") appears before the single return ax, and delete any duplicated
lines referencing maskSeg, segImage, or return.
- Around line 104-116: The code unconditionally calls
pydicom.dcmread(segImagePath, ...) even when segImagePath can be None and an
alignedSegImage is provided to padSegToMatchCT; update padSegToMatchCT
signature/usage to accept a segmentation header object (e.g., segHeader) or
ensure segWithHeader is obtained from alignedSegImage metadata, and change this
block to: if segImagePath is provided use pydicom.dcmread(segImagePath,
stop_before_pixels=True) to set segWithHeader, else require/consume a passed-in
segHeader (or extract header from alignedSegImage) before proceeding; adjust
callers to pass the new segHeader argument when they supply alignedSegImage
instead of a path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ad31124c-0650-4faf-9556-6a1634783313

📥 Commits

Reviewing files that changed from the base of the PR and between ffcafa8 and 698f8d9.

⛔ Files ignored due to path filters (1)
  • pixi.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • pyproject.toml
  • src/readii/image_processing.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
src/readii/image_processing.py (1)

58-157: Remove the fully commented padSegToMatchCT implementation from the module.

This large commented block is dead code and obscures the active API surface. If this function is intentionally deprecated/disabled, remove it and track deprecation in changelog/docs instead of keeping the full body commented inline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` around lines 58 - 157, Remove the large
commented-out implementation of padSegToMatchCT from
src/readii/image_processing.py: delete the entire commented block referencing
padSegToMatchCT and related helper calls (read_dicom_auto, loadSegmentation,
flattenImage, alignImages, MedImage, Series, etc. as they appear in the block)
so the module no longer contains dead/obscuring code; if this function was
intentionally deprecated, add a short entry to the project changelog/docs noting
its removal/deprecation instead of keeping the implementation commented inline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/readii/image_processing.py`:
- Around line 221-223: displayCTSegOverlay currently types accepts np.ndarray
but later calls functions that require sitk.Image (getCroppedImages,
getROICenterCoords), causing runtime failures; fix by detecting np.ndarray
inputs at the start of displayCTSegOverlay and converting them to sitk.Image (or
raising a clear error) before any calls to getCroppedImages or
getROICenterCoords, and ensure sliceIdx/crop logic operates on the converted
sitk.Image; reference the functions displayCTSegOverlay, getCroppedImages, and
getROICenterCoords when making the change.
- Around line 257-262: The docstring for displayCTSegOverlay has duplicate
parameter entries for dispMin, dispMax, and ax; remove the repeated parameter
blocks so each parameter appears exactly once, keeping the correct type and
concise description (e.g., dispMin : int, dispMax : int, ax : plt.Axes) in the
parameters section of displayCTSegOverlay and ensure the remaining docstring
follows the project's docstring style (numpy/google) and formatting.
- Line 368: The type hint for parameter segmentationLabel in getCroppedImages
conflicts with its None default; change its annotation from int to a nullable
type (e.g., Optional[int] or int | None) and add the necessary import (from
typing import Optional) if using Optional so the signature reflects that
segmentationLabel can be None; update any related docstring or callers if they
rely on a strict int type.

---

Nitpick comments:
In `@src/readii/image_processing.py`:
- Around line 58-157: Remove the large commented-out implementation of
padSegToMatchCT from src/readii/image_processing.py: delete the entire commented
block referencing padSegToMatchCT and related helper calls (read_dicom_auto,
loadSegmentation, flattenImage, alignImages, MedImage, Series, etc. as they
appear in the block) so the module no longer contains dead/obscuring code; if
this function was intentionally deprecated, add a short entry to the project
changelog/docs noting its removal/deprecation instead of keeping the
implementation commented inline.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 974135e9-628a-4a88-8bbe-10b997584018

📥 Commits

Reviewing files that changed from the base of the PR and between 698f8d9 and 02417e1.

⛔ Files ignored due to path filters (1)
  • pixi.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • src/readii/image_processing.py

Comment on lines +221 to +223
ctImage: sitk.Image | np.ndarray,
segImage: sitk.Image | np.ndarray,
sliceIdx:int=-1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

displayCTSegOverlay declares np.ndarray support but runtime paths still require sitk.Image.

At Line 266, getCroppedImages(...) expects sitk.Image; at Line 270, getROICenterCoords(...) also assumes sitk.Image. With np.ndarray inputs, crop=True or sliceIdx=-1 will fail at runtime.

Proposed fix
 def displayCTSegOverlay(
     ctImage: sitk.Image | np.ndarray,
     segImage: sitk.Image | np.ndarray,
@@
 ) -> plt.Axes:
@@
+    def _center_slice(seg: sitk.Image | np.ndarray) -> int:
+        segArr = sitk.GetArrayFromImage(seg) if isinstance(seg, sitk.Image) else seg
+        nz = np.argwhere(segArr != 0)
+        if nz.size == 0:
+            raise ValueError("segImage contains no ROI voxels; cannot infer center slice.")
+        return int(nz[len(nz) // 2, 0])
+
     # If crop indicated, crop the CT and segmentation to just around the ROI
     if crop:
+        if not isinstance(ctImage, sitk.Image) or not isinstance(segImage, sitk.Image):
+            raise TypeError("crop=True requires sitk.Image inputs for ctImage and segImage.")
         ctImage, segImage = getCroppedImages(ctImage, segImage)
@@
     # If slice index is not provided, get the center slice for the ROI in segImage
     if sliceIdx == -1:
-        sliceIdx, _, _ = getROICenterCoords(segImage)
+        sliceIdx = _center_slice(segImage)

Also applies to: 265-270, 273-277

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` around lines 221 - 223, displayCTSegOverlay
currently types accepts np.ndarray but later calls functions that require
sitk.Image (getCroppedImages, getROICenterCoords), causing runtime failures; fix
by detecting np.ndarray inputs at the start of displayCTSegOverlay and
converting them to sitk.Image (or raising a clear error) before any calls to
getCroppedImages or getROICenterCoords, and ensure sliceIdx/crop logic operates
on the converted sitk.Image; reference the functions displayCTSegOverlay,
getCroppedImages, and getROICenterCoords when making the change.

Comment on lines +257 to +262
dispMin : int
Value to use as min for cmap in display
dispMax : int
Value to use as max for cmap in display
ax : plt.Axes
Axis to plot the slice on. If None, will create a new axis.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Duplicate docstring entries in displayCTSegOverlay.

dispMin, dispMax, and ax are documented twice, which makes the API docs noisy and error-prone to maintain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` around lines 257 - 262, The docstring for
displayCTSegOverlay has duplicate parameter entries for dispMin, dispMax, and
ax; remove the repeated parameter blocks so each parameter appears exactly once,
keeping the correct type and concise description (e.g., dispMin : int, dispMax :
int, ax : plt.Axes) in the parameters section of displayCTSegOverlay and ensure
the remaining docstring follows the project's docstring style (numpy/google) and
formatting.


def getCroppedImages(ctImage, segImage, segmentationLabel=None):
"""A function to crop a CT and segmentation to close to the ROI within the segmentation.
def getCroppedImages(ctImage: sitk.Image, segImage: sitk.Image, segmentationLabel:int=None)-> tuple[sitk.Image, sitk.Image]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

segmentationLabel type hint conflicts with its None default.

Line 368 annotates segmentationLabel as int but defaults to None. Use Optional[int] (or int | None) to match actual behavior.

Proposed fix
-def getCroppedImages(ctImage: sitk.Image, segImage: sitk.Image, segmentationLabel:int=None)-> tuple[sitk.Image, sitk.Image]:
+def getCroppedImages(
+    ctImage: sitk.Image,
+    segImage: sitk.Image,
+    segmentationLabel: Optional[int] = None,
+) -> tuple[sitk.Image, sitk.Image]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/readii/image_processing.py` at line 368, The type hint for parameter
segmentationLabel in getCroppedImages conflicts with its None default; change
its annotation from int to a nullable type (e.g., Optional[int] or int | None)
and add the necessary import (from typing import Optional) if using Optional so
the signature reflects that segmentationLabel can be None; update any related
docstring or callers if they rely on a strict int type.

@codecov

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 41.69492% with 344 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.15%. Comparing base (ffcafa8) to head (984fb4c).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/readii/process/label.py 8.53% 75 Missing ⚠️
src/readii/process/subset.py 7.27% 51 Missing ⚠️
src/readii/io/loaders/general.py 40.00% 36 Missing ⚠️
src/readii/process/split.py 5.88% 32 Missing ⚠️
src/readii/analyze/correlation.py 52.45% 29 Missing ⚠️
src/readii/analyze/plot_correlation.py 71.42% 28 Missing ⚠️
src/readii/process/images/crop.py 44.00% 28 Missing ⚠️
src/readii/io/loaders/images.py 4.76% 20 Missing ⚠️
src/readii/io/loaders/features.py 52.50% 19 Missing ⚠️
src/readii/io/writers/correlation_writer.py 72.09% 12 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #140      +/-   ##
==========================================
+ Coverage   75.48%   76.15%   +0.67%     
==========================================
  Files          41       41              
  Lines        2027     2009      -18     
==========================================
  Hits         1530     1530              
+ Misses        497      479      -18     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@katy-scott
katy-scott merged commit 29cd034 into main Apr 15, 2026
17 of 19 checks passed
@github-project-automation github-project-automation Bot moved this from Focus to Done in READII-2-ROQC Pipeline Apr 15, 2026
@katy-scott
katy-scott deleted the katy/update-displayCTSegOverlay branch April 15, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant