Skip to content

Fix dim Xenium morphology tiles and improve OME-TIFF loading - #294

Open
jaspreetishar wants to merge 23 commits into
mainfrom
img_intensity_fix
Open

Fix dim Xenium morphology tiles and improve OME-TIFF loading#294
jaspreetishar wants to merge 23 commits into
mainfrom
img_intensity_fix

Conversation

@jaspreetishar

@jaspreetishar jaspreetishar commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Fix dim Xenium morphology tiles and improve OME-TIFF handling

  1. Newer Xenium morphology images have lower raw intensity values (~10× lower than older datasets), which causes tiles to appear dim when displayed directly.
  • Apply per-channel intensity windowing using a high-percentile upper bound (default: 99)
    • upper_percentile argument is now added to dega.pre.main()
  • Clip extreme bright pixels before display scaling
  • Normalize each channel independently for more stable visualization
  • Add optional gamma support for channel tone adjustment (default: 1)
  • Control channel brightness with a capped 8-bit white level (default: 40)
    • Instead of stretching the normalized signal to the full 0–255 display range, we cap it at 40. This intentionally compresses brightness, preventing channels from appearing blown out.
  • Keep non-DAPI scaling neutral by default to avoid over-bright composites
  1. Read Xenium OME-TIFF channels individually using tifffile to avoid loading the full image stack into memory.

  2. Detect morphology images using the *0000*.ome.tif pattern to support newer Xenium image naming.

** successfully tested the code changes on both private and public Xenium data.

Comment thread src/celldega/pre/trx_tile.py Outdated
Comment thread src/celldega/pre/boundary_tile.py Outdated
@jaspreetishar
jaspreetishar requested a review from huanlity March 13, 2026 20:59
Comment thread src/celldega/pre/__init__.py
Comment thread src/celldega/pre/__init__.py
Comment thread src/celldega/pre/__init__.py
Comment thread src/celldega/pre/__init__.py Outdated

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

Thanks for working on this and making the ome-tif reading part more efficiently, these are super helpful. Please see my individual comments above and let me know if you have questions.

…ng hi calculation; tested use_row_groups=True without None in tile_dict.get((tile_i, tile_j))
@jaspreetishar

Copy link
Copy Markdown
Contributor Author

Thank you for the helpful review, @huanlity! I have addressed your questions and implemented your suggestions. Please let me know your thoughts.

@jaspreetishar
jaspreetishar requested a review from huanlity March 17, 2026 15:36

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

I tested the preprocess notebook and everything worked as expected. Great work! I approved the PR but maybe we can wait for Nick to take a look when he is back before merging.

@jaspreetishar

Copy link
Copy Markdown
Contributor Author

I tested the preprocess notebook and everything worked as expected. Great work! I approved the PR but maybe we can wait for Nick to take a look when he is back before merging.

Yes, that sounds good to me - thank you for your review!

Copilot AI 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.

Pull request overview

This PR adjusts Xenium morphology tile generation to avoid dim rendering on newer datasets by adding per-channel intensity windowing and memory-friendlier OME-TIFF channel loading, while threading new display parameters through the preprocessing entrypoint.

Changes:

  • Add per-channel windowing and 8-bit display scaling controls (upper_percentile, white_level) for morphology tile generation.
  • Update Xenium morphology discovery/loading to find *0000*.ome.tif and read channels individually via tifffile.
  • Minor refactors/formatting in row-group-related tests and tile row-group ordering helpers.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/celldega/pre/__init__.py Implements per-channel windowing/gamma/white-level scaling and switches Xenium OME-TIFF loading to per-channel reads.
src/celldega/pre/run_pre_processing.py Exposes upper_percentile/white_level in main() and forwards them into image tiling.
src/celldega/pre/trx_tile.py Small cleanup in tile dict access when ordering tiles for row groups.
src/celldega/pre/boundary_tile.py Small cleanup in tile dict access when ordering tiles for row groups.
tests/unit/test_pre/test_row_groups.py Formatting/IO changes in row-group unit tests.
tests/unit/test_pre/test_chromium.py Adds a stub module injection to allow importing celldega.pre in Chromium unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/celldega/pre/run_pre_processing.py
Comment thread tests/unit/test_pre/test_chromium.py
Comment thread tests/unit/test_pre/test_row_groups.py
Comment thread src/celldega/pre/__init__.py
Comment thread src/celldega/pre/__init__.py Outdated
Comment thread src/celldega/pre/__init__.py
jaspreetishar and others added 3 commits March 18, 2026 14:08
copilot suggested fix

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@cornhundred

Copy link
Copy Markdown
Collaborator

Image intensity normalization for Xenium tiles

Reworks Xenium morphology image tiling to use per-channel intensity windowing
(à la Xenium Explorer) instead of a fixed brightness multiplier.

Changes

  • _process_image_channel now clips each channel at a configurable upper_percentile,
    normalizes, and maps to an 8-bit display range scaled by white_level.
  • New tunable kwargs (backward-compatible, sensible defaults):
    • create_image_tiles(..., upper_percentile=99, white_level=100)
    • create_image_tiles_xenium(..., upper_percentile=99, white_level=100)
    • run_pre_processing.main(..., upper_percentile=99, white_level=100)
  • Reads one channel at a time from the OME-TIFF (series.asarray(key=...)) to avoid
    loading the full multi-channel image into memory.
  • Minor: dict.get(key, None)dict.get(key) in boundary_tile/trx_tile.

Merge note (main → branch)

Resolved conflicts from the DEGA-487 cluster-collection merge by combining both sides:

  • Kept this branch's intensity logic.
  • Adopted main's resolve_xenium_morphology_ome_path() for locating the morphology
    OME-TIFF (handles newer/Atera filenames + morphology.ome.tif fallback), replacing the
    hardcoded *0000*.ome.tif glob.
  • Adopted main's resolver-based _check_required_files.
  • Renamed the internal path_landscape_files param to path_dega_files for consistency
    (the old name was undefined in run_pre_processing.main).

⚠️ Behavior change

Non-DAPI channels no longer receive the previous scale boost; brightness is now
controlled by upper_percentile/white_level. Regenerated tiles will look different.

Testing

  • ruff check / ruff format clean
  • pytest tests/unit/test_pre/ — all passing

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.

4 participants