Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions negpy/features/process/fade.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
#: Percentile of the analysis grid that Remove Stain reads as each channel's white.
WHITE_PCT = 99.9
#: To suggest Remove Stain, the brightest channel's white is within this density of full scale.
STAIN_FULL_SCALE_TOLERANCE = 0.1
#: To suggest Remove Stain, blue is the darkest channel at the white, by at least this density.
STAIN_BLUE_MIN = 0.2
STAIN_FULL_SCALE_TOLERANCE = 0.2
#: To suggest Remove Stain, blue's white is at least this density below both red's and green's.
STAIN_BLUE_MIN = 0.08
#: Remove Stain puts the white at least this log density below full scale, so clear areas do not clip.
FADE_WHITE_HEADROOM = 0.1

Expand Down Expand Up @@ -167,9 +167,9 @@ def white_anchored_grid(grid: ImageBuffer, white: tuple[float, float, float]) ->

def stain_suggested(white: tuple[float, float, float]) -> bool:
"""Whether the white reads as a yellow stain on clear film: the brightest channel near full
scale, and blue clearly the darkest. A dim scan with no white object never qualifies."""
brightest = max(white)
return brightest >= -STAIN_FULL_SCALE_TOLERANCE and white[2] <= min(white[0], white[1]) and brightest - white[2] >= STAIN_BLUE_MIN
scale, and blue below both red and green. A red cast leaves blue near green, and a dim scan
with no white object never qualifies."""
return max(white) >= -STAIN_FULL_SCALE_TOLERANCE and min(white[0], white[1]) - white[2] >= STAIN_BLUE_MIN


class EstimateMethod(StrEnum):
Expand Down
8 changes: 6 additions & 2 deletions tests/test_fade_white_anchor.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,14 @@ def test_the_anchored_estimate_reads_the_true_survivals_through_a_stain():
[
((-0.05, -0.18, -0.73), True), # NORAD 106-25: bright, heavy yellow stain
((-0.04, -0.16, -0.28), True), # NORAD 106-5: blue below red by more than the minimum
((-0.14, -0.12, -0.49), True), # a heavy stain on a scan placed under full scale
((-0.01, -0.04, -0.16), True), # a mild stain on a bright white
((-0.76, -1.05, -1.05), False), # SilverFast at gamma 2.2: no white near full scale
((-2.9, -3.4, -3.25), False), # a scan with no white object
((-0.25, -0.12, -0.01), False), # bluish whites: a snow or sky frame
((0.0, -0.05, -0.15), False), # a stain under the minimum
((0.0, -0.05, -0.10), False), # a stain under the minimum
((-0.09, -0.02, -0.13), False), # a red cast: blue barely below red
((-0.25, -0.30, -0.60), False), # a stain on a white too far under full scale
((0.0, -0.4, -0.3), False), # green, not blue, is darkest
],
)
Expand All @@ -261,7 +265,7 @@ def test_stain_suggestion_rule(white, expected):


def test_stain_suggestion_thresholds_are_the_documented_ones():
assert (STAIN_FULL_SCALE_TOLERANCE, STAIN_BLUE_MIN) == (0.1, 0.2)
assert (STAIN_FULL_SCALE_TOLERANCE, STAIN_BLUE_MIN) == (0.2, 0.08)


@unittest.skipUnless(GPUDevice.get().is_available, "GPU not available")
Expand Down
Loading