diff --git a/Granny/Analyses/StarchArea.py b/Granny/Analyses/StarchArea.py index 471c6e3..d25a28a 100644 --- a/Granny/Analyses/StarchArea.py +++ b/Granny/Analyses/StarchArea.py @@ -118,13 +118,15 @@ 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 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 @@ -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. @@ -211,48 +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 extractImage(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)) - 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): - """ - 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)) - gray = cast(NDArray[np.uint8], cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) + grayscale = cast(NDArray[np.uint8], cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) - # re-adjusts the image to [0 255] - low, high = extractImage(gray) - gray = adjustImage(gray, low, high) + # 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() - # create thresholded matrices + # 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() - 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 mask using percentage-based threshold on original range + mask = np.logical_and((grayscale > 0), (grayscale <= threshold_value)).astype(np.uint8) - # creates new image using threshold matrices + # Apply mask overlay to image new_img = self._drawMask(new_img, mask) ground_truth = np.count_nonzero( diff --git a/Granny/assets/starch_scales.yml b/Granny/assets/starch_scales.yml index cc271a1..08b9732 100644 --- a/Granny/assets/starch_scales.yml +++ b/Granny/assets/starch_scales.yml @@ -10,10 +10,8 @@ # rating: [corresponding rating values] # # Each variety has its own starch scale based on research and industry standards. - -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] +# +# MSU varieties calibrated with 55% threshold (2026-02-12) WA38_1: index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] @@ -23,74 +21,113 @@ 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] 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: + # 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] - # 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] + rating: [0.881028019, 0.658026584, 0.598684959, 0.313197178, 0.235314219, 0.115934565, 0.044768709] 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] + # 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: - # 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] + # 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] -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] - -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 - 55% threshold (2026-02-12) + index: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] + rating: [0.970695189, 0.948662668, 0.791846554, 0.779420670, 0.745290611, 0.663642314, 0.460208582] + +FUJI: + # 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.974763241, 0.901142588, 0.845687779, 0.805437350, 0.717441404, 0.539812629, 0.333291895, 0.168391585] + +IDARED: + # 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.982588090, 0.955356105, 0.874148553, 0.813374837, 0.657680211, 0.612489305, 0.332182826] + +JONATHAN: + # 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.952191921, 0.903481677, 0.776124925, 0.769690669, 0.606057588, 0.411963190, 0.238755173, 0.127008793]