Skip to content
69 changes: 27 additions & 42 deletions Granny/Analyses/StarchArea.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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(
Expand Down
119 changes: 78 additions & 41 deletions Granny/assets/starch_scales.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]