refactor: gitlab to github migration#4
Conversation
- 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
There was a problem hiding this comment.
Code Review
This pull request migrates the project's development environment and dependency management from PDM to uv, updates pre-commit and linter configurations, and revises the documentation and example code. It also refactors the threshold calculation logic in folding.py and standardizes imports across several modules. The review feedback highlights several critical issues in the updated documentation where incorrect dictionary keys are used (which would cause KeyErrors at runtime), suggests falling back to the tile's own tissue mask in folding.py when the local neighborhood is empty, recommends raising a ValueError for mismatched local parameters, and points out redundant imports in blur_score_laplacian.py.
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.
| if local_values.size > 0: | ||
| return threshold_yen(local_values) | ||
| else: | ||
| return 1.0 | ||
|
|
||
| values = MaskedArray(img, ~mask).compressed() | ||
|
|
||
| if values.size == 0: | ||
| return 1.0 | ||
|
|
||
| return threshold_yen(values) |
There was a problem hiding this comment.
If local_tiles is provided but the local neighborhood has no tissue (i.e., local_values.size == 0), the function currently returns a hardcoded 1.0 threshold. This completely ignores the current tile's own tissue mask (img and mask), which could still contain valid tissue. Instead, it should fall back to the tile's own tissue mask to calculate the threshold, and only return 1.0 if the tile's own tissue mask is also empty.
| if local_values.size > 0: | |
| return threshold_yen(local_values) | |
| else: | |
| return 1.0 | |
| values = MaskedArray(img, ~mask).compressed() | |
| if values.size == 0: | |
| return 1.0 | |
| return threshold_yen(values) | |
| if local_values.size > 0: | |
| return threshold_yen(local_values) | |
| values = MaskedArray(img, ~mask).compressed() | |
| if values.size == 0: | |
| return 1.0 | |
| return threshold_yen(values) |
| if local_tiles is not None and local_mask is not None: | ||
| local_values = MaskedArray(local_tiles, ~local_mask).compressed() |
There was a problem hiding this comment.
If only one of local_tiles or local_mask is provided, the function silently ignores local_tiles and falls back to the global image. It is safer and more robust to raise a ValueError when there is a mismatch between these two parameters.
if (local_tiles is None) != (local_mask is None):
raise ValueError("Both local_tiles and local_mask must be provided together.")
if local_tiles is not None and local_mask is not None:
local_values = MaskedArray(local_tiles, ~local_mask).compressed()
📝 WalkthroughWalkthroughThis PR migrates the project from PDM to UV as the dependency manager, updates CI/CD infrastructure to use GitHub Actions with shared templates, refines pre-commit and linting configurations, and comprehensively updates documentation and examples. Minor code improvements enhance type safety in the folding module and consolidate imports across QC functions. Version bumped to 1.0.1. ChangesBuild System and Tooling Migration
Documentation, Examples, and Code Quality
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Both ColorConversion and convert_color are now imported from rationai.staining, consistent with the rest of the codebase.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
rationai/qc/folding/folding.py (2)
119-121:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winFix dtype mismatch in local_eosin_channel initialization.
Line 121 assigns
np.ones_like(local_tiles)tolocal_eosin_channel, butlocal_tilesis an RGB image with dtype uint8, while the parameter is typed asNDArray[np.float64] | None. The mypy error confirms this type mismatch.🔧 Proposed fix
else: 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(local_tiles.shape[:2], dtype=np.float64)Note: Use
shape[:2]to get height×width only, since eosin_channel is 2D while local_tiles is 3D (RGB).🤖 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 `@rationai/qc/folding/folding.py` around lines 119 - 121, The local_eosin_channel is created from local_tiles which is an RGB uint8 array causing a dtype mismatch; instead allocate a 2D float array matching the spatial shape of local_tiles (height×width) and dtype float64 like eosin_channel/tissue_mask. Replace the np.ones_like(local_tiles) usage by creating an array using local_tiles.shape[:2] (or np.ones_like(tissue_mask)) with dtype=np.float64 so local_eosin_channel has the correct 2D shape and type; update references to local_eosin_channel accordingly.Source: Pipeline failures
154-169:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEliminate variable redefinition and simplify control flow.
The variable
resultis defined with type annotation on both line 156 and line 163, causing a mypy redefinition error. Both branches also construct nearly identical dictionaries, differing only in the value offolding_test.♻️ Proposed refactoring
if hematoxylin_eosin_stained: folding_test = reconstruction(folding_test_markers, thresholded_eosin) - result: FoldArtifacts = { - "folding": folding_test, - "thresholded_saturation": thresholded_saturation, - "thresholded_eosin": thresholded_eosin, - "thresholded_value": thresholded_value, - } - return result - result: FoldArtifacts = { + else: + folding_test = folding_test_markers + + result: FoldArtifacts = { "folding": folding_test, "thresholded_saturation": thresholded_saturation, "thresholded_eosin": thresholded_eosin, "thresholded_value": thresholded_value, } + return result🤖 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 `@rationai/qc/folding/folding.py` around lines 154 - 169, The code duplicates the FoldArtifacts dict and redefines `result` twice, causing a mypy redefinition error; fix by computing `folding_test` conditionally (e.g., set `folding_test = reconstruction(folding_test_markers, thresholded_eosin)` only if `hematoxylin_eosin_stained` else keep existing value or None) and then build and return a single `result: FoldArtifacts = { "folding": folding_test, "thresholded_saturation": thresholded_saturation, "thresholded_eosin": thresholded_eosin, "thresholded_value": thresholded_value }` once; update the code paths in the folding function so only `folding_test` is chosen per branch and the `result` construction/return is unified.Source: Pipeline failures
🧹 Nitpick comments (3)
.github/workflows/mkdocs-build.yml (1)
11-11: Shared workflow@mainreference appears intentional in this repo.Both
.github/workflows/mkdocs-build.ymland.github/workflows/python-lint.ymldelegate toRationAI/.github/.github/workflows/*@main``, indicating the@main(unpinned) pattern is used consistently for “RationAI Standard” shared workflows. Supply-chain risk remains as a design tradeoff; pinning to a commit SHA should be considered only if required by your security policy.🤖 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 @.github/workflows/mkdocs-build.yml at line 11, The workflow reference "RationAI/.github/.github/workflows/mkdocs-build.yml@main" is intentionally unpinned; decide whether to leave it as `@main` or pin to a specific commit SHA/tag for supply-chain risk reduction. If you must pin, replace the `@main` suffix with a vetted commit SHA or release tag in the reference string (and make the same change for the analogous "RationAI/.github/.github/workflows/python-lint.yml@main" reference), run the CI to validate the included shared workflows still resolve and behave as expected, and update repository docs/policy noting the chosen pinning strategy.Source: Linters/SAST tools
docs/getting-started/installation.md (1)
21-36: ⚡ Quick winConsider documenting the Python version requirement.
The installation instructions work correctly, but the project requires Python >=3.11 (as declared in pyproject.toml). Adding a note about this requirement would help users avoid installation failures due to Python version mismatches.
📋 Suggested addition
Add a brief note before or after the installation methods:
!!! note "Python Version" This library requires Python 3.11 or higher.🤖 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/getting-started/installation.md` around lines 21 - 36, Add a short note about the Python version requirement near the "### Install the Core Quality Control Library" section: state that the library requires Python 3.11 or higher (as declared in pyproject.toml) and place this note immediately before or after the listed installation methods so users see it before attempting installation.rationai/qc/blur/blur_score_laplacian.py (1)
5-5: 💤 Low valueMypy stub incompleteness for skimage.morphology (informational).
The pipeline shows mypy errors for
binary_dilationandbinary_erosionnot being explicitly exported. These functions are available in scikit-image 0.24.0 at runtime but may be missing from the type stubs. This is a mypy tooling limitation rather than a code issue.If you want to suppress these specific mypy errors, you can add
# type: ignore[attr-defined]comments, but the code itself is correct.🤖 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 `@rationai/qc/blur/blur_score_laplacian.py` at line 5, Mypy reports missing attributes for skimage.morphology exports; silence this tool-only error by annotating the import of binary_dilation and binary_erosion with a type ignore for undefined attributes (add "# type: ignore[attr-defined]" to the import line that brings in binary_dilation and binary_erosion) so the runtime-available functions binary_dilation and binary_erosion can be used without failing static checks.Source: Pipeline failures
🤖 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/getting-started/quality-control.md`:
- Around line 238-240: The code uses the wrong dict key for the FoldArtifacts
output; change the access to use the TypedDict's defined key "folding" instead
of "folding_per_pixel" (i.e., update the expression that builds mask from
artifacts[...] so Image.fromarray receives artifacts["folding"].astype(np.uint8)
scaled appropriately); look for the mask creation line that calls
Image.fromarray and update the key reference to "folding".
- Around line 56-62: The example uses wrong dictionary keys from the
ResidualArtifacts TypedDict; replace any use of artifacts["artifacts_per_pixel"]
with artifacts["coverage_mask"] and stop referencing non-existent keys
artifacts['number_of_flagged_pixels'] and
artifacts['number_of_examined_pixels']; instead use the provided
artifacts["coverage"] value and the coverage_mask to compute or describe
coverage as needed. Update the explanatory text that mentions "examined pixels"
and "flagged pixels" to refer to the API's terms ("coverage_mask" and
"coverage") or to derived values computed from those two fields (e.g., percent
coverage) so documentation matches the ResidualArtifacts contract.
In `@pyproject.toml`:
- Around line 19-20: The pyproject.toml still lists git dependencies pointing to
GitLab for the rationai-masks and rationai-staining packages; update these
entries (the strings referencing "rationai-masks @
git+https://gitlab.ics.muni.cz/..." and "rationai-staining @
git+https://gitlab.ics.muni.cz/...") to the correct GitHub repository URLs if
those packages were migrated (or else update the PR description/docs to
explicitly state these two dependencies remain on GitLab); locate the dependency
lines in pyproject.toml and replace the GitLab URLs with the authoritative
GitHub repo addresses for RationAI/masks and RationAI/staining (or add a clear
comment in the PR/docs clarifying they intentionally remain on GitLab).
In `@rationai/qc/staining/color_difference.py`:
- Line 3: Replace the incorrect import and field accesses to match the updated
rationai-staining API: change the import in this module to import
ConversionDirection (not ConversionType) alongside ColorConversion, and update
all uses of conversion.conv_type to conversion.direction and
conversion.value[...] to use conversion.matrix or conversion.inverse as
appropriate; specifically inspect code paths that expect RGB2STAIN/STAIN2RGB and
switch logic to read conversion.direction, and pull the transformation arrays
from conversion.matrix (or the inverse matrix from conversion.inverse) instead
of conversion.value.
---
Outside diff comments:
In `@rationai/qc/folding/folding.py`:
- Around line 119-121: The local_eosin_channel is created from local_tiles which
is an RGB uint8 array causing a dtype mismatch; instead allocate a 2D float
array matching the spatial shape of local_tiles (height×width) and dtype float64
like eosin_channel/tissue_mask. Replace the np.ones_like(local_tiles) usage by
creating an array using local_tiles.shape[:2] (or np.ones_like(tissue_mask))
with dtype=np.float64 so local_eosin_channel has the correct 2D shape and type;
update references to local_eosin_channel accordingly.
- Around line 154-169: The code duplicates the FoldArtifacts dict and redefines
`result` twice, causing a mypy redefinition error; fix by computing
`folding_test` conditionally (e.g., set `folding_test =
reconstruction(folding_test_markers, thresholded_eosin)` only if
`hematoxylin_eosin_stained` else keep existing value or None) and then build and
return a single `result: FoldArtifacts = { "folding": folding_test,
"thresholded_saturation": thresholded_saturation, "thresholded_eosin":
thresholded_eosin, "thresholded_value": thresholded_value }` once; update the
code paths in the folding function so only `folding_test` is chosen per branch
and the `result` construction/return is unified.
---
Nitpick comments:
In @.github/workflows/mkdocs-build.yml:
- Line 11: The workflow reference
"RationAI/.github/.github/workflows/mkdocs-build.yml@main" is intentionally
unpinned; decide whether to leave it as `@main` or pin to a specific commit
SHA/tag for supply-chain risk reduction. If you must pin, replace the `@main`
suffix with a vetted commit SHA or release tag in the reference string (and make
the same change for the analogous
"RationAI/.github/.github/workflows/python-lint.yml@main" reference), run the CI
to validate the included shared workflows still resolve and behave as expected,
and update repository docs/policy noting the chosen pinning strategy.
In `@docs/getting-started/installation.md`:
- Around line 21-36: Add a short note about the Python version requirement near
the "### Install the Core Quality Control Library" section: state that the
library requires Python 3.11 or higher (as declared in pyproject.toml) and place
this note immediately before or after the listed installation methods so users
see it before attempting installation.
In `@rationai/qc/blur/blur_score_laplacian.py`:
- Line 5: Mypy reports missing attributes for skimage.morphology exports;
silence this tool-only error by annotating the import of binary_dilation and
binary_erosion with a type ignore for undefined attributes (add "# type:
ignore[attr-defined]" to the import line that brings in binary_dilation and
binary_erosion) so the runtime-available functions binary_dilation and
binary_erosion can be used without failing static checks.
🪄 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: ffda644f-4c55-42f0-ac37-59585255edac
⛔ Files ignored due to path filters (2)
pdm.lockis excluded by!**/*.lockuv.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
.github/workflows/mkdocs-build.yml.github/workflows/python-lint.yml.gitlab-ci.yml.markdownlint.yaml.mypy.ini.pre-commit-config.yaml.ruff.tomlREADME.mddocs/getting-started/installation.mddocs/getting-started/quality-control.mddocs/index.mdpyproject.tomlrationai/qc/blur/blur_score_laplacian.pyrationai/qc/folding/folding.pyrationai/qc/residual_artifacts/residual_artifacts_and_coverage.pyrationai/qc/staining/color_difference.pyrationai/qc/staining/dominant_stains.pyrationai/qc/staining/staining_difference.py
💤 Files with no reviewable changes (1)
- .gitlab-ci.yml
| ```python linenums="18" | ||
| mask = Image.fromarray(255 * artifacts["artifacts_per_pixel"].astype(np.uint8)) | ||
| mask.save("residual_mask.png") | ||
|
|
||
| print("Coverage Number:", artifacts["coverage"]) | ||
| print(f"Number of flagged pixels: {artifacts['number_of_flagged_pixels']}") | ||
| print(f"Number of examined pixels: {artifacts['number_of_examined_pixels']}") | ||
| ``` |
There was a problem hiding this comment.
Correct the dictionary keys to match the ResidualArtifacts API contract.
The ResidualArtifacts TypedDict defines only two keys: coverage_mask and coverage. The example code uses incorrect keys that will cause KeyError exceptions at runtime:
- Line 57 uses
artifacts["artifacts_per_pixel"]but should useartifacts["coverage_mask"] - Lines 60-61 reference
artifacts['number_of_flagged_pixels']andartifacts['number_of_examined_pixels'], but these keys don't exist in the return type
The narrative text in lines 68-69 also needs updating since it references "examined pixels" and "flagged pixels" counts that aren't part of the API.
📝 Proposed fix
-mask = Image.fromarray(255 * artifacts["artifacts_per_pixel"].astype(np.uint8))
+mask = Image.fromarray(255 * artifacts["coverage_mask"].astype(np.uint8))
mask.save("residual_mask.png")
-print(f"Number of flagged pixels: {artifacts['number_of_flagged_pixels']}")
-print(f"Number of examined pixels: {artifacts['number_of_examined_pixels']}")
+print(f"Coverage: {artifacts['coverage']}")Also update the narrative in lines 68-69:
-In our example, 17900 out of the 233744 examined pixels were labeled as artifacts,
-meaning that **little more than 7% of the image's foreground area** is covered by artifacts.
+In our example, the coverage value indicates that **little more than 7% of the image's foreground area** is covered by artifacts.🤖 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/getting-started/quality-control.md` around lines 56 - 62, The example
uses wrong dictionary keys from the ResidualArtifacts TypedDict; replace any use
of artifacts["artifacts_per_pixel"] with artifacts["coverage_mask"] and stop
referencing non-existent keys artifacts['number_of_flagged_pixels'] and
artifacts['number_of_examined_pixels']; instead use the provided
artifacts["coverage"] value and the coverage_mask to compute or describe
coverage as needed. Update the explanatory text that mentions "examined pixels"
and "flagged pixels" to refer to the API's terms ("coverage_mask" and
"coverage") or to derived values computed from those two fields (e.g., percent
coverage) so documentation matches the ResidualArtifacts contract.
| ```python linenums="27" | ||
| mask = Image.fromarray(255 * artifacts["folding_per_pixel"].astype(np.uint8)) | ||
| ``` |
There was a problem hiding this comment.
Correct the dictionary key to match the FoldArtifacts API contract.
The FoldArtifacts TypedDict defines the main output mask under the key folding, not folding_per_pixel.
📝 Proposed fix
-mask = Image.fromarray(255 * artifacts["folding_per_pixel"].astype(np.uint8))
+mask = Image.fromarray(255 * artifacts["folding"].astype(np.uint8))📝 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.
| ```python linenums="27" | |
| mask = Image.fromarray(255 * artifacts["folding_per_pixel"].astype(np.uint8)) | |
| ``` |
🤖 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/getting-started/quality-control.md` around lines 238 - 240, The code
uses the wrong dict key for the FoldArtifacts output; change the access to use
the TypedDict's defined key "folding" instead of "folding_per_pixel" (i.e.,
update the expression that builds mask from artifacts[...] so Image.fromarray
receives artifacts["folding"].astype(np.uint8) scaled appropriately); look for
the mask creation line that calls Image.fromarray and update the key reference
to "folding".
| "rationai-masks @ git+https://gitlab.ics.muni.cz/rationai/digital-pathology/libraries/masks.git", | ||
| "rationai-staining @ git+https://gitlab.ics.muni.cz/rationai/digital-pathology/libraries/staining.git", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check if RationAI organization has masks and staining repositories on GitHub
gh repo view RationAI/masks --json name,url 2>/dev/null || echo "masks repo not found"
gh repo view RationAI/staining --json name,url 2>/dev/null || echo "staining repo not found"
# Also check if there are any other references to gitlab URLs in the repository
rg -n 'gitlab\.ics\.muni\.cz' --type-add 'config:*.{toml,yaml,yml,md,txt}' -t configRepository: RationAI/quality-control
Length of output: 359
Git dependencies still point to GitLab despite the “GitLab → GitHub” migration goal (pyproject.toml 19-20).
rationai-masks and rationai-staining still reference https://gitlab.ics.muni.cz/.... If the migration is intended to cover these libraries as well, update them to the correct GitHub URLs (the assumed RationAI/masks and RationAI/staining repos aren’t found on GitHub); otherwise, adjust the PR objectives/docs to clarify these specific dependencies remain on GitLab.
🤖 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 `@pyproject.toml` around lines 19 - 20, The pyproject.toml still lists git
dependencies pointing to GitLab for the rationai-masks and rationai-staining
packages; update these entries (the strings referencing "rationai-masks @
git+https://gitlab.ics.muni.cz/..." and "rationai-staining @
git+https://gitlab.ics.muni.cz/...") to the correct GitHub repository URLs if
those packages were migrated (or else update the PR description/docs to
explicitly state these two dependencies remain on GitLab); locate the dependency
lines in pyproject.toml and replace the GitLab URLs with the authoritative
GitHub repo addresses for RationAI/masks and RationAI/staining (or add a clear
comment in the PR/docs clarifying they intentionally remain on GitLab).
| @@ -1,9 +1,9 @@ | |||
| import numpy as np | |||
| from numpy.typing import NDArray | |||
| from rationai.staining import ColorConversion, ConversionType | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ConversionType and ColorConversion definitions
rg -nP --type=py -C5 'class\s+ConversionType|class\s+ColorConversion'
# Check what attributes ColorConversion has
ast-grep --pattern $'class ColorConversion {
$$$
}'Repository: RationAI/quality-control
Length of output: 50
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where rationai.staining is implemented in this repo
echo "== Files mentioning rationai.staining =="
rg -n --hidden --no-ignore-vcs -S "rationai\.staining" . || true
echo
echo "== Search for ConversionType and ColorConversion definitions in repo =="
rg -n --hidden --no-ignore-vcs -S --type=py "ConversionType|ColorConversion" rationai || true
echo
# Locate the staining package root
echo "== Listing rationai/staining directory (if present) =="
ls -la rationai/staining 2>/dev/null || true
echo
# Check what the package exports
if [ -f rationai/staining/__init__.py ]; then
echo "== rationai/staining/__init__.py =="
cat -n rationai/staining/__init__.py
else
echo "== No rationai/staining/__init__.py found =="
fi
echo
# Show implementation files if they exist
fd -t f "color_difference.py" rationai || true
fd -t f "*staining*" rationai || true
echo
# Inspect the imported module for actual symbols/attributes
for f in $(fd -t f "conversion*.py|*color*conversion*.py|*staining*.py" rationai/staining 2>/dev/null || true); do
echo "== File: $f =="
head -n 200 "$f"
doneRepository: RationAI/quality-control
Length of output: 3409
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate rationai package in this sandbox =="
python3 - <<'PY'
import rationai, inspect, os
import rationai.staining as st
print("rationai module file:", getattr(rationai, "__file__", None))
print("rationai.staining module file:", getattr(st, "__file__", None))
print("rationai.staining members (sample):", sorted([n for n in dir(st) if not n.startswith("_")])[:80])
print("Has ConversionType:", hasattr(st, "ConversionType"))
print("Has ColorConversion:", hasattr(st, "ColorConversion"))
print("ConversionType object:", getattr(st, "ConversionType", None))
print("ColorConversion object:", getattr(st, "ColorConversion", None))
PY
echo
echo "== Inspect ColorConversion and its attributes =="
python3 - <<'PY'
import inspect
from rationai.staining import ColorConversion
print("ColorConversion is:", ColorConversion)
print("ColorConversion defined at:", inspect.getsourcefile(ColorConversion))
print("ColorConversion source snippet:")
src = inspect.getsource(ColorConversion)
print("\n".join(src.splitlines()[:80]))
PY
echo
echo "== Search repo for .conv_type and .value usage on ColorConversion =="
rg -n --hidden --no-ignore-vcs "\.conv_type\b|\.value\b" rationai/qc || true
echo
echo "== Search repo for ConversionType usage =="
rg -n --hidden --no-ignore-vcs "ConversionType" rationai/qc || trueRepository: RationAI/quality-control
Length of output: 262
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Repo root files for dependency info =="
ls -ლა | head -n 200
echo
echo "== Search for rationai-staining in dependency files =="
rg -n --hidden --no-ignore-vcs "rationai[-_]?staining" -S . || true
echo
echo "== Look for pyproject/requirements =="
ls -ლა pyproject.toml requirements*.txt setup.cfg setup.py poetry.lock 2>/dev/null || true
echo
echo "== Does this repo contain any rationai/staining code locally? =="
find . -maxdepth 4 -type d -name "staining" | head -n 50
find . -maxdepth 6 -type f -path "*rationai*/staining/*" | head -n 50
echo
echo "== If any local type hints reference ConversionType/ColorConversion, show them =="
rg -n --hidden --no-ignore-vcs "ConversionType|ColorConversion" rationai/qc || trueRepository: RationAI/quality-control
Length of output: 192
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Repo root files for dependency info =="
ls -la | head -n 200
echo
echo "== Search for rationai-staining in dependency files =="
rg -n --hidden --no-ignore-vcs "rationai[-_]?staining|rationai\.staining" -S pyproject.toml requirements*.txt setup.cfg setup.py poetry.lock 2>/dev/null || true
rg -n --hidden --no-ignore-vcs "rationai[-_]?staining|rationai\.staining" -S . || true
echo
echo "== Look for pyproject/requirements =="
ls -la pyproject.toml requirements*.txt setup.cfg setup.py poetry.lock 2>/dev/null || true
echo
echo "== Does this repo contain any local rationai/staining code? =="
find . -maxdepth 4 -type d -name "staining" | head -n 50
find . -maxdepth 6 -type f -path "*rationai*/staining/*" | head -n 50
echo
echo "== Any local type hints referencing ConversionType/ColorConversion? =="
rg -n --hidden --no-ignore-vcs "ConversionType|ColorConversion" rationai/qc || trueRepository: RationAI/quality-control
Length of output: 5658
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== List docs/api/staining =="
ls -la docs/api/staining || true
echo
echo "== Search docs/api/staining for ConversionType / ColorConversion =="
rg -n --hidden --no-ignore-vcs "ConversionType|ColorConversion|conv_type|\.value\b" docs/api/staining || true
echo
echo "== Show docs/api/staining __init__ or index if present =="
ls -la docs/api/staining 2>/dev/null | head -n 50
echo
# Identify likely markdown/python files
find docs/api/staining -maxdepth 2 -type f | head -n 50
echo
echo "== Print any files containing ConversionType/ColorConversion =="
for f in $(rg -l --hidden --no-ignore-vcs "ConversionType|ColorConversion" docs/api/staining 2>/dev/null || true); do
echo "--- $f ---"
sed -n '1,160p' "$f"
done
echo
echo "== Inspect rationai/qc/staining/__init__.py =="
cat -n rationai/qc/staining/__init__.py 2>/dev/null || true
echo
echo "== Inspect rationai/qc/staining/color_difference.py (relevant portion) =="
sed -n '1,220p' rationai/qc/staining/color_difference.pyRepository: RationAI/quality-control
Length of output: 5881
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== pyproject.toml: locate rationai-staining dependency =="
rg -n "rationai-staining" pyproject.toml
sed -n '1,80p' pyproject.toml
echo
echo "== uv.lock: show rationai-staining entries =="
rg -n "name = \"rationai-staining\"|rationai-staining" uv.lock | head -n 80
echo
echo "== Print the rationai-staining package block from uv.lock (if any) =="
python3 - <<'PY'
import re
path="uv.lock"
text=open(path,'r',encoding='utf-8').read().splitlines()
# find lines containing 'name = "rationai-staining"'
idx=[i for i,l in enumerate(text) if 'name = "rationai-staining"' in l]
print("Found blocks at lines:", idx[:10])
for i in idx[:1]:
start=max(0,i-20)
end=min(len(text), i+120)
print("\n".join(text[start:end]))
PYRepository: RationAI/quality-control
Length of output: 9747
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Python sys.path / site-packages =="
python3 - <<'PY'
import sys, site
print("sys.version:", sys.version)
print("sys.path:")
for p in sys.path:
print(" -", p)
print("site.getsitepackages():", site.getsitepackages())
print("site.getusersitepackages():", site.getusersitepackages())
PY
echo
echo "== Search common python site-packages for rationai-staining =="
python3 - <<'PY'
import sys, site, os
paths = []
paths += site.getsitepackages()
paths += [site.getusersitepackages()]
paths = [p for p in paths if p and os.path.isdir(p)]
for base in paths:
print("##", base)
# list matching directories
for cand in ["rationai", "rationai/staining"]:
full = os.path.join(base, cand)
if os.path.exists(full):
print("FOUND:", full)
PY
echo
echo "== Locate any cached checkout of staining.git (uv cache / git clones) =="
# Try to find directories/files containing the commit hash from uv.lock
commit="c48d216e910df11f3e6891aa071ebb8d04635727"
rg -n --hidden --no-ignore-vcs "$commit" -S ~ 2>/dev/null | head -n 50 || true
echo
echo "== Search for ColorConversion / ConversionType symbols in site-packages and uv cache =="
python3 - <<'PY'
import os, site, sys, glob, subprocess, textwrap
bases=[]
import site as s
bases += [p for p in s.getsitepackages() if os.path.isdir(p)]
bases += [s.getusersitepackages()] if os.path.isdir(s.getusersitepackages()) else []
# also add uv cache locations
candidates=[
os.path.expanduser("~/.cache/uv"),
os.path.expanduser("~/.local/share/uv"),
os.path.expanduser("~/.uv"),
]
for c in candidates:
if os.path.isdir(c):
bases.append(c)
# de-dupe
seen=set(); out=[]
for b in bases:
if b not in seen:
seen.add(b); out.append(b)
print("Search bases:")
for b in out:
print(" -", b)
# rg is faster; run via shell for each base with tight scope
PY
# Use rg across the discovered bases only (best-effort)
# If rg finds nothing, exit 0 due to `|| true`.
for base in "$(python3 - <<'PY'
import os, site
bases=[]
import site as s
bases += [p for p in s.getsitepackages() if os.path.isdir(p)]
bases += [s.getusersitepackages()] if os.path.isdir(s.getusersitepackages()) else []
for c in [os.path.expanduser("~/.cache/uv"), os.path.expanduser("~/.local/share/uv"), os.path.expanduser("~/.uv")]:
if os.path.isdir(c):
bases.append(c)
# print bases one per line
for b in bases:
print(b)
PY
)"; do
if [ -d "$base" ]; then
echo "## Searching in $base"
rg -n --hidden --no-ignore-vcs --type=py "class\s+ColorConversion|class\s+ConversionType|conv_type|\.value\b" "$base" 2>/dev/null | head -n 80 || true
fi
done
echo
echo "== Also search repository-wide for any vendored staining implementation =="
rg -n --hidden --no-ignore-vcs --type=py "class\s+ColorConversion|class\s+ConversionType" . || trueRepository: RationAI/quality-control
Length of output: 1204
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find uv caches that might contain the staining.git checkout =="
for d in \
"$HOME/.cache/uv" \
"$HOME/.local/share/uv" \
"$HOME/.uv" \
/tmp/uv* \
; do
if [ -d "$d" ]; then
echo "## $d"
find "$d" -maxdepth 6 -type f -iname "*staining*" 2>/dev/null | head -n 50 || true
find "$d" -maxdepth 6 -type d -iname "*staining*" 2>/dev/null | head -n 50 || true
fi
done
echo
echo "== Search entire home for the pinned commit hash (best-effort, limited depth) =="
commit="c48d216e910df11f3e6891aa071ebb8d04635727"
for d in "$HOME/.cache/uv" "$HOME/.local/share/uv" "$HOME/.uv"; do
if [ -d "$d" ]; then
rg -n --hidden --no-ignore-vcs "$commit" "$d" 2>/dev/null | head -n 20 || true
fi
done
echo
echo "== Search for a python module directory named rationai/staining =="
for base in /usr/local/lib/python3.11/dist-packages /usr/lib/python3/dist-packages /usr/local/lib/python3.11/site-packages "$HOME/.local/lib/python3.11/site-packages"; do
if [ -d "$base" ]; then
ls -la "$base/rationai" 2>/dev/null || true
ls -la "$base/rationai/staining" 2>/dev/null || true
fi
done
echo
echo "== If any rationai-staining source exists, print candidate file paths containing ColorConversion =="
# Find any py files that mention ColorConversion within likely cache locations
candidates=()
for d in "$HOME/.cache/uv" "$HOME/.local/share/uv" "$HOME/.uv"; do
[ -d "$d" ] && candidates+=("$d")
done
if [ ${`#candidates`[@]} -eq 0 ]; then
echo "No uv cache directories found."
exit 0
fi
rg -n --hidden --no-ignore-vcs "class\s+ColorConversion|ColorConversion\s*=" "${candidates[@]}" 2>/dev/null | head -n 40 || trueRepository: RationAI/quality-control
Length of output: 416
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in docs/api/staining/*.md; do
echo "===== $f ====="
cat -n "$f"
echo
doneRepository: RationAI/quality-control
Length of output: 452
🌐 Web query:
rationai-staining ColorConversion conv_type value ConversionType
💡 Result:
Based on the available documentation for the RationAI project (specifically the ratiopath library), the term ColorConversion and the parameter conv_type are not standard identifiers within the core RationAI staining API [1][2]. While RationAI provides tools for digital pathology stain conversions—such as handling stain matrices and augmentation [1][2]—the specific key-value pair structure conv_type: ConversionType appears to be characteristic of unrelated hardware or generic computer vision SDKs, such as the SIMA.ai CVU ColorConvert graph, which explicitly defines a conv_type parameter to specify color conversion operations (e.g., BGR to RGB, RGB to Gray) [3]. If you are encountering this parameter in a digital pathology workflow, it is likely part of a lower-level hardware abstraction layer or an external library integrated into the processing pipeline rather than the RationAI staining library itself. To determine the correct ConversionType values, you should consult the specific API documentation of the hardware acceleration layer or the imaging library (such as SIMA or a similar vision framework) being utilized in your code [3].
Citations:
- 1: https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/standard-conversions/
- 2: https://context7.com/rationai/ratiopath/llms.txt
- 3: https://docs.sima.ai/pages/cvu/cvu_graphs/cvu_graph_0_sima_colorconvert.html
🌐 Web query:
site:rationai.gitlab-pages.ics.muni.cz staining ColorConversion
💡 Result:
In the context of digital pathology and image processing, staining color conversion refers to the computational transformation of image data between different color spaces, specifically between standard RGB (Red, Green, Blue) color space and a stain-specific color space [1]. This process is critical for analyzing histological slides, where tissues are stained with specific dyes (such as Hematoxylin and Eosin, or H&E) to highlight different biological structures. Because digital images are typically captured in RGB, researchers use color conversion algorithms to: 1. Deconvolve or separate the signals of individual stains (Stain Deconvolution): This allows for the independent analysis of each stain component, which is essential for quantifying tissue properties or identifying specific cell types [1]. 2. Normalize staining variations (Stain Normalization): Because staining intensity can vary between labs and protocols, color conversion is used to map an image to a "standard" stain appearance, ensuring consistency for downstream machine learning or computer vision tasks [1]. These operations are often provided as specialized software libraries designed for digital pathology workflows, which facilitate the mathematical mapping between raw pixel values in RGB and the underlying stain density concentrations [1].
Citations:
🌐 Web query:
site:rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining ConversionType
💡 Result:
In the RationAI Staining docs there doesn’t appear to be a ConversionType. The API uses ConversionDirection to specify the direction of a ColorConversion, with the two directions shown in the docs/source:
ConversionDirection.RGB2STAINConversionDirection.STAIN2RGB(and you can useconversion.inverseto flip direction) [1]
Source searched: the Staining “Color Conversion” API page (class ColorConversion) [1].
[1] https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/color-conversion/
🌐 Web query:
site:rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining ColorConversion
💡 Result:
ColorConversion (in the rationai.staining library) is a class representing a color conversion between stain space and RGB space. It stores a conversion matrix and conversion direction, exposes:
matrix(numpy array form of the conversion matrix)direction(conversion direction)inverse(returns the inverse conversion, using an inverted matrix and inverted direction)from_stain_vectors(stain1, stain2, stain3=None, conversion_direction=RGB2STAIN)(builds a conversion from stain vectors assumed to be in stain space; ifstain3isn’t provided it’s computed as a residual orthogonal vector; returns either STAIN→RGB or RGB→STAIN depending onconversion_direction). [1]
It’s used, for example, by AugmentStainingTransform to convert RGB→stain space, apply augmentation in stain space, then convert back. [2]
Sources: [1] (rationai.gitlab-pages.ics.muni.cz), [2] (rationai.gitlab-pages.ics.muni.cz)
Citations:
- 1: https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/color-conversion/
- 2: https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/transforms/augment_staining/
🌐 Web query:
site:rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining "conv_type"
💡 Result:
No matches for the string conv_type appear in the RationAI Staining documentation under .../digital-pathology/libraries/staining/ (it’s not referenced in the API pages that define the color/stain conversion interfaces).
Instead of a conv_type parameter, the API uses a conversion: ColorConversion object (e.g., convert_color(tile, conversion, ...)) to control what conversion is performed. [1] The stain-normalization API similarly takes explicit stain matrices/vectors (e.g., rgb2stain, target_stain1, target_stain2, …), not a conv_type selector. [2]
Sources:
[1] (rationai.gitlab-pages.ics.muni.cz)
[2] (rationai.gitlab-pages.ics.muni.cz)
Citations:
- 1: https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/convert-color/
- 2: https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/normalize-staining/
🌐 Web query:
site:rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining "ConversionType"
💡 Result:
No matches for ConversionType exist in the Staining library documentation on rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining.
The API uses ConversionDirection (e.g., RGB2STAIN, STAIN2RGB) as the type controlling direction, including the ColorConversion constructor argument conversion_direction: ConversionDirection and related logic. [1]
Sources:
[1] (rationai.gitlab-pages.ics.muni.cz)
Citations:
- 1: https://rationai.gitlab-pages.ics.muni.cz/digital-pathology/libraries/staining/api/color-conversion/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Repo-wide usage of ConversionType =="
rg -n --hidden --no-ignore-vcs "ConversionType" rationai/qc || true
echo
echo "== Repo-wide usage of .conv_type =="
rg -n --hidden --no-ignore-vcs "\.conv_type\b" rationai/qc || true
echo
echo "== Repo-wide usage of .value\b =="
rg -n --hidden --no-ignore-vcs "\.value\b" rationai/qc || trueRepository: RationAI/quality-control
Length of output: 597
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --hidden --no-ignore-vcs "ConversionDirection" rationai/qc || true
rg -n --hidden --no-ignore-vcs "\.direction\b" rationai/qc || true
rg -n --hidden --no-ignore-vcs "\.matrix\b" rationai/qc || trueRepository: RationAI/quality-control
Length of output: 50
Align rationai/qc/staining/color_difference.py with rationai-staining’s current ColorConversion API (lines 3, 16-19)
rationai-stainingexportsConversionDirection(e.g.,RGB2STAIN/STAIN2RGB), notConversionType—sofrom rationai.staining import ... ConversionTypeshould be replaced.ColorConversionusesdirection(andmatrix/inverse), notconv_typeorvalue—soconversion.conv_typeandconversion.value[...]should be updated to the documented fields.
🧰 Tools
🪛 GitHub Actions: Python Lint (RationAI Standard) / 1_run _ MyPy.txt
[error] 3-3: mypy: Module "rationai.staining" has no attribute "ConversionType" [attr-defined]
🪛 GitHub Actions: Python Lint (RationAI Standard) / run _ MyPy
[error] 3-3: mypy: Module "rationai.staining" has no attribute "ConversionType" [attr-defined]
🤖 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 `@rationai/qc/staining/color_difference.py` at line 3, Replace the incorrect
import and field accesses to match the updated rationai-staining API: change the
import in this module to import ConversionDirection (not ConversionType)
alongside ColorConversion, and update all uses of conversion.conv_type to
conversion.direction and conversion.value[...] to use conversion.matrix or
conversion.inverse as appropriate; specifically inspect code paths that expect
RGB2STAIN/STAIN2RGB and switch logic to read conversion.direction, and pull the
transformation arrays from conversion.matrix (or the inverse matrix from
conversion.inverse) instead of conversion.value.
Source: Pipeline failures
Summary
This PR migrates the CI/CD pipeline from GitLab to GitHub and applies necessary
linting fixes to ensure the merge pipeline passes. It also includes AI
review-suggested fixes for pre-existing code in
master(suggestions originate from #2).Note
This PR is a part of a two-stage PR based on #2. While there might be some issues in this PR, they should be fixed after the PR #5 is merged. The original PR was split in order to be more "review-friendly."
Changes
CI/CD Migration
Build System & Config
pdmtouvDocumentation
docs/getting-started/quality-control.mdLinting & AI Review Fixes (Existing Code)
ruffin the existing code_get_threshold()infolding/folding.pyto handle empty masks (according to AI review in refactor: residual coverage, uv, and migration #2)Summary by CodeRabbit
Documentation
Chores