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)
35class BarcodeCode39EngineInitConfig: 36 aspect_ratio: float = 0.2854396602149411 37 alpha_min: float = 0.7 38 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.
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.
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
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
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