Skip to content

feat(channels)!: emission-band colorblind palette + source-file opt-in#58

Merged
ferrinm merged 2 commits into
mainfrom
feat/issue-51-emission-band-channel-colors
Jul 23, 2026
Merged

feat(channels)!: emission-band colorblind palette + source-file opt-in#58
ferrinm merged 2 commits into
mainfrom
feat/issue-51-emission-band-channel-colors

Conversation

@ferrinm

@ferrinm ferrinm commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replaces the fluorophore-name → true-color mapping in channel_colors.py with an emission-band colorblind palette (cyan / green / yellow / magenta / white) per the ADR-0007 Nature Methods "Points of View" recommendation. Colors are driven by what the eye sees (emission midpoint → excitation → dye-name substring → round-robin palette).
  • Adds convert(..., channel_colors="source-file") — a third accepted value that trusts the source file's stored per-channel color (LIF LUTName, OME-XML ARGB int) with fall-through to the emission-band scheme. config.channel_colors in the audit is preserved verbatim (dict / None / "source-file").
  • Handles residual collisions (two far-red dyes both landing on white) by round-robining through UNKNOWN_PALETTE skipping already-taken slots and firing a new ChannelColorCollisionWarning naming the reassigned channels.
  • NAME_COLORS / PALETTE / color_for_channel are removed (loud import-time failure over a silent shim); the acceptance criteria's DAPI/AF546/AF647 → cyan/magenta/white real-file case is pinned by test. See ADR-0007 for the resolution of the "555 nm" ambiguity (excitation, not emission).

Closes #51

Test plan

  • uv run pytest -q — 366 passed, 2 skipped (missing-extras skips only), 0 failed
  • Band-boundary edge cases (500 nm exact / 545 / 580 / 610 / 660 / 740) via test_emission_boundary_*
  • Excitation-band edges (405 / 499 / 555 / 651 / 620 / 780) via test_excitation_*
  • Collision handling: two AF647 channels → second reassigned + ChannelColorCollisionWarning fires via test_assign_colors_reallocates_collisions_and_warns and test_channels_to_omero_reallocates_collisions
  • Dye-name fallback for readers with no wavelength via test_dye_name_* and test_non_lif_reader_default_channels_use_band_scheme
  • channel_colors="source-file" end-to-end (with and without LUTName present) via test_api_source_file_mode_*
  • config.channel_colors audit round-trip preserves "source-file" via test_convert_preserves_channel_colors_verbatim_in_audit
  • Dict override wins over band scheme via test_api_dict_override_wins_over_band_scheme
  • Reporter's real-file case (DAPI 405 / Alexa 546 555 / Alexa 647 651 → cyan/magenta/white) pinned by test_dapi_af546_af647_produces_cyan_magenta_white
  • Ruff / Black / prettier hooks all pass on commit

🤖 Generated with Claude Code

…file opt-in

Replaces the fluorophore-name → true-color lookup with an emission-band
scheme (cyan/green/yellow/magenta/white — the Nature Methods "Points of
View" CMY palette) so overlays stay distinguishable under red-green
colorblindness and no two red-family dyes collide the way ALEXA 546 and
ALEXA 647 did under the old substring match. Colors are driven by what
the eye sees (emission midpoint), falling back to excitation, then
dye-name substring, then a round-robin palette; residual collisions
reallocate through UNKNOWN_PALETTE and fire ChannelColorCollisionWarning
naming the reassigned channel so users know to reach for the override
mechanism.

Adds channel_colors="source-file" as a third accepted value on
convert(): trusts the source file's stored per-channel color (LIF
LUTName, OME-XML ARGB int) where present, falling through to the
emission-band scheme where not. config.channel_colors in the audit
record preserves the literal value (dict, None, or "source-file") so
downstream tools can distinguish user intent.

NAME_COLORS / PALETTE / color_for_channel are removed rather than kept
as deprecated shims — the failure at import time is loud on purpose and
no pilot user depends on the exact old hex values. See ADR-0007 for the
full rationale, band boundaries, source-file design, and the resolution
of the "555 nm" ambiguity from the reporting session (excitation, not
emission — the excitation-fallback table's [545, 620) magenta band
covers the yellow-orange laser bucket).

Closes #51

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n-band-channel-colors

# Conflicts:
#	CHANGELOG.md
#	src/zarrmony/api.py
@ferrinm
ferrinm merged commit e91fbd8 into main Jul 23, 2026
5 checks passed
@ferrinm
ferrinm deleted the feat/issue-51-emission-band-channel-colors branch July 23, 2026 22:26
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.

feat(channels): emission-band colorblind palette as default; source-file opt-in

1 participant