vkit.engine.barcode.code39

  1# Copyright 2022 vkit-x Administrator. All Rights Reserved.
  2#
  3# This project (vkit-x/vkit) is dual-licensed under commercial and SSPL licenses.
  4#
  5# The commercial license gives you the full rights to create and distribute software
  6# on your own terms without any SSPL license obligations. For more information,
  7# please see the "LICENSE_COMMERCIAL.txt" file.
  8#
  9# This project is also available under Server Side Public License (SSPL).
 10# The SSPL licensing is ideal for use cases such as open source projects with
 11# SSPL distribution, student/academic purposes, hobby projects, internal research
 12# projects without external distribution, or other projects where all SSPL
 13# obligations can be met. For more information, please see the "LICENSE_SSPL.txt" file.
 14from typing import Optional, Any
 15import string
 16
 17import attrs
 18from numpy.random import Generator as RandomGenerator
 19import numpy as np
 20from barcode import Code39
 21from barcode.writer import BaseWriter, mm2px
 22from PIL import Image as PilImage, ImageDraw as PilImageDraw
 23
 24from vkit.element import Mask, ScoreMap
 25from vkit.engine.interface import (
 26    Engine,
 27    EngineExecutorFactory,
 28    NoneTypeEngineInitResource,
 29)
 30from .type import BarcodeEngineRunConfig
 31
 32
 33@attrs.define
 34class BarcodeCode39EngineInitConfig:
 35    aspect_ratio: float = 0.2854396602149411
 36    alpha_min: float = 0.7
 37    alpha_max: float = 1.0
 38
 39
 40# REFERENCE: https://github.com/WhyNotHugo/python-barcode/blob/main/barcode/writer.py
 41class NoTextImageWriter(BaseWriter):  # type: ignore
 42
 43    def __init__(self, mode: str = "L"):
 44        super().__init__(
 45            self._init,
 46            self._paint_module,
 47            None,
 48            self._finish,
 49        )
 50        self.mode = mode
 51        self.dpi = 300
 52        self._image = None
 53        self._draw = None
 54
 55    def calculate_size(self, modules_per_line: int, number_of_lines: int):
 56        width = 2 * self.quiet_zone + modules_per_line * self.module_width
 57        height = 2.0 + self.module_height * number_of_lines
 58        return width, height
 59
 60    def _init(self, code: str):
 61        width, height = self.calculate_size(len(code[0]), len(code))
 62        size = (int(mm2px(width, self.dpi)), int(mm2px(height, self.dpi)))
 63        self._image = PilImage.new(self.mode, size, self.background)  # type: ignore
 64        self._draw = PilImageDraw.Draw(self._image)  # type: ignore
 65
 66    def _paint_module(self, xpos: int, ypos: int, width: int, color: Any):
 67        size = [
 68            (mm2px(xpos, self.dpi), mm2px(ypos, self.dpi)),
 69            (
 70                mm2px(xpos + width, self.dpi),
 71                mm2px(ypos + self.module_height, self.dpi),
 72            ),
 73        ]
 74        self._draw.rectangle(size, outline=color, fill=color)  # type: ignore
 75
 76    def _finish(self):
 77        return self._image
 78
 79
 80class BarcodeCode39Engine(
 81    Engine[
 82        BarcodeCode39EngineInitConfig,
 83        NoneTypeEngineInitResource,
 84        BarcodeEngineRunConfig,
 85        ScoreMap,
 86    ]
 87):  # yapf: disable
 88
 89    @classmethod
 90    def get_type_name(cls) -> str:
 91        return 'code39'
 92
 93    @classmethod
 94    def convert_barcode_pil_image_to_mask(cls, barcode_pil_image: PilImage.Image):
 95        mat = np.asarray(barcode_pil_image)
 96        mask = Mask(mat=mat).to_inverted_mask()
 97
 98        # Trim.
 99        np_hori_max = np.amax(mask.mat, axis=0)
100        np_hori_nonzero = np.nonzero(np_hori_max)[0]
101        assert len(np_hori_nonzero) >= 2
102        left = int(np_hori_nonzero[0])
103        right = int(np_hori_nonzero[-1])
104
105        np_vert_max = np.amax(mask.mat, axis=1)
106        np_vert_nonzero = np.nonzero(np_vert_max)[0]
107        assert len(np_vert_nonzero) >= 2
108        up = int(np_vert_nonzero[0])
109        down = int(np_vert_nonzero[-1])
110
111        mask = mask.to_cropped_mask(up=up, down=down, left=left, right=right)
112        return mask
113
114    def __init__(
115        self,
116        init_config: BarcodeCode39EngineInitConfig,
117        init_resource: Optional[NoneTypeEngineInitResource] = None,
118    ):
119        super().__init__(init_config, init_resource)
120
121        self.ascii_letters = tuple(string.ascii_letters)
122
123    def run(self, run_config: BarcodeEngineRunConfig, rng: RandomGenerator) -> ScoreMap:
124        num_chars = max(
125            1,
126            round(run_config.width / (run_config.height * self.init_config.aspect_ratio)),
127        )
128        text = ''.join(rng.choice(self.ascii_letters) for _ in range(num_chars))
129        pil_image = Code39(code=text, writer=NoTextImageWriter()).render()
130
131        mask = self.convert_barcode_pil_image_to_mask(pil_image)
132
133        barcode_score_map = ScoreMap.from_shapable(mask)
134        barcode_score_map[mask] = float(
135            rng.uniform(self.init_config.alpha_min, self.init_config.alpha_max)
136        )
137
138        if barcode_score_map.shape != (run_config.height, run_config.width):
139            barcode_score_map = barcode_score_map.to_resized_score_map(
140                resized_height=run_config.height,
141                resized_width=run_config.width,
142            )
143
144        return barcode_score_map
145
146
147barcode_code39_engine_executor_factory = EngineExecutorFactory(BarcodeCode39Engine)
class BarcodeCode39EngineInitConfig:
35class BarcodeCode39EngineInitConfig:
36    aspect_ratio: float = 0.2854396602149411
37    alpha_min: float = 0.7
38    alpha_max: float = 1.0
BarcodeCode39EngineInitConfig( aspect_ratio: float = 0.2854396602149411, alpha_min: float = 0.7, alpha_max: float = 1.0)
2def __init__(self, aspect_ratio=attr_dict['aspect_ratio'].default, alpha_min=attr_dict['alpha_min'].default, alpha_max=attr_dict['alpha_max'].default):
3    self.aspect_ratio = aspect_ratio
4    self.alpha_min = alpha_min
5    self.alpha_max = alpha_max

Method generated by attrs for class BarcodeCode39EngineInitConfig.

class NoTextImageWriter(barcode.writer.BaseWriter):
42class NoTextImageWriter(BaseWriter):  # type: ignore
43
44    def __init__(self, mode: str = "L"):
45        super().__init__(
46            self._init,
47            self._paint_module,
48            None,
49            self._finish,
50        )
51        self.mode = mode
52        self.dpi = 300
53        self._image = None
54        self._draw = None
55
56    def calculate_size(self, modules_per_line: int, number_of_lines: int):
57        width = 2 * self.quiet_zone + modules_per_line * self.module_width
58        height = 2.0 + self.module_height * number_of_lines
59        return width, height
60
61    def _init(self, code: str):
62        width, height = self.calculate_size(len(code[0]), len(code))
63        size = (int(mm2px(width, self.dpi)), int(mm2px(height, self.dpi)))
64        self._image = PilImage.new(self.mode, size, self.background)  # type: ignore
65        self._draw = PilImageDraw.Draw(self._image)  # type: ignore
66
67    def _paint_module(self, xpos: int, ypos: int, width: int, color: Any):
68        size = [
69            (mm2px(xpos, self.dpi), mm2px(ypos, self.dpi)),
70            (
71                mm2px(xpos + width, self.dpi),
72                mm2px(ypos + self.module_height, self.dpi),
73            ),
74        ]
75        self._draw.rectangle(size, outline=color, fill=color)  # type: ignore
76
77    def _finish(self):
78        return self._image

Baseclass for all writers.

Initializes the basic writer options. Childclasses can add more attributes and can set them directly or using self.set_options(option=value).

:parameters: initialize : Function Callback for initializing the inheriting writer. Is called: callback_initialize(raw_code) paint_module : Function Callback for painting one barcode module. Is called: callback_paint_module(xpos, ypos, width, color) paint_text : Function Callback for painting the text under the barcode. Is called: callback_paint_text(xpos, ypos) using self.text as text. finish : Function Callback for doing something with the completely rendered output. Is called: return callback_finish() and must return the rendered output.

NoTextImageWriter(mode: str = 'L')
44    def __init__(self, mode: str = "L"):
45        super().__init__(
46            self._init,
47            self._paint_module,
48            None,
49            self._finish,
50        )
51        self.mode = mode
52        self.dpi = 300
53        self._image = None
54        self._draw = None
def calculate_size(self, modules_per_line: int, number_of_lines: int):
56    def calculate_size(self, modules_per_line: int, number_of_lines: int):
57        width = 2 * self.quiet_zone + modules_per_line * self.module_width
58        height = 2.0 + self.module_height * number_of_lines
59        return width, height

Calculates the size of the barcode in pixel.

:parameters: modules_per_line : Integer Number of modules in one line. number_of_lines : Integer Number of lines of the barcode.

:returns: Width and height of the barcode in pixel.

Inherited Members
barcode.writer.BaseWriter
save
register_callback
set_options
packed
render
 81class BarcodeCode39Engine(
 82    Engine[
 83        BarcodeCode39EngineInitConfig,
 84        NoneTypeEngineInitResource,
 85        BarcodeEngineRunConfig,
 86        ScoreMap,
 87    ]
 88):  # yapf: disable
 89
 90    @classmethod
 91    def get_type_name(cls) -> str:
 92        return 'code39'
 93
 94    @classmethod
 95    def convert_barcode_pil_image_to_mask(cls, barcode_pil_image: PilImage.Image):
 96        mat = np.asarray(barcode_pil_image)
 97        mask = Mask(mat=mat).to_inverted_mask()
 98
 99        # Trim.
100        np_hori_max = np.amax(mask.mat, axis=0)
101        np_hori_nonzero = np.nonzero(np_hori_max)[0]
102        assert len(np_hori_nonzero) >= 2
103        left = int(np_hori_nonzero[0])
104        right = int(np_hori_nonzero[-1])
105
106        np_vert_max = np.amax(mask.mat, axis=1)
107        np_vert_nonzero = np.nonzero(np_vert_max)[0]
108        assert len(np_vert_nonzero) >= 2
109        up = int(np_vert_nonzero[0])
110        down = int(np_vert_nonzero[-1])
111
112        mask = mask.to_cropped_mask(up=up, down=down, left=left, right=right)
113        return mask
114
115    def __init__(
116        self,
117        init_config: BarcodeCode39EngineInitConfig,
118        init_resource: Optional[NoneTypeEngineInitResource] = None,
119    ):
120        super().__init__(init_config, init_resource)
121
122        self.ascii_letters = tuple(string.ascii_letters)
123
124    def run(self, run_config: BarcodeEngineRunConfig, rng: RandomGenerator) -> ScoreMap:
125        num_chars = max(
126            1,
127            round(run_config.width / (run_config.height * self.init_config.aspect_ratio)),
128        )
129        text = ''.join(rng.choice(self.ascii_letters) for _ in range(num_chars))
130        pil_image = Code39(code=text, writer=NoTextImageWriter()).render()
131
132        mask = self.convert_barcode_pil_image_to_mask(pil_image)
133
134        barcode_score_map = ScoreMap.from_shapable(mask)
135        barcode_score_map[mask] = float(
136            rng.uniform(self.init_config.alpha_min, self.init_config.alpha_max)
137        )
138
139        if barcode_score_map.shape != (run_config.height, run_config.width):
140            barcode_score_map = barcode_score_map.to_resized_score_map(
141                resized_height=run_config.height,
142                resized_width=run_config.width,
143            )
144
145        return barcode_score_map

Abstract base class for generic types.

A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::

class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.

This class can then be used as follows::

def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default

BarcodeCode39Engine( init_config: vkit.engine.barcode.code39.BarcodeCode39EngineInitConfig, init_resource: Union[vkit.engine.interface.NoneTypeEngineInitResource, NoneType] = None)
115    def __init__(
116        self,
117        init_config: BarcodeCode39EngineInitConfig,
118        init_resource: Optional[NoneTypeEngineInitResource] = None,
119    ):
120        super().__init__(init_config, init_resource)
121
122        self.ascii_letters = tuple(string.ascii_letters)
@classmethod
def get_type_name(cls) -> str:
90    @classmethod
91    def get_type_name(cls) -> str:
92        return 'code39'
@classmethod
def convert_barcode_pil_image_to_mask(cls, barcode_pil_image: PIL.Image.Image):
 94    @classmethod
 95    def convert_barcode_pil_image_to_mask(cls, barcode_pil_image: PilImage.Image):
 96        mat = np.asarray(barcode_pil_image)
 97        mask = Mask(mat=mat).to_inverted_mask()
 98
 99        # Trim.
100        np_hori_max = np.amax(mask.mat, axis=0)
101        np_hori_nonzero = np.nonzero(np_hori_max)[0]
102        assert len(np_hori_nonzero) >= 2
103        left = int(np_hori_nonzero[0])
104        right = int(np_hori_nonzero[-1])
105
106        np_vert_max = np.amax(mask.mat, axis=1)
107        np_vert_nonzero = np.nonzero(np_vert_max)[0]
108        assert len(np_vert_nonzero) >= 2
109        up = int(np_vert_nonzero[0])
110        down = int(np_vert_nonzero[-1])
111
112        mask = mask.to_cropped_mask(up=up, down=down, left=left, right=right)
113        return mask
def run( self, run_config: vkit.engine.barcode.type.BarcodeEngineRunConfig, rng: numpy.random._generator.Generator) -> vkit.element.score_map.ScoreMap:
124    def run(self, run_config: BarcodeEngineRunConfig, rng: RandomGenerator) -> ScoreMap:
125        num_chars = max(
126            1,
127            round(run_config.width / (run_config.height * self.init_config.aspect_ratio)),
128        )
129        text = ''.join(rng.choice(self.ascii_letters) for _ in range(num_chars))
130        pil_image = Code39(code=text, writer=NoTextImageWriter()).render()
131
132        mask = self.convert_barcode_pil_image_to_mask(pil_image)
133
134        barcode_score_map = ScoreMap.from_shapable(mask)
135        barcode_score_map[mask] = float(
136            rng.uniform(self.init_config.alpha_min, self.init_config.alpha_max)
137        )
138
139        if barcode_score_map.shape != (run_config.height, run_config.width):
140            barcode_score_map = barcode_score_map.to_resized_score_map(
141                resized_height=run_config.height,
142                resized_width=run_config.width,
143            )
144
145        return barcode_score_map