preprocess.py Source File

preprocess.py Source File#

Mobilint SDK qb Compiler: preprocess.py Source File
Mobilint SDK qb Compiler v0.11.0.1
MCS002-KR
preprocess.py
Go to the documentation of this file.
1
4
5
6import copy
7import gc
8import numpy as np
9import cv2
10import os
11import inspect
12from typing import Tuple, Union, List, Optional, Dict
13import torch
14import torchvision.transforms.functional as F
15from functools import partial
16from pathlib import Path
17from transformers import AutoTokenizer, AutoModelForCausalLM
18from .utils_preprocess import *
19
20
23
24
25DTYPE_GETDATA_MAP = {"Image": "GetImage", "Npy": "GetNpy", "LLM": "GetText"}
26__preprocs__ = {"Image": {}, "Npy": {}, "LLM": {}}
27
28
29def preprocess_fnc(fnc, Datatype="Image"):
30 if not inspect.isclass(fnc) and not inspect.isfunction(fnc):
31 raise TypeError(f"module must be a class or a function, but got {type(fnc)}.")
32 __preprocs__[Datatype][fnc.__name__.lower()] = fnc
33 return fnc
34
35
36@partial(preprocess_fnc, Datatype="Npy")
37class GetNpy:
38 """
39 @brief Load NumPy data from an in-memory array or a .npy file.
40 """
41
42 def __init__(self):
43 super().__init__()
44
45 def __call__(self, npy_input: Union[np.ndarray, str]) -> np.ndarray:
46 """
47 Args:
48 npy_input (Union[np.ndarray, str]): Input .npy path or numpy data.
49 """
50 if isinstance(npy_input, str):
51 assert os.path.isfile(npy_input), f"Can not find npy file from {npy_input}."
52 npy = np.load(npy_input)
53 elif isinstance(npy_input, np.ndarray):
54 npy = npy_input
55 else:
56 raise TypeError(
57 f"Expected str or np.ndarray npy_input, but got {type(npy_input)}."
58 )
59 return npy
60
61
62@partial(preprocess_fnc, Datatype="Npy")
64 def __init__(
65 self,
66 mean: Union[List[float], np.ndarray],
67 std: Union[List[float], np.ndarray],
68 to_float_div255: bool = False,
69 ):
70 super().__init__()
71 assert all(
72 [isinstance(element, (np.ndarray, list, tuple)) for element in [mean, std]]
73 ), f"We support List and np.ndarry data type for mean and std values. But type mean: {type(mean)}, type std: {type(std)}"
74 self.mean = np.array(mean) if isinstance(mean, (list, tuple)) else mean
75 self.std = np.array(std) if isinstance(std, (list, tuple)) else std
76 self.to_float_div255 = to_float_div255
77
78 def __call__(self, img: np.ndarray):
79 img = img.copy().astype(np.float32)
80 if self.to_float_div255:
81 img /= 255.0
82 try:
83 normalize_(img, self.mean, self.std)
84 except:
85 raise Exception(
86 f"Calling the '{self.__class__.__name__}', normalization process faild. check the img shape:{img.shape}, mean:{self.mean}, and std:{self.std}"
87 )
88
89
90@partial(preprocess_fnc, Datatype="Npy")
91class NpyPad:
92 """
93 @brief Pad NumPy arrays to a desired shape.
94
95 @param shape list[int]. Expected output shape after padding.
96 @param padding_type string. Padding mode, either "constant" or "reflect".
97 @param pad_val float. Fill value when padding_mode is 'constant'. Defaults to 0.
98 """
99
100 def __init__(
101 self, shape: List[int], padding_type: str, pad_val: Optional[float] = 0
102 ):
103 assert padding_type in [
104 "constant",
105 "reflect",
106 ], f"padding_type is not correct, padding_type must be 'constant', 'reflect', but input padding type is {padding_type}"
107 self.shape = np.array(shape)
108 self.padding_type = padding_type
109 self.pad_val = pad_val
110
111 def __call__(self, data: np.ndarray):
112 assert (
113 len(self.shape) == data.ndim
114 ), f"Dimension of padded data shape is not equal to the npy data. dimension of padded shape: {self.shape}, data dimension: {data.ndim}"
115 residual_pad = self.shape - np.array(data.shape)
116
117 assert (
118 residual_pad
119 ).min() >= 0, "Padded data shape must be larger than original shape"
120 if self.padding_type == "constant":
121 data = np.pad(
122 data,
123 residual_pad.max() // 2 + 1,
124 "constant",
125 constant_values=self.pad_val,
126 )
127 elif self.padding_type == "reflect":
128 data = np.pad(data, residual_pad.max() // 2 + 1, "reflect")
129 else:
130 raise NotImplementedError(
131 f"padding_type: {self.padding_type}, padding_type must be 'constant' or 'reflect'"
132 )
133
134 return self.slice_padded_data(data)
135
136 def slice_padded_data(self, data):
137 residual_slice = np.array(data.shape) - self.shape
138 slice_list = [
139 slice(residual_slice[i] // 2, residual_slice[i] // 2 + self.shape[i], None)
140 for i in range(data.ndim)
141 ]
142
143 return data[tuple(slice_list)]
144
145
146@preprocess_fnc
148 """
149 @brief Retrieve an image tensor from a file path or NumPy array using OpenCV.
150
151 @param to_float32 bool. Cast the output image to float32 when True. Defaults to False.
152 @param channel_order string. Channel order to enforce ("bgr", "rgb", "bw", "rgba", or "brga"). Defaults to "bgr".
153 """
154
155 def __init__(self, to_float32: bool = False, channel_order: str = "bgr"):
156 super().__init__()
157 self.to_float32 = to_float32
158 assert channel_order.lower() in ["bgr", "rgb", "bw", "rgba", "brga"]
159 self.channel_order = channel_order.lower()
160
161 def __call__(self, img_input: Union[np.ndarray, str]) -> np.ndarray:
162 """
163 Args:
164 img_input (Union[np.ndarray, str]): Input image path or numpy tensor.
165
166 Returns:
167 keepdims: Contains image numpy tensor and image meta such as "ori_shape".
168 """
169 if isinstance(img_input, str):
170 img = self.get_image_data(img_input)
171 if self.channel_order == "bw":
172 if self.num_channel == 1:
173 pass
174 elif self.num_channel == 3:
175 img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
176 else:
177 img = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
178 # (H, w) -> (H, W, 1)
179 img = np.expand_dims(img, axis=-1)
180 elif self.channel_order == "bgr":
181 if self.num_channel == 1:
182 raise ValueError(
183 f"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'bgr' format."
184 )
185 elif self.num_channel == 3:
186 pass
187 else:
188 img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
189 elif self.channel_order == "rgb":
190 if self.num_channel == 1:
191 raise ValueError(
192 f"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'rgb' format."
193 )
194 elif self.num_channel == 3:
195 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
196 else:
197 img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
198 elif self.channel_order == "rgba":
199 if self.num_channel == 1:
200 raise ValueError(
201 f"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'rgba' format."
202 )
203 elif self.num_channel == 3:
204 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)
205 print(
206 f"The input image has three channels. As the alpha channel is added to the image, the values of the last transparency channel are filled with 255."
207 )
208 else:
209 img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
210 elif self.channel_order == "brga":
211 if self.num_channel == 1:
212 raise ValueError(
213 f"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'bgra' format."
214 )
215 elif self.num_channel == 3:
216 img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
217 print(
218 f"The input image has three channels. As the alpha channel is added to the image, the values of the last transparency channel are filled with 255."
219 )
220 else:
221 pass
222 else:
223 raise NotImplementedError(
224 f"Got unsupported channel_order: {self.channel_order}."
225 )
226 elif isinstance(img_input, np.ndarray):
227 img = img_input
228 else:
229 raise TypeError(
230 f"Expected str or np.ndarray img_input, but got {type(img_input)}."
231 )
232
233 if self.to_float32:
234 img = img.astype(np.float32)
235
236 return img
237
238 def get_image_data(self, img_input: str):
239 assert os.path.isfile(img_input), f"Can not find img file fro {img_input}"
240 img = cv2.imread(img_input, cv2.IMREAD_COLOR)
241 if len(img.shape) == 2:
242 self.num_channel = 1
243 elif len(img.shape) == 3:
244 if img.shape[-1] == 3:
245 self.num_channel = 3
246 elif img.shape[-1] == 4:
247 self.num_channel = 4
248 else:
249 raise ValueError(f"the number of image channel must be 1 or 3 or 4.")
250
251 return img
252
253
254@partial(preprocess_fnc, Datatype="LLM")
255class GetText:
256 """@class GetText
257 @brief Extract text strings from raw inputs.
258 @param text_key Optional key to pull from dictionary inputs.
259 @param strip Remove leading and trailing whitespace when true.
260 """
261
262 def __init__(self, text_key: Optional[str] = None, strip: bool = True):
263 super().__init__()
264 self.text_key = text_key
265 self.strip = strip
266
267 def __call__(self, text_input: Union[str, Dict[str, str]]):
268 if isinstance(text_input, dict):
269 if self.text_key:
270 if self.text_key not in text_input:
271 raise KeyError(
272 f"Key '{self.text_key}' not found in text input dictionary."
273 )
274 text = text_input[self.text_key]
275 else:
276 candidates = [v for v in text_input.values() if isinstance(v, str)]
277 if not candidates:
278 raise ValueError(
279 "No string value found in input dictionary for text extraction."
280 )
281 text = candidates[0]
282 elif isinstance(text_input, str):
283 if os.path.isfile(text_input):
284 with open(text_input, "r", encoding="utf-8") as f:
285 text = f.read()
286 else:
287 text = text_input
288 else:
289 raise TypeError(
290 f"Expected str or dict[str, str] text_input, but got {type(text_input)}."
291 )
292
293 if self.strip:
294 text = text.strip()
295 return text
296
297
298@partial(preprocess_fnc, Datatype="LLM")
300 """@class TokenizeToEmbedding
301 @brief Convert tokenized text into embeddings using a Hugging Face model.
302 @param tokenizer_dir Directory containing tokenizer resources.
303 @param model_dir Optional directory for the model weights; defaults to tokenizer_dir.
304 @param device Device string determining where inference runs.
305 @param trust_remote_code Whether to load custom code from the model repo.
306 @param min_seqlen Minimum token length required for a sample.
307 @param max_seqlen Maximum token length; longer sequences are truncated.
308 @param pad_to_max When true, pad sequences up to max_seqlen with zeros.
309 @param dtype NumPy dtype used for exported embeddings.
310 """
311
312 def __init__(
313 self,
314 tokenizer_dir: str,
315 model_dir: Optional[str] = None,
316 device: Optional[str] = None,
317 trust_remote_code: bool = False,
318 min_seqlen: int = 0,
319 max_seqlen: Optional[int] = None,
320 pad_to_max: bool = False,
321 dtype: str = "float32",
322 ):
323 super().__init__()
324 self.tokenizer_dir = tokenizer_dir
325 self.tokenizer = AutoTokenizer.from_pretrained(
326 tokenizer_dir, trust_remote_code=trust_remote_code
327 )
328 model_path = model_dir or tokenizer_dir
329 llm_model = AutoModelForCausalLM.from_pretrained(
330 model_path, trust_remote_code=trust_remote_code
331 )
332 if device is None:
333 device = "cuda" if torch.cuda.is_available() else "cpu"
334 self.device = torch.device(device)
335
336 embedding_module = llm_model.get_input_embeddings()
337 if embedding_module is None:
338 raise ValueError("Loaded model does not expose an input embedding layer.")
339
340 embedding_copy = copy.deepcopy(embedding_module)
341 del embedding_module
342 embedding_copy.to(self.device)
343 embedding_copy.weight.requires_grad_(False)
344 self.embedding_layer = embedding_copy.eval()
345
346 del llm_model
347 gc.collect()
348 if self.device.type == "cuda":
349 torch.cuda.empty_cache()
350
351 self.min_seqlen = max(0, int(min_seqlen))
352 self.max_seqlen = int(max_seqlen) if max_seqlen is not None else None
353 self.pad_to_max = pad_to_max
354 if self.pad_to_max and self.max_seqlen is None:
355 raise ValueError("pad_to_max=True requires max_seqlen to be specified.")
356
357 supported_dtypes = {
358 "float32": np.float32,
359 "float16": np.float16,
360 }
361 if dtype not in supported_dtypes:
362 raise ValueError(
363 f"Unsupported dtype '{dtype}'. Supported values: {sorted(supported_dtypes)}"
364 )
365 self.np_dtype = supported_dtypes[dtype]
366 self.model_name = Path(model_path).name
367
368 def __call__(self, text: str):
369 if not isinstance(text, str):
370 raise TypeError(f"Expected text input as str, but got {type(text)}.")
371
372 token_ids = self.tokenizer(text, return_tensors="pt")["input_ids"]
373 if token_ids.numel() == 0:
374 return None
375
376 token_ids = token_ids.squeeze(0).to(self.device)
377 embedded_text = self.embedding_layer(token_ids)
378
379 if embedded_text.ndim == 1:
380 embedded_text = embedded_text.unsqueeze(0).unsqueeze(0)
381 elif embedded_text.ndim == 2:
382 embedded_text = embedded_text.unsqueeze(0)
383
384 seq_len = embedded_text.shape[1]
385 if seq_len < self.min_seqlen:
386 return None
387
388 if self.max_seqlen is not None and seq_len > self.max_seqlen:
389 embedded_text = embedded_text[:, : self.max_seqlen, :]
390 seq_len = embedded_text.shape[1]
391
392 if self.pad_to_max and seq_len < self.max_seqlen:
393 pad_len = self.max_seqlen - seq_len
394 pad_tensor = torch.zeros(
395 embedded_text.shape[0],
396 pad_len,
397 embedded_text.shape[2],
398 device=embedded_text.device,
399 dtype=embedded_text.dtype,
400 )
401 embedded_text = torch.cat([embedded_text, pad_tensor], dim=1)
402
403 result = (
404 embedded_text.detach().to("cpu").numpy().astype(self.np_dtype, copy=False)
405 )
406 return result
407
408
409@preprocess_fnc
410class Pad:
411 """
412 @brief Pad images to a target shape or to meet a size divisor.
413
414 @param shape tuple[int]. Expected output shape (h, w). Defaults to None.
415 @param size_divisor int. Ensures width and height are divisible by this value. Defaults to None.
416 @param pad_val float. Fill value used during padding. Defaults to 0.0.
417 @param right_bottom bool. When True, pad on the right and bottom only. Defaults to False.
418 """
419
420 def __init__(
421 self,
422 shape=None,
423 size_divisor: int = None,
424 pad_val: float = 0.0,
425 right_bottom: bool = False,
426 ):
427 super().__init__()
428 self.pad_val = pad_val
429 self.size_divisor = size_divisor
430 self.shape = shape
431 self.right_bottom = right_bottom
432
433 def __call__(self, img: np.ndarray):
434 if self.shape is not None:
435 padded_img = impad(
436 img,
437 shape=self.shape,
438 pad_val=self.pad_val,
439 right_bottom=self.right_bottom,
440 )
441 elif self.size_divisor is not None:
442 padded_img = imgpad_to_multiple(
443 img,
444 self.size_divisor,
445 pad_val=self.pad_val,
446 right_bottom=self.right_bottom,
447 )
448 else:
449 raise NotImplementedError(f"Unsupported pad operation.")
450
451 return padded_img
452
453
454@preprocess_fnc
456 """
457 @brief Apply mean/std normalization to images.
458
459 @param mean list[float] or np.ndarray. Normalization mean.
460 @param std list[float] or np.ndarray. Normalization standard deviation.
461 @param to_float_div255 bool. Divide by 255 before normalization when True. Defaults to False.
462 """
463
464 def __init__(
465 self,
466 mean: Union[List[float], np.ndarray],
467 std: Union[List[float], np.ndarray],
468 to_float_div255: bool = False,
469 ):
470 super().__init__()
471 if not isinstance(mean, (np.ndarray, list, tuple)):
472 raise TypeError(
473 f"Expected mean to be np.ndarray or List[float], but got {type(mean)}."
474 )
475
476 if not isinstance(std, (np.ndarray, list, tuple)):
477 raise TypeError(
478 f"Expected std to be np.ndarray or List[float], but got {type(std)}."
479 )
480
481 mean = np.array(mean, dtype=np.float32) if isinstance(mean, List) else mean
482 self.mean = np.float32(mean.reshape(1, -1))
483 std = np.array(std, dtype=np.float32) if isinstance(std, List) else std
484 self.std = np.float32(std.reshape(1, -1))
485 self.to_float_div255 = to_float_div255
486
487 def __call__(self, img: np.ndarray):
488 img = img.copy().astype(np.float32)
489 if self.to_float_div255:
490 img /= 255.0
491
492 normalize_(img, self.mean, self.std)
493 return img
494
495
496@preprocess_fnc
498 def __init__(self, size: List[int], interpolation: str):
499 super().__init__()
500 self.size = size # h, w
501 self.interpolation = interpolation
502
503 def __call__(self, img: np.ndarray):
504 img_tensor = img_npy_to_torch_tensor(img)
505 resized_img_tensor = F.resize(
506 img_tensor,
507 size=self.size,
508 interpolation=TORCH_INTERP_CODES[self.interpolation],
509 )
510 img = torch_tensor_to_img_npy(resized_img_tensor)
511 return img
512
513
514@preprocess_fnc
515class Resize:
516 """
517 @brief Resize images with optional aspect-ratio preservation.
518
519 @param img_scale float or tuple[int, int]. Scaling factor or target size (h, w).
520 @param keep_ratio bool. Preserve aspect ratio when True. Defaults to False.
521 @param interpolation string. Interpolation mode ("nearest", "bilinear", "bicubic", "area", or "lanczos").
522 """
523
524 def __init__(
525 self,
526 img_scale: Union[float, Tuple[int, int]],
527 keep_ratio: bool,
528 interpolation: str,
529 ):
530 super().__init__()
531 self.img_scale = img_scale
532 self.keep_ratio = keep_ratio
533 self.interpolation = interpolation
534
535 def __call__(self, img: np.ndarray):
536 if self.keep_ratio:
537 h, w = img.shape[:2]
538 img = imrescale(
539 img,
540 self.img_scale,
541 return_scale=False,
542 interpolation=self.interpolation,
543 )
544 new_h, new_w = img.shape[:2]
545 w_scale = new_w / w
546 h_scale = new_h / h
547 else:
548 img, w_scale, h_scale = imresize(
549 img, self.img_scale, return_scale=True, interpolation=self.interpolation
550 )
551
552 return img
553
554
555@preprocess_fnc
557 """
558 @brief Perform a centered crop on the image.
559
560 @param size list[int]. Target height and width.
561 """
562
563 def __init__(self, size: List[int]):
564 super().__init__()
565 assert has_same_type(size), "size must consist of same type elements."
566 type_check(size[0], (int))
567 if len(size) == 1:
568 self.size = size * 2
569 elif len(size) == 2:
570 self.size = size
571 else:
572 raise AssertionError(f"Invalid shape of size; {size}")
573
574 def __call__(self, img: np.ndarray):
575 # img_tensor = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0)
576 # img_tensor = F.center_crop(img_tensor, self.size)
577 # result.img = img_tensor.squeeze(0).permute(1, 2, 0).numpy().astype(np.uint8)
578
579 # h, w = img.shape[:2] # h, w, c
580 # h_target = min(self.size[0], h)
581 # w_target = min(self.size[1], w)
582
583 # if h_target == h and w_target == w:
584 # return result
585
586 # x_begin = round(w - w_target/2)
587 # x_end = x_begin + w_target
588 # y_begin = round(h - h_target/2)
589 # y_end = y_begin + h_target
590
591 # result.img = img[y_begin:y_end, x_begin:x_end, :]
592 h, w = img.shape[:2]
593 if (self.size[1] > w) or (self.size[0] > h):
594 padding = [
595 (self.size[1] - w) // 2 if self.size[1] > w else 0,
596 (self.size[0] - h) // 2 if self.size[0] > h else 0,
597 (self.size[1] - w + 1) // 2 if self.size[1] > w else 0,
598 (self.size[0] - h + 1) // 2 if self.size[0] > h else 0,
599 ]
600 img = impad(img, padding=tuple(padding))
601 h, w = img.shape[:2]
602 if (self.size[1] == w) and (self.size[0] == h):
603 return img
604 crop_top = round((h - self.size[0]) / 2.0)
605 crop_left = round((w - self.size[1]) / 2.0)
606 img = img[
607 crop_top : crop_top + self.size[0], crop_left : crop_left + self.size[1], :
608 ]
609 return img
610
611
612@preprocess_fnc
613class YoloPre:
614 """@class YoloPre
615 @brief Apply YOLO-style resize, padding, and normalization to images.
616 @param size Target image size provided as [height, width].
617 """
618
619 def __init__(self, size: List[int]):
620 super().__init__()
621 assert has_same_type(size), "size must consist of same type elements."
622 type_check(size[0], (int))
623 if len(size) == 1:
624 self.size = size * 2
625 elif len(size) == 2:
626 self.size = size
627 else:
628 raise AssertionError(f"Invalid shape of size; {size}")
629
630 def __call__(self, img: np.ndarray):
631 if not isinstance(img, np.ndarray):
632 raise TypeError(f"Expected numpy.ndarray as input, but got {type(img)}.")
633
634 h0, w0 = img.shape[:2]
635 r = min(self.size[0] / h0, self.size[1] / w0)
636 new_unpad = (int(round(w0 * r)), int(round(h0 * r)))
637 dh = self.size[0] - new_unpad[1]
638 dw = self.size[1] - new_unpad[0]
639
640 dw /= 2
641 dh /= 2
642
643 if (img.shape[1], img.shape[0]) != new_unpad:
644 img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
645
646 top = int(round(dh - 0.1))
647 bottom = int(round(dh + 0.1))
648 left = int(round(dw - 0.1))
649 right = int(round(dw + 0.1))
650
651 img = cv2.copyMakeBorder(
652 img,
653 top,
654 bottom,
655 left,
656 right,
657 cv2.BORDER_CONSTANT,
658 value=(114, 114, 114),
659 )
660
661 img = (img / 255.0).astype(np.float32)
662 return img
663
664
665@preprocess_fnc
667 def __init__(self, shape: str = "HWC"):
668 super().__init__()
669 assert shape.lower() in ["hwc", "chw", "bhwc", "bchw"]
670 self.shape = shape
671
672 def __call__(self, img: np.ndarray):
673 if "hwc" in self.shape.lower():
674 pass
675 elif "chw" in self.shape.lower():
676 img = img.transpose(2, 0, 1)
677 if "b" in self.shape.lower():
678 img = np.expand_dims(img, axis=0)
679 return img
680
681
683 """
684 @brief Build a calibration preprocessing pipeline from a YAML or dict configuration.
685
686 @details Supported preprocessing types:
687 - `Datatype`: Selects how input data is loaded. Supported value: `Image`.
688 - `GetImage`: Loads an image tensor via OpenCV or PIL. Must appear before other operators.
689 - `Pre-Order`: Explicit list of preprocessing stages to execute sequentially; entries must match the
690 preprocessing definitions.
691 - `Pad`: Adds padding to the tensor.
692 - `Normalize`: Applies mean/std normalization.
693 - `ResizeTorch`: Resizes images using `torchvision.transforms.functional.resize`.
694 - `Resize`: Resizes images using `cv2.resize`.
695 - `CenterCrop`: Performs a center crop.
696 - `SetOrder`: Reorders axes; must be the final operator.
697
698 @par YAML structure
699 @code{.yaml}
700 [Pre-processing Type]
701 [Parameter]: [Argument]
702 ...
703
704 # Example
705 Datatype: Image
706 GetImage:
707 to_float32: false
708 channel_order: RGB
709 Pre-Order: [ResizeTorch, CenterCrop, Normalize, SetOrder]
710 Pre-processing:
711 ResizeTorch:
712 size: [256, 256]
713 interpolation: blinear
714 CenterCrop:
715 size: [224, 224]
716 Normalize:
717 mean: [0.485, 0.456, 0.406]
718 std: [0.229, 0.224, 0.225]
719 to_float_div255: true
720 SetOrder:
721 shape: HWC
722 @endcode
723
724 @par Operator parameters
725 - `GetImage`: `to_float32` (bool, default False); `channel_order` (string, default "bgr").
726 - `Pad`: `shape` (tuple[int]); `size_divisor` (int); `pad_val` (float, default 0); `right_bottom` (bool, default False).
727 - `Normalize`: `mean` (list/ndarray); `std` (list/ndarray); `to_float_div255` (bool, default False).
728 - `ResizeTorch`: `size` (list[int]); `interpolation` (nearest|bilinear|bicubic|box|hamming|lanczos).
729 - `Resize`: `img_scale` (float or tuple[int, int]); `keep_ratio` (bool, default False); `interpolation`
730 (nearest|bilinear|bicubic|area|lanczos).
731 - `CenterCrop`: `size` (list[int]).
732 - `SetOrder`: `shape` (HWC|CHW|BHWC|BCHW, default HWC).
733
734 @param args dict. Parsed preprocessing configuration.
735 @param to_tensor bool. When True, converts the resulting NumPy array to a Torch tensor.
736 """
737
738 def __init__(self, args, to_tensor=False):
739 self.preprocess = []
740 self.to_tensor = to_tensor
741 self.args = args
742 self.get_pre_list(args)
743
744 def __call__(self, img):
745 for fnc in self.preprocess:
746 img = fnc(img)
747 if self.to_tensor:
748 img = img_npy_to_torch_tensor(img)
749 return img
750
751 def __repre__(self):
752 msg = "Preprocess Function\n"
753 for fnc_type, fnc_args in self.args.items():
754 msg += f"- {fnc_type}\n"
755 for k, v in fnc_args.items():
756 msg += f"\t{k}: {v}\n"
757 msg += f"- Return as Tensor: {self.to_tensor}"
758 return msg
759
760 def __str__(self):
761 return self.__repre__()
762
763 def get_pre_list(self, args):
764 order = self.check_yaml_pre(args)
765 self.preprocess.append(
766 __preprocs__[self.datatype][self.loadtype.lower()](**args[self.loadtype])
767 )
768 for fnc_type in order:
769 fnc_args = args["Pre-processing"][fnc_type]
770 fnc_type = fnc_type.lower()
771 if fnc_type not in __preprocs__[self.datatype].keys():
772 raise KeyError(
773 f"{fnc_type} is already registered in {__preprocs__.keys()}."
774 )
775 fnc = (
776 __preprocs__[self.datatype][fnc_type](**fnc_args)
777 if inspect.isclass(__preprocs__[self.datatype][fnc_type])
778 else partial(__preprocs__[self.datatype][fnc_type], **fnc_args)
779 )
780 self.preprocess.append(fnc)
781
782 def check_yaml_pre(self, args):
783 assert (
784 "Pre-processing" in args
785 ), "Write the Pre-processing block in the yaml file."
786
787 preproc_cfg = args["Pre-processing"]
788 pre_order = args.get("Pre-Order")
789
790 if pre_order is None:
791 pre_order = list(preproc_cfg.keys())
792 else:
793 assert not set(pre_order) - set(
794 preproc_cfg.keys()
795 ), "All elements of Pre-Order must be included in pre-processing. check the yaml file."
796 assert args.get(
797 "Datatype", 0
798 ), "Write the Datatype in the yaml file, Datatype is not writen in the yaml file and we support 'Image' and 'Npy'."
799
800 self.datatype = args["Datatype"]
801 self.loadtype = DTYPE_GETDATA_MAP[self.datatype]
802
803 pre_set = set([name.lower() for name in pre_order])
804 pre_not_include = pre_set - set(__preprocs__[self.datatype].keys())
805
806 if self.loadtype not in args.keys():
807 raise Exception(
808 f"Datatype is {self.datatype}, but getting data method is not {self.loadtype}"
809 )
810 elif pre_not_include:
811 raise Exception(
812 f"{pre_not_include} is not supported if data type is {self.datatype}"
813 )
814 else:
815 return pre_order
816
817
818
Extract text strings from raw inputs.
Convert tokenized text into embeddings using a Hugging Face model.
Apply YOLO-style resize, padding, and normalization to images.
Perform a centered crop on the image.
Retrieve an image tensor from a file path or NumPy array using OpenCV.
np.ndarray __call__(self, Union[np.ndarray, str] img_input)
Args: img_input (Union[np.ndarray, str]): Input image path or numpy tensor.
get_image_data(self, str img_input)
Load NumPy data from an in-memory array or a .npy file.
Definition preprocess.py:37
np.ndarray __call__(self, Union[np.ndarray, str] npy_input)
Args: npy_input (Union[np.ndarray, str]): Input .npy path or numpy data.
Definition preprocess.py:45
Apply mean/std normalization to images.
Pad NumPy arrays to a desired shape.
Definition preprocess.py:91
Pad images to a target shape or to meet a size divisor.
Resize images with optional aspect-ratio preservation.
Build a calibration preprocessing pipeline from a YAML or dict configuration.