Source code for gs1grader.graders.modulation_grader

import os
from datetime import datetime
from zoneinfo import ZoneInfo

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

from gs1grader.common import (
    DecodedDmtxData,
    get_modulation_attributes,
    map_raw_fit_x_y,
)
from gs1grader.grader_interface import DataMatrixGraderInterface


[docs] class ModulationGrader(DataMatrixGraderInterface): """Grader for evaluating the modulation quality of a Data Matrix code. Modulation measures the contrast between light and dark modules in the Data Matrix. Higher modulation values indicate better contrast between modules, which improves readability and scanning reliability. The modulation grade is determined by the following thresholds: - Grade A: 50% or higher - Grade B: 40% to 50% - Grade C: 30% to 40% - Grade D: 20% to 30% - Grade F: Less than 20% """ def __init__(self): self.grade_thresholds = { range(50, 256): "A", range(40, 50): "B", range(30, 40): "C", range(20, 30): "D", range(0, 20): "F", } self.modulation_grades = {}
[docs] def compute_grade(self, decoded_data: DecodedDmtxData) -> str: """ Compute modulation grade based on color values. This method calculates the modulation grade by analyzing the contrast between light and dark modules in the DataMatrix code. :param decoded_data: The decoded DataMatrix data containing: - fit_x: X coordinates of the fitted grid points - fit_y: Y coordinates of the fitted grid points - color: Color values at each grid point - dmtx_size_row: Number of rows in the DataMatrix - dmtx_size_col: Number of columns in the DataMatrix :type decoded_data: DecodedDmtxData :returns: The grade of the modulation (A, B, C, D, or F) :rtype: str """ modulation_attr = get_modulation_attributes(decoded_data) module_x_y = modulation_attr.module_x_y # remove empty modules module_x_y = { key: module_x_y[key] for key in module_x_y if len(module_x_y[key]) != 0 } for key in module_x_y: if ( key[0] > -1 and key[1] > -1 and key[0] < decoded_data.dmtx_size_row and key[1] < decoded_data.dmtx_size_col ): # Calculate MOD value mod_value = ( ( 2 * abs( modulation_attr.module_average[key] - modulation_attr.global_threshold ) ) * 100 // modulation_attr.symbol_contrast ) # Determine the grade level based on MOD value for grade_range in self.grade_thresholds.keys(): if mod_value in grade_range: self.modulation_grades[key] = self.grade_thresholds[ grade_range ] break if self.modulation_grades.values(): # Find the highest grade for the entire matrix return max(self.modulation_grades.values()) return "F"
[docs] def explain_grade( self, decoded_data: DecodedDmtxData, explanation_path: str ) -> str: """Generate a visual explanation of the modulation grading. This method creates a visualization of the DataMatrix with color-coded borders indicating the modulation grade of each module. The visualization is saved as an image file in the specified path. :param decoded_data: Decoded DataMatrix data containing fit coordinates, color values, and matrix dimensions :type decoded_data: DecodedDmtxData :param explanation_path: Directory path where the explanation image will be saved :type explanation_path: str :return: The filename of the saved explanation image :rtype: str """ timestamp = datetime.now(tz=ZoneInfo("Europe/Berlin")).strftime( "%Y%m%d_%H%M%S" ) filename = os.path.join( explanation_path, "modulation_grade_" + timestamp ) raw_fit_x_y_dict = map_raw_fit_x_y(decoded_data=decoded_data) modulation_grades_raw = {} for (x, y), _ in self.modulation_grades.items(): modulation_grades_raw[ raw_fit_x_y_dict[(x, y)] ] = self.modulation_grades.get((x, y)) # Set the figure size with gridspec to maintain square grids fig, ax = plt.subplots(figsize=(8, 6)) scatter = ax.scatter( decoded_data.raw_x, decoded_data.raw_y, c=decoded_data.color, cmap="viridis", marker="s", s=50, edgecolors="none", ) grade_colors = { "A": "lightgreen", "B": "lightcoral", "C": "indianred", "D": "darkorange", "F": "darkred", } # Add borders based on modulation grades for key, grade in modulation_grades_raw.items(): x, y = key color = grade_colors.get(grade, "lightgreen") rect = plt.Rectangle( (x, y), 5, 5, fill=False, edgecolor=color, linewidth=2 ) plt.gca().add_patch(rect) # Create legend for modulation grades legend_handles = [ mpatches.Patch(edgecolor=color, facecolor="none", label=grade) for grade, color in grade_colors.items() ] plt.legend( handles=legend_handles, bbox_to_anchor=(1.25, 0.5), loc="center left", title="Grades", frameon=False, ) plt.colorbar(scatter, ax=ax, label="Color Magnitude") plt.xlabel("X Coordinates of Modules") plt.ylabel("Y Coordinates of Modules") plt.title("Explanation of the Modulation grade for given datamatrix") plt.xticks(np.arange(0, max(decoded_data.raw_x), 5)) plt.yticks(np.arange(0, max(decoded_data.raw_y), 5)) # plt.xticks(np.arange(0, 5 * (decoded_data.dmtx_size_row + 1), 5)) # plt.yticks(np.arange(0, 5 * (decoded_data.dmtx_size_col + 1), 5)) # plt.grid(True) # Enable grid lines fig.savefig(filename) plt.close() return filename