From 5548a78d586ad978f323b8a7bc3027f1b1baae13 Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Thu, 22 Jan 2026 12:46:53 -0800 Subject: [PATCH 1/7] Implement percentage-based threshold for starch detection Convert threshold from absolute value to percentage of actual pixel range. User inputs 0-255 (e.g., 172 = 67.45%), applied as: low + (high - low) * percentage. Provides consistent detection across varying lighting conditions. --- Granny/Analyses/StarchArea.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Granny/Analyses/StarchArea.py b/Granny/Analyses/StarchArea.py index 471c6e3..b476a7c 100644 --- a/Granny/Analyses/StarchArea.py +++ b/Granny/Analyses/StarchArea.py @@ -118,9 +118,11 @@ def __init__(self): self.starch_threshold = IntValue( "starch_threshold", "starch_threshold", - "Threshold value for starch detection. Pixels with gray values <= this threshold " - + "are considered starch. Lower values detect only darker starch regions, higher " - + "values include lighter regions. Range is 0 to 255, default is 172.", + "Threshold value for starch detection (0-255 range). This value is converted to a " + + "percentage (value/255) and applied to each image's actual pixel range. " + + "Pixels with gray values <= threshold percentage are considered starch. " + + "Lower values detect only darker starch regions, higher values include lighter regions. " + + "Default is 172 (67.45% of range).", ) self.starch_threshold.setMin(0) self.starch_threshold.setMax(255) @@ -198,10 +200,12 @@ def _calculateStarch(self, img: NDArray[np.uint8]) -> Tuple[float, NDArray[np.ui Calculates the starch content in the given image and return the modified image. This function processes the input image to calculate the starch content. The process - involves blurring the image to remove noise, converting it to grayscale, adjusting - its intensity values, and creating a binary thresholded image to identify the starch - regions. The ratio of starch pixels to the total pixels in the ground truth is - returned along with the modified image. + involves blurring the image to remove noise, converting it to grayscale, extracting + the actual pixel range (min/max), and applying a percentage-based threshold to identify + starch regions. The threshold value (0-255) is converted to a percentage and applied + to each image's actual pixel range, ensuring consistent starch detection across images + with different lighting conditions. The ratio of starch pixels to the total pixels in + the ground truth is returned along with the modified image. Args: img (NDArray[np.uint8]): The input image as a NumPy array of type np.uint8. @@ -244,13 +248,21 @@ def adjustImage(img: NDArray[np.uint8], lIn: int, hIn: int, lOut: int = 0, hOut: img = cast(NDArray[np.uint8], cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)) gray = cast(NDArray[np.uint8], cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) - # re-adjusts the image to [0 255] + # extract actual min/max pixel values from the image low, high = extractImage(gray) - gray = adjustImage(gray, low, high) - # create thresholded matrices + # calculate percentage-based threshold + # User inputs threshold in 0-255 range (e.g., 172) + # Convert to percentage and apply to actual image range image_threshold = self.starch_threshold.getValue() - mask = np.logical_and((gray > 0), (gray <= image_threshold)).astype(np.uint8) + threshold_percentage = image_threshold / 255.0 + threshold_value = low + (high - low) * threshold_percentage + + # create thresholded matrices using percentage-based threshold on original range + mask = np.logical_and((gray > 0), (gray <= threshold_value)).astype(np.uint8) + + # normalize image to [0, 255] for visualization only + gray_normalized = adjustImage(gray, low, high) # creates new image using threshold matrices new_img = self._drawMask(new_img, mask) From 8a6651a6364088e12cdd059aa1d5b171817e4222 Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Thu, 12 Feb 2026 13:00:21 -0800 Subject: [PATCH 2/7] Add 4 new MSU starch scale varieties (EMPIRE, FUJI, IDARED, JONATHAN) All 4 varieties have monotonically decreasing ratings and were calibrated from MSU reference images using threshold 172. --- Granny/assets/starch_scales.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Granny/assets/starch_scales.yml b/Granny/assets/starch_scales.yml index cc271a1..ce60595 100644 --- a/Granny/assets/starch_scales.yml +++ b/Granny/assets/starch_scales.yml @@ -94,3 +94,23 @@ ROYAL_GALA: # Note: Minor non-monotonic at index 3.0 index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] rating: [0.910532554, 0.839360955, 0.928483858, 0.863534479, 0.774230735, 0.737439732, 0.568495148, 0.480588864, 0.325108538] + +EMPIRE: + # MSU starch scale (2026-02-10) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] + rating: [0.988115593, 0.974479220, 0.876777237, 0.871529845, 0.859762245, 0.845581581, 0.707960720] + +FUJI: + # MSU starch scale (2026-02-10) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.986821463, 0.972244952, 0.960990693, 0.903095521, 0.837198998, 0.750632730, 0.578647255, 0.450858254] + +IDARED: + # MSU starch scale (2026-02-10) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] + rating: [0.989654473, 0.984797736, 0.933599956, 0.897179091, 0.806548432, 0.781328571, 0.572557496] + +JONATHAN: + # MSU starch scale (2026-02-10) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.990524245, 0.990540032, 0.976034191, 0.916475538, 0.857157939, 0.572915018, 0.321327449, 0.111317418] From cf74199d2900142d84f474bd4608e786f3285e8e Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Thu, 12 Feb 2026 14:12:32 -0800 Subject: [PATCH 3/7] Change default starch threshold from 172 to 140 (55%) Testing on 15 MSU starch reference varieties showed that 55% threshold achieves perfect monotonicity (15/15 varieties) compared to 67.45% which had 8/15 monotonic with 9 violations. The percentage-based threshold adapts to each image's actual pixel range, providing more consistent results across varying lighting conditions. --- Granny/Analyses/StarchArea.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Granny/Analyses/StarchArea.py b/Granny/Analyses/StarchArea.py index b476a7c..b8fe588 100644 --- a/Granny/Analyses/StarchArea.py +++ b/Granny/Analyses/StarchArea.py @@ -122,11 +122,11 @@ def __init__(self): + "percentage (value/255) and applied to each image's actual pixel range. " + "Pixels with gray values <= threshold percentage are considered starch. " + "Lower values detect only darker starch regions, higher values include lighter regions. " - + "Default is 172 (67.45% of range).", + + "Default is 140 (55% of range).", ) self.starch_threshold.setMin(0) self.starch_threshold.setMax(255) - self.starch_threshold.setValue(172) + self.starch_threshold.setValue(140) self.starch_threshold.setIsRequired(False) # Gaussian blur kernel size parameter From 8e6edbb820f18726c95f9a293e45caa63b7c4d76 Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Thu, 12 Feb 2026 14:51:48 -0800 Subject: [PATCH 4/7] Update starch scales with 55% threshold calibration data - Updated 12 MSU varieties with new 55% threshold data achieving 15/15 monotonicity with 0 violations - Added 5 new varieties: HONEYCRISP, BRAEBURN, EVERCRISP, GALA, ROME - All MSU varieties now use consistent integer index scales (1-8) - Kept non-MSU varieties unchanged (HONEY_CRISP, WA38, ALLAN_BROS, CORNELL, PURDUE, DANJOU, MINNEISKA, ROYAL_GALA) --- Granny/assets/starch_scales.yml | 93 +++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/Granny/assets/starch_scales.yml b/Granny/assets/starch_scales.yml index ce60595..9fecfee 100644 --- a/Granny/assets/starch_scales.yml +++ b/Granny/assets/starch_scales.yml @@ -10,6 +10,8 @@ # rating: [corresponding rating values] # # Each variety has its own starch scale based on research and industry standards. +# +# MSU varieties calibrated with 55% threshold (2026-02-12) HONEY_CRISP: index: [1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0] @@ -28,18 +30,19 @@ ALLAN_BROS: rating: [0.997783524, 0.988769830, 0.951909478, 0.877526853, 0.721066082, 0.673838851, 0.417864608, 0.091652858] GOLDEN_DELICIOUS: - index: [1.0, 1.2, 1.5, 1.8, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 6.0] - rating: [0.998544220, 0.981819854, 0.974722333, 0.902015343, 0.893566670, 0.784215902, 0.780621478, 0.607040963, 0.717128225, 0.485321449, 0.279959478, 0.068212979] + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.983347032, 0.978499607, 0.956362227, 0.879515383, 0.782063377, 0.569627467, 0.293948219, 0.122036979] GRANNY_SMITH: - index: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - rating: [0.920742836, 0.890332499, 0.808227909, 0.721813109, 0.595806394, 0.278299256, 0.104111379] + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] + rating: [0.928685026, 0.899479649, 0.822260611, 0.727163318, 0.620976841, 0.444128086, 0.294305672] JONAGOLD: - # Updated with transparent background images (2026-01-22) - # Note: Minor non-monotonic at indices 7.0 and 9.0 - index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] - rating: [0.904449699, 0.874103352, 0.836673554, 0.838637605, 0.784962049, 0.594890812, 0.701861145, 0.443807162, 0.596292231] + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.979482831, 0.968446545, 0.931265757, 0.802088459, 0.642265245, 0.318271857, 0.180840708, 0.063420488] CORNELL: index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] @@ -48,69 +51,79 @@ CORNELL: PURDUE: index: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] rating: [0.942025840, 0.858846162, 0.767229201, 0.564366621, 0.484868413, 0.266928900, 0.074313346] - # Threshold 150 alternative (for comparison): - # index: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - # rating: [0.904324121, 0.755849937, 0.675973804, 0.411188084, 0.330359199, 0.160435085, 0.058093191] DANJOU: - # Updated with transparent background images (2026-01-22) # Note: Minor non-monotonic at index 3.5 index: [1.2, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0] rating: [0.936809998, 0.922681876, 0.872891694, 0.751986331, 0.632509641, 0.675894367, 0.385525954, 0.269095698] AMBROSIA: - # Updated with transparent background images (2026-01-22) - # Ratings averaged across A/B/C variants for each index + # MSU starch scale - 55% threshold (2026-02-12) index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] - rating: [0.938836999, 0.918713937, 0.899148039, 0.800415449, 0.743251611, 0.546193038, 0.434137897, 0.190703881] + rating: [0.971872720, 0.962896555, 0.908653906, 0.802704025, 0.690480695, 0.482492036, 0.341191274, 0.075346564] MINNEISKA: - # Updated with transparent background images (2026-01-22) # WARNING: Still non-monotonic - source images have quality issues # Index 6.0 missing (reference card says "apple clear of starch") index: [1.0, 2.0, 3.0, 4.0, 5.0] rating: [0.941631104, 0.906756575, 0.579645190, 0.643510650, 0.441163061] PINKLADY: - # Updated with transparent background images (2026-01-22) - # Excellent monotonic decrease - index: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - rating: [0.959550159, 0.699509917, 0.614671054, 0.502103609, 0.491582825, 0.311998962, 0.147608409] - -REDDELICIOUS1980: - # Updated with transparent background images (2026-01-22) - # Excellent monotonic decrease - index: [1.2, 1.5, 1.8, 2.0, 2.2, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 6.0] - rating: [0.930329713, 0.915044820, 0.856309558, 0.853819411, 0.792795414, 0.761423428, 0.711408319, 0.619893048, 0.495924193, 0.480951636, 0.431083558, 0.203781176] + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] + rating: [0.982401785, 0.963264866, 0.932712253, 0.769275561, 0.656903113, 0.478645492, 0.326187668] -REDDELICIOUS1990: - # Updated with transparent background images (2026-01-22) - # Excellent monotonic decrease - index: [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] - rating: [0.969424567, 0.945761350, 0.935346949, 0.880456871, 0.816953597, 0.736772334, 0.669650642, 0.579715970, 0.442378759, 0.320778000, 0.203632175, 0.181581181] +REDDELICIOUS: + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.970333145, 0.927816076, 0.859277333, 0.713880479, 0.506900655, 0.459462246, 0.268753816, 0.163449196] ROYAL_GALA: - # Updated with transparent background images (2026-01-22) # Note: Minor non-monotonic at index 3.0 index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] rating: [0.910532554, 0.839360955, 0.928483858, 0.863534479, 0.774230735, 0.737439732, 0.568495148, 0.480588864, 0.325108538] EMPIRE: - # MSU starch scale (2026-02-10) + # MSU starch scale - 55% threshold (2026-02-12) index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] - rating: [0.988115593, 0.974479220, 0.876777237, 0.871529845, 0.859762245, 0.845581581, 0.707960720] + rating: [0.970695189, 0.948662668, 0.791846554, 0.779420670, 0.745290611, 0.663642314, 0.460208582] FUJI: - # MSU starch scale (2026-02-10) + # MSU starch scale - 55% threshold (2026-02-12) index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] - rating: [0.986821463, 0.972244952, 0.960990693, 0.903095521, 0.837198998, 0.750632730, 0.578647255, 0.450858254] + rating: [0.974763241, 0.901142588, 0.845687779, 0.805437350, 0.717441404, 0.539812629, 0.333291895, 0.168391585] IDARED: - # MSU starch scale (2026-02-10) + # MSU starch scale - 55% threshold (2026-02-12) index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] - rating: [0.989654473, 0.984797736, 0.933599956, 0.897179091, 0.806548432, 0.781328571, 0.572557496] + rating: [0.982588090, 0.955356105, 0.874148553, 0.813374837, 0.657680211, 0.612489305, 0.332182826] JONATHAN: - # MSU starch scale (2026-02-10) + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.979482831, 0.968446545, 0.931266065, 0.802088459, 0.642265245, 0.318271857, 0.180828569, 0.063420488] + +HONEYCRISP: + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.981640465, 0.978403947, 0.937568685, 0.799603933, 0.620809574, 0.469053198, 0.211703838, 0.075295890] + +BRAEBURN: + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.975845161, 0.865763207, 0.770965694, 0.691933910, 0.675968067, 0.513611895, 0.322461173, 0.156570377] + +EVERCRISP: + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.985204666, 0.942957942, 0.921500522, 0.808915231, 0.680752183, 0.667710727, 0.329598534, 0.089511665] + +GALA: + # MSU starch scale - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + rating: [0.985676118, 0.900733517, 0.890346985, 0.643454695, 0.534702139, 0.530157722, 0.296498379, 0.125968575] + +ROME: + # MSU starch scale - 55% threshold (2026-02-12) index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] - rating: [0.990524245, 0.990540032, 0.976034191, 0.916475538, 0.857157939, 0.572915018, 0.321327449, 0.111317418] + rating: [0.952191921, 0.903481677, 0.776124925, 0.769690669, 0.606057588, 0.411963190, 0.238755173, 0.127008793] From 90171a3fbdb3eb552b5865d36291f922c0230b5e Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Wed, 25 Feb 2026 12:31:12 -0800 Subject: [PATCH 5/7] Rename starch calculation variables for clarity, remove duplicate HONEY_CRISP StarchArea.py: - extractImage() -> getPixelRange() - adjustImage() -> remapToRange() - gray -> grayscale - Removed unused grayscale_normalized variable starch_scales.yml: - Removed duplicate HONEY_CRISP entry (HONEYCRISP with 55% calibration is kept) --- Granny/Analyses/StarchArea.py | 14 +++++++------- Granny/assets/starch_scales.yml | 4 ---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Granny/Analyses/StarchArea.py b/Granny/Analyses/StarchArea.py index b8fe588..e391303 100644 --- a/Granny/Analyses/StarchArea.py +++ b/Granny/Analyses/StarchArea.py @@ -216,16 +216,16 @@ def _calculateStarch(self, img: NDArray[np.uint8]) -> Tuple[float, NDArray[np.ui - NDArray[np.uint8]: The modified image with identified starch regions. """ - def extractImage(img: NDArray[np.uint8]) -> Tuple[int, int]: + def getPixelRange(img: NDArray[np.uint8]) -> Tuple[int, int]: """ Extracts minimum and maximum pixel value of an image """ - hist, _ = np.histogram(gray, bins=256, range=(0, 255)) + hist, _ = np.histogram(grayscale, bins=256, range=(0, 255)) low = (hist != 0).argmax() high = 255 - (hist[::-1] != 0).argmax() return low, high - def adjustImage(img: NDArray[np.uint8], lIn: int, hIn: int, lOut: int = 0, hOut: int = 255): + def remapToRange(img: NDArray[np.uint8], lIn: int, hIn: int, lOut: int = 0, hOut: int = 255): """ Adjusts the intensity values of an image I to new values. This function is equivalent to normalize the image pixel values to [0, 255]. @@ -246,10 +246,10 @@ def adjustImage(img: NDArray[np.uint8], lIn: int, hIn: int, lOut: int = 0, hOut: # blurs the image to remove sharp noises, then converts it to gray scale kernel_size = self.blur_kernel.getValue() img = cast(NDArray[np.uint8], cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)) - gray = cast(NDArray[np.uint8], cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) + grayscale = cast(NDArray[np.uint8], cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) # extract actual min/max pixel values from the image - low, high = extractImage(gray) + low, high = getPixelRange(grayscale) # calculate percentage-based threshold # User inputs threshold in 0-255 range (e.g., 172) @@ -259,10 +259,10 @@ def adjustImage(img: NDArray[np.uint8], lIn: int, hIn: int, lOut: int = 0, hOut: threshold_value = low + (high - low) * threshold_percentage # create thresholded matrices using percentage-based threshold on original range - mask = np.logical_and((gray > 0), (gray <= threshold_value)).astype(np.uint8) + mask = np.logical_and((grayscale > 0), (grayscale <= threshold_value)).astype(np.uint8) # normalize image to [0, 255] for visualization only - gray_normalized = adjustImage(gray, low, high) + grayscale_normalized = remapToRange(grayscale, low, high) # creates new image using threshold matrices new_img = self._drawMask(new_img, mask) diff --git a/Granny/assets/starch_scales.yml b/Granny/assets/starch_scales.yml index 9fecfee..20dc015 100644 --- a/Granny/assets/starch_scales.yml +++ b/Granny/assets/starch_scales.yml @@ -13,10 +13,6 @@ # # MSU varieties calibrated with 55% threshold (2026-02-12) -HONEY_CRISP: - index: [1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0] - rating: [0.998998748, 0.947464712, 0.868898986, 0.783941273, 0.676589664, 0.329929925, 0.024131710] - WA38_1: index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] rating: [0.893993948, 0.855859903, 0.757963861, 0.597765822, 0.164192649, 0.080528335] From 16edc46b1b84c96b7399e428b05c9e7a14843fe0 Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Wed, 25 Feb 2026 13:01:43 -0800 Subject: [PATCH 6/7] Remove unused nested functions, inline histogram logic in _calculateStarch - Remove getPixelRange nested function and inline its histogram logic - Remove remapToRange function (dead code - was only used for visualization) - Remove unused grayscale_normalized variable - Clean up comments --- Granny/Analyses/StarchArea.py | 45 +++++++---------------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/Granny/Analyses/StarchArea.py b/Granny/Analyses/StarchArea.py index e391303..d25a28a 100644 --- a/Granny/Analyses/StarchArea.py +++ b/Granny/Analyses/StarchArea.py @@ -215,56 +215,29 @@ def _calculateStarch(self, img: NDArray[np.uint8]) -> Tuple[float, NDArray[np.ui - float: The ratio of starch pixels to total pixels in the ground truth. - NDArray[np.uint8]: The modified image with identified starch regions. """ - - def getPixelRange(img: NDArray[np.uint8]) -> Tuple[int, int]: - """ - Extracts minimum and maximum pixel value of an image - """ - hist, _ = np.histogram(grayscale, bins=256, range=(0, 255)) - low = (hist != 0).argmax() - high = 255 - (hist[::-1] != 0).argmax() - return low, high - - def remapToRange(img: NDArray[np.uint8], lIn: int, hIn: int, lOut: int = 0, hOut: int = 255): - """ - Adjusts the intensity values of an image I to new values. This function is equivalent - to normalize the image pixel values to [0, 255]. - """ - # Ensure img is in the range [lIn, hIn] - img = np.clip(img, lIn, hIn) - - # Normalize the image to the range [0, 1] - out = (img - lIn) / (hIn - lIn) - - # Scale and shift the normalized image to the range [lOut, hOut] - out = out * (hOut - lOut) + lOut - - return out.astype(np.uint8) - new_img = img.copy() - # blurs the image to remove sharp noises, then converts it to gray scale + # Blur the image to remove sharp noises, then convert to grayscale kernel_size = self.blur_kernel.getValue() img = cast(NDArray[np.uint8], cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)) grayscale = cast(NDArray[np.uint8], cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) - # extract actual min/max pixel values from the image - low, high = getPixelRange(grayscale) + # Get actual min/max pixel values from histogram + hist, _ = np.histogram(grayscale, bins=256, range=(0, 255)) + low = (hist != 0).argmax() + high = 255 - (hist[::-1] != 0).argmax() - # calculate percentage-based threshold - # User inputs threshold in 0-255 range (e.g., 172) + # Calculate percentage-based threshold + # User inputs threshold in 0-255 range (e.g., 140) # Convert to percentage and apply to actual image range image_threshold = self.starch_threshold.getValue() threshold_percentage = image_threshold / 255.0 threshold_value = low + (high - low) * threshold_percentage - # create thresholded matrices using percentage-based threshold on original range + # Create thresholded mask using percentage-based threshold on original range mask = np.logical_and((grayscale > 0), (grayscale <= threshold_value)).astype(np.uint8) - # normalize image to [0, 255] for visualization only - grayscale_normalized = remapToRange(grayscale, low, high) - - # creates new image using threshold matrices + # Apply mask overlay to image new_img = self._drawMask(new_img, mask) ground_truth = np.count_nonzero( From e4f78148d83b5922f5329cfd55a99e249f16ff64 Mon Sep 17 00:00:00 2001 From: AdenAthar Date: Wed, 25 Feb 2026 14:29:07 -0800 Subject: [PATCH 7/7] Update starch scales for Cornell, Purdue, Danjou, WA38 with 55% threshold - CORNELL: Updated ratings from new calibration - PURDUE: Updated ratings from new calibration - DANJOU: Updated ratings, interpolated 1.5 and 3.5 for monotonicity, added 6.0 - WA38: Added new entry with averaged A/B sample ratings --- Granny/assets/starch_scales.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Granny/assets/starch_scales.yml b/Granny/assets/starch_scales.yml index 20dc015..08b9732 100644 --- a/Granny/assets/starch_scales.yml +++ b/Granny/assets/starch_scales.yml @@ -21,6 +21,11 @@ WA38_2: index: [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0] rating: [0.950925926, 0.912917454, 0.839858059, 0.749211356, 0.770660718, 0.634160550, 0.571832210, 0.522944438, 0.178909419, 0.017493382, 0.075675075] +WA38: + # Calibrated with 55% threshold (2026-02-25) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] + rating: [0.980459235, 0.946203334, 0.811620149, 0.650315140, 0.248849024, 0.101848725] + ALLAN_BROS: index: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] rating: [0.997783524, 0.988769830, 0.951909478, 0.877526853, 0.721066082, 0.673838851, 0.417864608, 0.091652858] @@ -41,17 +46,20 @@ JONAGOLD: rating: [0.979482831, 0.968446545, 0.931265757, 0.802088459, 0.642265245, 0.318271857, 0.180840708, 0.063420488] CORNELL: + # Calibrated with 55% threshold (2026-02-25) index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] - rating: [0.990554095, 0.915430492, 0.822470328, 0.726896529, 0.610745795, 0.338955981, 0.150869695, 0.041547982] + rating: [0.950814279, 0.859641067, 0.814814815, 0.646408007, 0.537191447, 0.323038716, 0.226249041, 0.141040656] PURDUE: + # Calibrated with 55% threshold (2026-02-25) index: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - rating: [0.942025840, 0.858846162, 0.767229201, 0.564366621, 0.484868413, 0.266928900, 0.074313346] + rating: [0.881028019, 0.658026584, 0.598684959, 0.313197178, 0.235314219, 0.115934565, 0.044768709] DANJOU: - # Note: Minor non-monotonic at index 3.5 - index: [1.2, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0] - rating: [0.936809998, 0.922681876, 0.872891694, 0.751986331, 0.632509641, 0.675894367, 0.385525954, 0.269095698] + # Calibrated with 55% threshold (2026-02-25) + # Index 1.5 and 3.5 interpolated for monotonicity + index: [1.2, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0, 6.0] + rating: [0.846337197, 0.817211435, 0.788085673, 0.556374127, 0.446657794, 0.328440397, 0.210223000, 0.145805937, 0.052457699] AMBROSIA: # MSU starch scale - 55% threshold (2026-02-12)