Repository navigation
stain.check_options: check a conversion request without pixels (#97) - #98
Open
guijacquemet wants to merge 4 commits into
Open
guijacquemet wants to merge 4 commits into
guijacquemet wants to merge 4 commits into
Conversation
check_options(preset, *, white=None, stain_vectors=None, polarizer=None) makes, without pixels, every check to_scalar makes of the preset and these arguments: unknown preset, the presence rules (needed, unused, polarizer), white (range, shape, masked values before np.asarray) and the stain matrix (names, shape, finiteness, masked values, collinearity, conditioning; the preset's default vectors when none are given). to_scalar runs the same code first and uses its checked white and matrix, so there is one implementation; exception types, messages and outputs are unchanged. Tests call check_options with no image for every refusal, accept a request per preset, compare to_scalar's and check_options' refusals message for message, and show masked inputs are refused before np.asarray. Contract §3.4, CHANGELOG (Unreleased) and the AGENTS.md module map are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbytvF3Gsa85ix8Ne2yMDW
…eeze v0.6.0 parity (#97) Fixes the three review blockers of PR #98. B1. _check_arguments now requires a str polarizer before the membership test. np.array(["linear"]) passed `in POLARIZERS` element-wise and made orientation_valid a NumPy array; it is now refused with a ValueError naming the type (numpy.ndarray, bytes, int, list). None still gets the "needs polarizer" message. B2. optical_density._refuse_masked searches nested lists, tuples and object arrays (np.ma.masked or a masked array at any depth), so white and every stain vector (residual included) with masked content are refused as masked before np.asarray. v0.6.0 refused these later as "not three numbers" or an inhomogeneous sequence; the CHANGELOG lists this as a safety fix to the refusal-parity claim. B3. tests/data/stain_v060_parity.json, recorded by tests/stain_parity_cases.py on 95e1d6d (v0.6.0) in a temporary worktree, freezes the full exception type and message of the 40 refusals and the SHA-256 of image, exclusion and canonical provenance of 33 conversions. check_options and to_scalar must match the messages exactly; the 11 intentionally changed inputs (B1, B2) are listed with their v0.6.0 outcome and their new one. Default stain vectors are proven to go through the user-vector checks by replacing he_custom's defaults in a copy of PRESETS. The stale test_messages_are_unchanged comment is gone. test_stain_check_options.py joins the CI histology job so the scikit-image conversions run there without skips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbytvF3Gsa85ix8Ne2yMDW
CI failed the six custom_od conversions of test_outputs_are_those_of_v060 on every OS with the same digests, while they passed on the machine that recorded the table: custom_od solves a stain matrix through LAPACK, and OpenBLAS selects its kernel by CPU, so the last bits of the image and of the provenance (stain matrix, condition number) depend on the CPU, not on this code. The parity table now freezes, for custom_od only, the image values and the plain provenance, compared with a relative tolerance of 1e-9 (floats; everything else exactly), and the exclusion's digest exactly; the 27 other conversions keep their SHA-256 digests. Re-recorded on 95e1d6d in a temporary worktree: the refusals, the changed cases and the 27 digests are byte-identical to the previous table, and the six exclusion digests are unchanged. A 1e-6 relative change of a frozen image is caught. tools/check.py: 2222 passed, 2 skipped (csbdeep not installed), code rules and Ruff clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbytvF3Gsa85ix8Ne2yMDW
…tests (review M)
B1 (review M): an accepted str subclass such as np.str_ was stored as is in the provenance's
polarizer. orientation_valid came from comparing it, and the test only compared it with another
comparison result. The provenance now holds str(polarizer), and orientation_valid is
bool(...). The test asserts type(...) is str and type(...) is bool for "circular", "linear",
np.str_("circular") and np.str_("linear"). The numerical conversion is unchanged.
FILE (review M):
- custom_od parity now freezes the exclusion values as well as the image values. Both are
float32, compared with rtol 1e-6 and atol 1e-7 (a few float32 ULPs; rtol 1e-9 was effectively
bitwise for float32). Float64 provenance stays at 1e-9. Re-recorded on 95e1d6d: the refusals,
the changed cases, the 27 digests, the six images and the six provenances are identical to the
previous table.
- Tests: the masked search ends on a list or an object array that holds itself, with and without
masked content. stain_vectors_from_regions refuses a masked white before np.asarray.
tools/check.py: 2230 passed, 2 skipped (csbdeep not installed), code rules and Ruff clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbytvF3Gsa85ix8Ne2yMDW
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #97. Adds a public, pixel-free
morphobricks_normalization.stain.check_options(preset, *, white=None, stain_vectors=None, polarizer=None). It runs every checkto_scalarmakes about these arguments, so consumers can refuse an unusable request before reading an image. This closes the one known limitation of fiberfeatures v0.6.0's RGB input (review J/K item R1).What it checks, in
to_scalar's order:POLARIZERS.white: three finite values in (0, 255]. A masked array (an all-false mask included) or a sequence holdingnp.ma.maskedis refused beforenp.asarray.custom_od): names, three finite values each, norm ≥ 1e-6, masked values refused, collinear vectors (< 5°, sign-insensitive) refused, condition number > 50 refused. The default vectors go through the same checks.One implementation:
_checked_options()does the checks and returns the checked white and matrix.check_optionswraps it, andto_scalarcalls it directly to reuse the values, so each check runs once per call._custom_odand_white_rescalednow receive checked values.Parity with
main:Tests (
tests/test_stain_check_options.py, NumPy only):to_scalar, asserting identical messages;np.asarray;check_optionsruns with scikit-image blocked.Checks (
tools/check.py, Python 3.12):[dev,histology]: 2138 passed, 2 skipped (CSBDeep is installed only in its own CI job).No version change. It ships in the next normalization release, with the coordinated pin move; fiberfeatures and MorphoFeature then call it at planning.
Generated by Claude Code