Skip to content

refactor: typing and api#5

Open
TomasBalak wants to merge 5 commits into
masterfrom
refactor/typing-and-api
Open

refactor: typing and api#5
TomasBalak wants to merge 5 commits into
masterfrom
refactor/typing-and-api

Conversation

@TomasBalak

Copy link
Copy Markdown
Collaborator

Summary

This PR fixes typing issues, unifies return value naming across all QC modules, migrates to the refactored staining library API, and replaces deprecated scikit-image functions.

Note

This PR is a part of a two-stage PR based on #2. The original PR was split in order to be more "review-friendly." This PR should be merged after PR #4 is merged.

Changes

Typing System

  • Add FloatingPointImage type alias (NDArray[np.float64])
  • Extend BlurScore, FoldArtifacts, ResidualArtifacts TypedDicts with
    number_of_examined_pixels and number_of_flagged_pixels fields
  • Update NDArray type hints across all modules for better type safety

API Consistency

  • Residual artifacts: rename coverage_maskartifacts_per_pixel, replace coverage (float ratio) with raw pixel counts number_of_examined_pixels / number_of_flagged_pixels
  • Fold detection: rename produced mask foldingfolding_per_pixel
  • Blur score: add number_of_examined_pixels / number_of_flagged_pixels to return dictionaries; rename internal blur_score_per_pixel variable to blur_score_pooled to avoid shadowing return key

Staining Library API Migration

  • ColorConversion.RGB2HERStandardConversions.RGB2HER
  • ConversionTypeConversionDirection with .conv_type.direction
  • .value[0][index].matrix[index] for color conversion matrix access

Deprecation Fixes

  • Replace skimage.morphology.binary_dilation/binary_erosion with
    dilation/erosion
  • Simplify blur score logic (remove redundant > 0 casts, use in-place
    operators)

Bug Fixes

  • Fix fold detection return to handle non-H&E stained images (previously returned duplicate code paths)
  • Fix local tile handling when masks are None

- Replace GitLab CI with GitHub Actions workflows
- Migrate from pdm to uv
- Update pre-commit config, ruff, mypy, and markdownlint configs
- Update documentation URLs from GitLab to GitHub
- Reorganize pyproject.toml for uv compatibility
- Fix import ordering across all modules
- Fix folding threshold calculation for empty masks
- Apply ruff auto-fixes for consistency
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@TomasBalak, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 58 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d4d9556-f3b1-4414-b32f-22130ba81246

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3ec66 and 7961a57.

⛔ Files ignored due to path filters (2)
  • pdm.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • .github/workflows/mkdocs-build.yml
  • .github/workflows/python-lint.yml
  • .gitlab-ci.yml
  • .markdownlint.yaml
  • .mypy.ini
  • .pre-commit-config.yaml
  • .ruff.toml
  • README.md
  • docs/getting-started/installation.md
  • docs/getting-started/quality-control.md
  • docs/index.md
  • pyproject.toml
  • rationai/qc/blur/blur_score_laplacian.py
  • rationai/qc/blur/blur_score_piqe.py
  • rationai/qc/blur/blur_score_roberts.py
  • rationai/qc/blur/utils.py
  • rationai/qc/folding/folding.py
  • rationai/qc/residual_artifacts/residual_artifacts_and_coverage.py
  • rationai/qc/staining/color_difference.py
  • rationai/qc/staining/dominant_stains.py
  • rationai/qc/staining/staining_difference.py
  • rationai/qc/typing.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/typing-and-api

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request migrates the project's dependency management from PDM to uv, updates pre-commit and linter configurations, and standardizes the return structures of several quality control functions to consistently report the number of examined and flagged pixels. It also replaces deprecated scikit-image morphological operations with their modern equivalents and updates the documentation accordingly. The review identified a critical logic bug in the folding safety check where a condition is mathematically impossible to satisfy and creates a 3D array instead of a 2D array, a shape mismatch in the initialization of local_eosin_channel, and a potential shape mismatch in blur_score_piqe.py when image dimensions are not divisible by 16.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +151 to 154
folding_test_markers = opening(
thresholded_eosin & thresholded_saturation & thresholded_value,
disk(cell_nucleus_size // (mpp)),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There is a critical logic bug in the preceding if statement (lines 144-148) that directly impacts this line:\n\n1. Impossible Condition: np.sum(thresholded_value) * 2 > tile.size compares a 2D mask sum (max H * W) against tile.size (which is H * W * 3 for an RGB image). Thus, the condition is mathematically impossible to satisfy, and the safety check never triggers. It should compare against tile.shape[0] * tile.shape[1] or tile.size // 3.\n2. Shape Mismatch: If the condition were ever met, thresholded_value = np.zeros(tile.shape) would create a 3D array of shape (H, W, 3) instead of a 2D array of shape (H, W). This would cause a ValueError or unexpected behavior on line 152 during the bitwise AND operation thresholded_eosin & thresholded_saturation & thresholded_value.\n\nTo fix this, the preceding block should be updated to:\npython\n num_pixels = tile.shape[0] * tile.shape[1]\n if (\n np.sum(thresholded_value) * 2 > num_pixels\n or np.sum(thresholded_saturation) * 2 > num_pixels\n or (hematoxylin_eosin_stained and np.sum(thresholded_eosin) * 2 > num_pixels)\n ):\n thresholded_value = np.zeros(tile.shape[:2], dtype=bool)\n

eosin_channel = np.ones_like(tissue_mask)
if local_tiles is not None:
local_eosin_channel = np.ones_like(local_tiles)
local_eosin_channel = np.ones_like(local_tiles, dtype=np.float64)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

local_tiles is a 3D RGB image of shape (H_local, W_local, 3). Using np.ones_like(local_tiles) creates a 3D array of shape (H_local, W_local, 3). However, local_eosin_channel is expected to be a 2D single-channel array of shape (H_local, W_local) to match eosin_channel and other channels.\n\nWe should initialize it as a 2D array using the spatial dimensions of local_tiles.

Suggested change
local_eosin_channel = np.ones_like(local_tiles, dtype=np.float64)
local_eosin_channel = np.ones(local_tiles.shape[:2], dtype=np.float64)

# activity_mask is multiplied by the foreground mask to nullify background pixels
activity_mask = activity_mask * foreground_mask
# Invert and restrict the mask to foreground
activity_mask = foreground_mask * ~(activity_mask > 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If grayscale_img.shape != activity_mask.shape due to the height not being divisible by 16, the padding logic above (which only pads the width/columns) will not prevent a shape mismatch here, leading to a ValueError during multiplication.\n\nConsider updating the padding logic earlier in the function to dynamically pad both dimensions:\npython\n if grayscale_img.shape != activity_mask.shape:\n pad_h = grayscale_img.shape[0] - activity_mask.shape[0]\n pad_w = grayscale_img.shape[1] - activity_mask.shape[1]\n activity_mask = np.pad(activity_mask, ((0, pad_h), (0, pad_w)), mode="edge")\n

Both ColorConversion and convert_color are now imported from
rationai.staining, consistent with the rest of the codebase.
- Add FloatingPointImage type alias and update return type annotations
- Rename fold detection key from 'folding' to 'folding_per_pixel'
- Rename residual artifacts key from 'coverage_mask'/'coverage' to
  'artifacts_per_pixel'/'number_of_examined_pixels'/'number_of_flagged_pixels'
- Add number_of_examined_pixels and number_of_flagged_pixels to all
  blur score and fold artifact return dictionaries
- Replace deprecated binary_dilation/binary_erosion with dilation/erosion
- Replace deprecated ColorConversion with StandardConversions API
- Update color_difference.py for new staining library API
- Simplify blur score logic and fix shadowing issues
@TomasBalak TomasBalak force-pushed the refactor/typing-and-api branch from 689becd to 7961a57 Compare June 9, 2026 18:51
@TomasBalak TomasBalak marked this pull request as ready for review June 9, 2026 18:56
@TomasBalak TomasBalak requested review from a team June 9, 2026 18:56
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