Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ dist/
06-Package
*logs/
_build/
venv/
.coverage
coverage.lcov
results/

# Files
*.pyc
Expand Down
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 @@ -267,6 +268,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 @@ -542,7 +547,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 @@ -610,6 +627,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 @@ -636,6 +669,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 @@ -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 @@ -420,6 +421,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