Skip to content
Merged
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ test_perf.py
*.jpeg
*.jpg
*.tiff
CLAUDE.md
76 changes: 76 additions & 0 deletions Granny/Analyses/Analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,82 @@ def resetRetValues(self):
"""
self.ret_values = {}

def _parse_qr_from_filename(self, filename: str) -> dict:
"""
Extract QR code information from segmented image filename.

Expected format: PROJECT_LOT_DATE_VARIETY_fruit_##.png
Example: APPLE2025_LOT001_2025-12-02_BB-Late_fruit_01.png

Args:
filename: Image filename (with or without path)

Returns:
Dictionary with QR information:
{
'project': project code or empty string,
'lot': lot code or empty string,
'date': date string or empty string,
'variety': variety string or empty string
}

Notes:
- Returns empty strings for all fields if parsing fails
- Handles legacy filenames gracefully (no QR data)
"""
import re
from pathlib import Path

# Extract just the filename without path
filename_only = Path(filename).name

# Pattern: PROJECT_LOT_DATE_VARIETY_fruit_##.png
# Use regex to match everything before "_fruit_##"
pattern = r'^(.+?)_(.+?)_(.+?)_(.+?)_fruit_\d+\.(?:png|jpg|jpeg)$'
match = re.match(pattern, filename_only)

if match:
return {
'project': match.group(1),
'lot': match.group(2),
'date': match.group(3),
'variety': match.group(4)
}
else:
# Parsing failed - return empty strings (no QR data)
return {
'project': '',
'lot': '',
'date': '',
'variety': ''
}

def _add_qr_metadata(self, result_img, filename: str):
"""
Parse QR/barcode metadata from filename and add to result image.

Args:
result_img: Image instance to add metadata values to
filename: Image filename to parse
"""
qr_info = self._parse_qr_from_filename(filename)
if qr_info['project']:
project_val = StringValue("project", "project", "Project code from QR code")
project_val.setValue(qr_info['project'])
result_img.addValue(project_val)

lot_val = StringValue("lot", "lot", "Lot code from QR code")
lot_val.setValue(qr_info['lot'])
result_img.addValue(lot_val)

date_val = StringValue("date", "date", "Date from QR code")
date_val.setValue(qr_info['date'])
result_img.addValue(date_val)

variety_val = StringValue("variety", "variety", "Variety from QR code")
variety_val.setValue(qr_info['variety'])
result_img.addValue(variety_val)

def performAnalysis(self) -> List[Image]:
"""
Once all required parameters have been set, this function is used
Expand Down
4 changes: 4 additions & 0 deletions Granny/Analyses/BlushColor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from Granny.Models.Values.ImageListValue import ImageListValue
from Granny.Models.Values.IntValue import IntValue
from Granny.Models.Values.MetaDataValue import MetaDataValue
from Granny.Models.Values.StringValue import StringValue
from numpy.typing import NDArray


Expand Down Expand Up @@ -266,6 +267,9 @@ def _processImage(self, image_instance: Image) -> Image:
result_img: Image = RGBImage(image_instance.getImageName())
result_img.setImage(result)

# Extract and add QR/barcode metadata from filename (if present)
self._add_qr_metadata(result_img, image_instance.getImageName())

# saves the calculated score to the image_instance as a parameter
rating = FloatValue(
"rating", "rating", "Granny calculated rating of total blush area."
Expand Down
4 changes: 4 additions & 0 deletions Granny/Analyses/PeelColor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from Granny.Models.Values.ImageListValue import ImageListValue
from Granny.Models.Values.IntValue import IntValue
from Granny.Models.Values.MetaDataValue import MetaDataValue
from Granny.Models.Values.StringValue import StringValue
from numpy.typing import NDArray


Expand Down Expand Up @@ -493,6 +494,9 @@ def _processImage(self, image_instance: Image) -> Image:
)
b_value.setValue(b)

# Extract and add QR/barcode metadata from filename (if present)
self._add_qr_metadata(image_instance, image_instance.getImageName())

# adds ratings to to the image_instance as parameters
image_instance.addValue(
bin_value,
Expand Down
36 changes: 35 additions & 1 deletion Granny/Analyses/Segmentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from Granny.Models.Values.FloatValue import FloatValue
from Granny.Models.Values.ImageListValue import ImageListValue
from Granny.Models.Values.IntValue import IntValue
from Granny.Utils.QRCodeDetector import QRCodeDetector
from numpy.typing import NDArray


Expand Down Expand Up @@ -297,6 +298,10 @@ def __init__(self):
)
)

# Initialize QR code detector for variety information extraction
self.qr_detector = QRCodeDetector()
self.variety_info = None # Will store detected variety information if QR code found

self.addInParam(
self.model,
self.input_images,
Expand Down Expand Up @@ -575,7 +580,19 @@ def _extractImage(self, tray_image: Image) -> List[Image]:
mask = sorted_masks[i]
for channel in range(3):
individual_image[:, :, channel] = tray_image_array[y1:y2, x1:x2, channel] * mask[y1:y2, x1:x2] # type: ignore
image_name = pathlib.Path(tray_image.getImageName()).stem + f"_fruit_{i+1:02d}" + ".png"

# Build filename: use QR data if detected, otherwise use default tray name
if self.variety_info is not None:
# QR code detected - use PROJECT_LOT_DATE_VARIETY_fruit_##.png
project = self.variety_info['project']
lot = self.variety_info['lot']
date = self.variety_info['date']
variety = self.variety_info['full']
image_name = f"{project}_{lot}_{date}_{variety}_fruit_{i+1:02d}.png"
else:
# No QR code - use default naming: tray_name_fruit_##.png
image_name = pathlib.Path(tray_image.getImageName()).stem + f"_fruit_{i+1:02d}" + ".png"

image_instance: Image = RGBImage(image_name)
image_instance.setImage(individual_image)
individual_images.append(image_instance)
Expand Down Expand Up @@ -643,6 +660,22 @@ def performAnalysis(self) -> List[Image]:
if h > w:
image_instance.rotateImage()

# Detect QR code to extract variety information (optional)
try:
qr_data, qr_points = self.qr_detector.detect(image_instance.getImage())
if qr_data:
self.variety_info = self.qr_detector.extract_variety_info(qr_data)
print(f"QR Code detected: {qr_data}")
print(f" Project: {self.variety_info['project']}, Lot: {self.variety_info['lot']}")
print(f" Date: {self.variety_info['date']}, Variety: {self.variety_info['full']}")
else:
print("No QR code detected - using default naming")
self.variety_info = None
except Exception as e:
# QR detection failed, continue with default naming
print(f"QR detection error: {str(e)} - using default naming")
self.variety_info = None

# predicts fruit instances in the image
result = self._segmentInstances(image=image_instance.getImage())

Expand All @@ -669,6 +702,7 @@ def performAnalysis(self) -> List[Image]:

self.masked_images.setImageList([masked_image])
self.masked_images.writeValue()

except:
AttributeError("Error with the results.")

Expand Down
4 changes: 4 additions & 0 deletions Granny/Analyses/StarchArea.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from Granny.Models.Values.ImageListValue import ImageListValue
from Granny.Models.Values.IntValue import IntValue
from Granny.Models.Values.MetaDataValue import MetaDataValue
from Granny.Models.Values.StringValue import StringValue
from numpy.typing import NDArray


Expand Down Expand Up @@ -344,6 +345,9 @@ def _processImage(self, image_instance: Image) -> Image:
result_img: Image = RGBImage(image_instance.getImageName())
result_img.setImage(result)

# Extract and add QR/barcode metadata from filename (if present)
self._add_qr_metadata(result_img, image_instance.getImageName())

# saves the calculated score to the image_instance as a parameter
rating = FloatValue(
"rating", "rating", "Granny calculated rating of total starch area."
Expand Down
4 changes: 4 additions & 0 deletions Granny/Analyses/SuperficialScald.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from Granny.Models.Values.ImageListValue import ImageListValue
from Granny.Models.Values.IntValue import IntValue
from Granny.Models.Values.MetaDataValue import MetaDataValue
from Granny.Models.Values.StringValue import StringValue
from numpy.typing import NDArray


Expand Down Expand Up @@ -367,6 +368,9 @@ def _processImage(self, image_instance: Image) -> Image:
result_img: Image = RGBImage(image_instance.getImageName())
result_img.setImage(binarized_image)

# Extract and add QR/barcode metadata from filename (if present)
self._add_qr_metadata(result_img, image_instance.getImageName())

# saves the calculated score to the image_instance as a parameter
rating = FloatValue(
"rating", "rating", "Granny calculated rating of total starch area."
Expand Down
8 changes: 7 additions & 1 deletion Granny/Models/Values/MetaDataValue.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ def writeValue(self):
)
image_rating.to_csv(os.path.join(self.value, "results.csv"), header=True, index=False)
tray_avg = image_rating.drop(columns=["Name"])
tray_avg = tray_avg.groupby("TrayName").mean().reset_index()
string_cols = tray_avg.select_dtypes(include=["object"]).columns.difference(["TrayName"]).tolist()
tray_numeric = tray_avg.groupby("TrayName").mean(numeric_only=True).reset_index()
if string_cols:
tray_strings = tray_avg.groupby("TrayName")[string_cols].first().reset_index()
tray_avg = tray_strings.merge(tray_numeric, on="TrayName")
else:
tray_avg = tray_numeric
tray_avg.to_csv(os.path.join(self.value, "tray_summary.csv"), header=True, index=False)

def getImageList(self):
Expand Down
Loading