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 ..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(
124        self,
125        run_config: BarcodeEngineRunConfig,
126        rng: Optional[RandomGenerator] = None,
127    ) -> ScoreMap:
128        assert rng is not None
129
130        num_chars = max(
131            1,
132            round(run_config.width / (run_config.height * self.init_config.aspect_ratio)),
133        )
134        text = ''.join(rng.choice(self.ascii_letters) for _ in range(num_chars))
135        pil_image = Code39(code=text, writer=NoTextImageWriter()).render()
136
137        mask = self.convert_barcode_pil_image_to_mask(pil_image)
138
139        barcode_score_map = ScoreMap.from_shapable(mask)
140        barcode_score_map[mask] = float(
141            rng.uniform(self.init_config.alpha_min, self.init_config.alpha_max)
142        )
143
144        if barcode_score_map.shape != (run_config.height, run_config.width):
145            barcode_score_map = barcode_score_map.to_resized_score_map(
146                resized_height=run_config.height,
147                resized_width=run_config.width,
148            )
149
150        return barcode_score_map
151
152
153barcode_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(
125        self,
126        run_config: BarcodeEngineRunConfig,
127        rng: Optional[RandomGenerator] = None,
128    ) -> ScoreMap:
129        assert rng is not None
130
131        num_chars = max(
132            1,
133            round(run_config.width / (run_config.height * self.init_config.aspect_ratio)),
134        )
135        text = ''.join(rng.choice(self.ascii_letters) for _ in range(num_chars))
136        pil_image = Code39(code=text, writer=NoTextImageWriter()).render()
137
138        mask = self.convert_barcode_pil_image_to_mask(pil_image)
139
140        barcode_score_map = ScoreMap.from_shapable(mask)
141        barcode_score_map[mask] = float(
142            rng.uniform(self.init_config.alpha_min, self.init_config.alpha_max)
143        )
144
145        if barcode_score_map.shape != (run_config.height, run_config.width):
146            barcode_score_map = barcode_score_map.to_resized_score_map(
147                resized_height=run_config.height,
148                resized_width=run_config.width,
149            )
150
151        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: Union[numpy.random._generator.Generator, NoneType] = None) -> vkit.element.score_map.ScoreMap:
124    def run(
125        self,
126        run_config: BarcodeEngineRunConfig,
127        rng: Optional[RandomGenerator] = None,
128    ) -> ScoreMap:
129        assert rng is not None
130
131        num_chars = max(
132            1,
133            round(run_config.width / (run_config.height * self.init_config.aspect_ratio)),
134        )
135        text = ''.join(rng.choice(self.ascii_letters) for _ in range(num_chars))
136        pil_image = Code39(code=text, writer=NoTextImageWriter()).render()
137
138        mask = self.convert_barcode_pil_image_to_mask(pil_image)
139
140        barcode_score_map = ScoreMap.from_shapable(mask)
141        barcode_score_map[mask] = float(
142            rng.uniform(self.init_config.alpha_min, self.init_config.alpha_max)
143        )
144
145        if barcode_score_map.shape != (run_config.height, run_config.width):
146            barcode_score_map = barcode_score_map.to_resized_score_map(
147                resized_height=run_config.height,
148                resized_width=run_config.width,
149            )
150
151        return barcode_score_map