Skip to content
Merged
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
76 changes: 76 additions & 0 deletions tests/test_Analyses/test_Analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from Granny.Analyses.StarchArea import StarchArea
from Granny.Models.Images.RGBImage import RGBImage


def _get_analysis():
"""Use StarchArea as a concrete implementation of Analysis."""
return StarchArea()


def test_parse_qr_from_filename_valid():
analysis = _get_analysis()
result = analysis._parse_qr_from_filename(
"APPLE2025_LOT001_2025-12-02_BB-Late_fruit_01.png"
)
assert result["project"] == "APPLE2025"
assert result["lot"] == "LOT001"
assert result["date"] == "2025-12-02"
assert result["variety"] == "BB-Late"


def test_parse_qr_from_filename_with_path():
analysis = _get_analysis()
result = analysis._parse_qr_from_filename(
"/some/path/to/APPLE2025_LOT001_2025-12-02_BB-Late_fruit_05.png"
)
assert result["project"] == "APPLE2025"
assert result["variety"] == "BB-Late"


def test_parse_qr_from_filename_jpg():
analysis = _get_analysis()
result = analysis._parse_qr_from_filename(
"PROJ_LOT_DATE_VAR_fruit_01.jpg"
)
assert result["project"] == "PROJ"
assert result["variety"] == "VAR"


def test_parse_qr_from_filename_legacy():
"""Legacy filenames without QR data should return empty strings."""
analysis = _get_analysis()
result = analysis._parse_qr_from_filename("apple_fruit_01.png")
assert result["project"] == ""
assert result["lot"] == ""
assert result["date"] == ""
assert result["variety"] == ""


def test_parse_qr_from_filename_no_match():
analysis = _get_analysis()
result = analysis._parse_qr_from_filename("random_image.png")
assert result["project"] == ""


def test_add_qr_metadata_valid():
analysis = _get_analysis()
img = RGBImage("APPLE2025_LOT001_2025-12-02_BB-Late_fruit_01.png")
analysis._add_qr_metadata(img, "APPLE2025_LOT001_2025-12-02_BB-Late_fruit_01.png")

metadata = img.getMetaData()
assert "project" in metadata
assert metadata["project"].getValue() == "APPLE2025"
assert metadata["lot"].getValue() == "LOT001"
assert metadata["date"].getValue() == "2025-12-02"
assert metadata["variety"].getValue() == "BB-Late"


def test_add_qr_metadata_legacy():
"""Legacy filenames should not add QR metadata."""
analysis = _get_analysis()
img = RGBImage("apple_fruit_01.png")
analysis._add_qr_metadata(img, "apple_fruit_01.png")

metadata = img.getMetaData()
assert "project" not in metadata
assert "lot" not in metadata
Empty file.
97 changes: 97 additions & 0 deletions tests/test_Models/test_Utils/test_QRCodeDetector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import cv2
import numpy as np
from Granny.Utils.QRCodeDetector import QRCodeDetector


def test_instantiation():
detector = QRCodeDetector()
assert detector.detector is not None
assert isinstance(detector.barcode_enabled, bool)


def test_detect_returns_none_for_blank_image():
detector = QRCodeDetector()
blank = np.zeros((100, 100, 3), dtype=np.uint8)
data, points = detector.detect(blank)
assert data is None
assert points is None


def test_detect_barcode_rotation_invariant():
"""Barcode detection should work regardless of image rotation."""
detector = QRCodeDetector()
if not detector.barcode_enabled:
return

# Create a test image with a Code128 barcode using pyzbar's expected input
# We'll use a real barcode image if available, otherwise test the rotation logic
# by generating a simple barcode-like pattern
from pyzbar import pyzbar

# Create a synthetic barcode image using python-barcode if available
try:
import barcode
from barcode.writer import ImageWriter
import tempfile
import os

code = barcode.get("code128", "TEST123", writer=ImageWriter())
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
code.save(tmp.name.replace(".png", ""))
barcode_path = tmp.name.replace(".png", "") + ".png"

img = cv2.imread(barcode_path)
if img is None:
return

# Test original orientation
data, points = detector._detect_barcode(img)
assert data == "TEST123"

# Test 90 degree rotation
rotated = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
data, points = detector._detect_barcode(rotated)
assert data == "TEST123"

# Test 180 degree rotation
rotated = cv2.rotate(img, cv2.ROTATE_180)
data, points = detector._detect_barcode(rotated)
assert data == "TEST123"

# Test 270 degree rotation
rotated = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
data, points = detector._detect_barcode(rotated)
assert data == "TEST123"

os.unlink(barcode_path)
except ImportError:
# python-barcode not installed, skip
pass


def test_extract_variety_info_pipe_format():
detector = QRCodeDetector()
info = detector.extract_variety_info("APPLE2026|LOT002|2026-01-23|BB-Early")
assert info["project"] == "APPLE2026"
assert info["lot"] == "LOT002"
assert info["date"] == "2026-01-23"
assert info["full"] == "BB-Early"
assert info["variety"] == "BB"
assert info["timing"] == "Early"


def test_extract_variety_info_legacy_format():
detector = QRCodeDetector()
info = detector.extract_variety_info("BB-Late")
assert info["project"] == "UNKNOWN"
assert info["lot"] == "UNKNOWN"
assert info["full"] == "BB-Late"
assert info["variety"] == "BB"
assert info["timing"] == "Late"


def test_extract_variety_info_malformed_pipe():
detector = QRCodeDetector()
info = detector.extract_variety_info("only|two")
assert info["project"] == "UNKNOWN"
assert info["full"] == "only|two"
85 changes: 84 additions & 1 deletion tests/test_Models/test_Values/test_MetaDataValue.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,88 @@
import os
import tempfile
import pandas as pd
from Granny.Models.Values.MetaDataValue import MetaDataValue
from Granny.Models.Values.FloatValue import FloatValue
from Granny.Models.Values.StringValue import StringValue
from Granny.Models.Images.RGBImage import RGBImage
import numpy as np


def _make_image(name, rating_val, project=None, lot=None, date=None, variety=None):
"""Helper to create a test image with metadata."""
img = RGBImage(name)
img.setImage(np.zeros((10, 10, 3), dtype=np.uint8))


rating = FloatValue("rating", "rating", "test rating")
rating.setMin(0.0)
rating.setMax(1.0)
rating.setValue(rating_val)
img.addValue(rating)

if project:
for key, val in [("project", project), ("lot", lot), ("date", date), ("variety", variety)]:
sv = StringValue(key, key, f"test {key}")
sv.setValue(val)
img.addValue(sv)

return img


def test_write_tray_summary_with_string_columns():
"""tray_summary.csv should include string metadata columns like project, lot, date, variety."""
with tempfile.TemporaryDirectory() as tmpdir:
mdv = MetaDataValue("results", "results", "test")
mdv.setValue(tmpdir)

images = [
_make_image("PROJ_LOT1_2025-01-01_VAR_fruit_01.png", 0.8, "PROJ", "LOT1", "2025-01-01", "VAR"),
_make_image("PROJ_LOT1_2025-01-01_VAR_fruit_02.png", 0.6, "PROJ", "LOT1", "2025-01-01", "VAR"),
]
mdv.setImageList(images)
mdv.writeValue()

tray_df = pd.read_csv(os.path.join(tmpdir, "tray_summary.csv"))
assert "project" in tray_df.columns
assert "lot" in tray_df.columns
assert "date" in tray_df.columns
assert "variety" in tray_df.columns
assert tray_df["project"].iloc[0] == "PROJ"
assert tray_df["lot"].iloc[0] == "LOT1"
assert tray_df["rating"].iloc[0] == 0.7 # average of 0.8 and 0.6


def test_write_tray_summary_without_string_columns():
"""tray_summary.csv should still work without string metadata."""
with tempfile.TemporaryDirectory() as tmpdir:
mdv = MetaDataValue("results", "results", "test")
mdv.setValue(tmpdir)

images = [
_make_image("apple_fruit_01.png", 0.9),
_make_image("apple_fruit_02.png", 0.7),
]
mdv.setImageList(images)
mdv.writeValue()

tray_df = pd.read_csv(os.path.join(tmpdir, "tray_summary.csv"))
assert "TrayName" in tray_df.columns
assert "rating" in tray_df.columns
assert abs(tray_df["rating"].iloc[0] - 0.8) < 0.001


def test_results_csv_has_all_rows():
"""results.csv should have one row per image."""
with tempfile.TemporaryDirectory() as tmpdir:
mdv = MetaDataValue("results", "results", "test")
mdv.setValue(tmpdir)

images = [
_make_image("PROJ_LOT1_2025-01-01_VAR_fruit_01.png", 0.5, "PROJ", "LOT1", "2025-01-01", "VAR"),
_make_image("PROJ_LOT1_2025-01-01_VAR_fruit_02.png", 0.6, "PROJ", "LOT1", "2025-01-01", "VAR"),
_make_image("PROJ_LOT1_2025-01-01_VAR_fruit_03.png", 0.7, "PROJ", "LOT1", "2025-01-01", "VAR"),
]
mdv.setImageList(images)
mdv.writeValue()

results_df = pd.read_csv(os.path.join(tmpdir, "results.csv"))
assert len(results_df) == 3