preprocess.py Source File

preprocess.py Source File#

Mobilint SDK qb Compiler: preprocess.py Source File
Mobilint SDK qb Compiler v0.12.0.0
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
17
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 try:
326 from transformers import AutoTokenizer, AutoModelForCausalLM
327 except:
328 raise ImportError(
329 "Failed to import transformers. Please refer to the Mobilint Docker environment and install an appropriate version of transformers."
330 )
331 self.tokenizer = AutoTokenizer.from_pretrained(
332 tokenizer_dir, trust_remote_code=trust_remote_code
333 )
334 model_path = model_dir or tokenizer_dir
335 llm_model = AutoModelForCausalLM.from_pretrained(
336 model_path, trust_remote_code=trust_remote_code
337 )
338 if device is None:
339 device = "cuda" if torch.cuda.is_available() else "cpu"
340 self.device = torch.device(device)
341
342 embedding_module = llm_model.get_input_embeddings()
343 if embedding_module is None:
344 raise ValueError("Loaded model does not expose an input embedding layer.")
345
346 embedding_copy = copy.deepcopy(embedding_module)
347 del embedding_module
348 embedding_copy.to(self.device)
349 embedding_copy.weight.requires_grad_(False)
350 self.embedding_layer = embedding_copy.eval()
351
352 del llm_model
353 gc.collect()
354 if self.device.type == "cuda":
355 torch.cuda.empty_cache()
356
357 self.min_seqlen = max(0, int(min_seqlen))
358 self.max_seqlen = int(max_seqlen) if max_seqlen is not None else None
359 self.pad_to_max = pad_to_max
360 if self.pad_to_max and self.max_seqlen is None:
361 raise ValueError("pad_to_max=True requires max_seqlen to be specified.")
362
363 supported_dtypes = {
364 "float32": np.float32,
365 "float16": np.float16,
366 }
367 if dtype not in supported_dtypes:
368 raise ValueError(
369 f"Unsupported dtype '{dtype}'. Supported values: {sorted(supported_dtypes)}"
370 )
371 self.np_dtype = supported_dtypes[dtype]
372 self.model_name = Path(model_path).name
373
374 def __call__(self, text: str):
375 if not isinstance(text, str):
376 raise TypeError(f"Expected text input as str, but got {type(text)}.")
377
378 token_ids = self.tokenizer(text, return_tensors="pt")["input_ids"]
379 if token_ids.numel() == 0:
380 return None
381
382 token_ids = token_ids.squeeze(0).to(self.device)
383 embedded_text = self.embedding_layer(token_ids)
384
385 if embedded_text.ndim == 1:
386 embedded_text = embedded_text.unsqueeze(0).unsqueeze(0)
387 elif embedded_text.ndim == 2:
388 embedded_text = embedded_text.unsqueeze(0)
389
390 seq_len = embedded_text.shape[1]
391 if seq_len < self.min_seqlen:
392 return None
393
394 if self.max_seqlen is not None and seq_len > self.max_seqlen:
395 embedded_text = embedded_text[:, : self.max_seqlen, :]
396 seq_len = embedded_text.shape[1]
397
398 if self.pad_to_max and seq_len < self.max_seqlen:
399 pad_len = self.max_seqlen - seq_len
400 pad_tensor = torch.zeros(
401 embedded_text.shape[0],
402 pad_len,
403 embedded_text.shape[2],
404 device=embedded_text.device,
405 dtype=embedded_text.dtype,
406 )
407 embedded_text = torch.cat([embedded_text, pad_tensor], dim=1)
408
409 result = (
410 embedded_text.detach().to("cpu").numpy().astype(self.np_dtype, copy=False)
411 )
412 return result
413
414
415@preprocess_fnc
416class Pad:
417 """
418 @brief Pad images to a target shape or to meet a size divisor.
419
420 @param shape tuple[int]. Expected output shape (h, w). Defaults to None.
421 @param size_divisor int. Ensures width and height are divisible by this value. Defaults to None.
422 @param pad_val float. Fill value used during padding. Defaults to 0.0.
423 @param right_bottom bool. When True, pad on the right and bottom only. Defaults to False.
424 """
425
426 def __init__(
427 self,
428 shape=None,
429 size_divisor: int = None,
430 pad_val: float = 0.0,
431 right_bottom: bool = False,
432 ):
433 super().__init__()
434 self.pad_val = pad_val
435 self.size_divisor = size_divisor
436 self.shape = shape
437 self.right_bottom = right_bottom
438
439 def __call__(self, img: np.ndarray):
440 if self.shape is not None:
441 padded_img = impad(
442 img,
443 shape=self.shape,
444 pad_val=self.pad_val,
445 right_bottom=self.right_bottom,
446 )
447 elif self.size_divisor is not None:
448 padded_img = imgpad_to_multiple(
449 img,
450 self.size_divisor,
451 pad_val=self.pad_val,
452 right_bottom=self.right_bottom,
453 )
454 else:
455 raise NotImplementedError(f"Unsupported pad operation.")
456
457 return padded_img
458
459
460@preprocess_fnc
462 """
463 @brief Apply mean/std normalization to images.
464
465 @param mean list[float] or np.ndarray. Normalization mean.
466 @param std list[float] or np.ndarray. Normalization standard deviation.
467 @param to_float_div255 bool. Divide by 255 before normalization when True. Defaults to False.
468 """
469
470 def __init__(
471 self,
472 mean: Union[List[float], np.ndarray],
473 std: Union[List[float], np.ndarray],
474 to_float_div255: bool = False,
475 ):
476 super().__init__()
477 if not isinstance(mean, (np.ndarray, list, tuple)):
478 raise TypeError(
479 f"Expected mean to be np.ndarray or List[float], but got {type(mean)}."
480 )
481
482 if not isinstance(std, (np.ndarray, list, tuple)):
483 raise TypeError(
484 f"Expected std to be np.ndarray or List[float], but got {type(std)}."
485 )
486
487 mean = np.array(mean, dtype=np.float32) if isinstance(mean, List) else mean
488 self.mean = np.float32(mean.reshape(1, -1))
489 std = np.array(std, dtype=np.float32) if isinstance(std, List) else std
490 self.std = np.float32(std.reshape(1, -1))
491 self.to_float_div255 = to_float_div255
492
493 def __call__(self, img: np.ndarray):
494 img = img.copy().astype(np.float32)
495 if self.to_float_div255:
496 img /= 255.0
497
498 normalize_(img, self.mean, self.std)
499 return img
500
501
502@preprocess_fnc
504 def __init__(self, size: List[int], interpolation: str):
505 super().__init__()
506 self.size = size # h, w
507 self.interpolation = interpolation
508
509 def __call__(self, img: np.ndarray):
510 img_tensor = img_npy_to_torch_tensor(img)
511 resized_img_tensor = F.resize(
512 img_tensor,
513 size=self.size,
514 interpolation=TORCH_INTERP_CODES[self.interpolation],
515 )
516 img = torch_tensor_to_img_npy(resized_img_tensor)
517 return img
518
519
520@preprocess_fnc
521class Resize:
522 """
523 @brief Resize images with optional aspect-ratio preservation.
524
525 @param img_scale float or tuple[int, int]. Scaling factor or target size (h, w).
526 @param keep_ratio bool. Preserve aspect ratio when True. Defaults to False.
527 @param interpolation string. Interpolation mode ("nearest", "bilinear", "bicubic", "area", or "lanczos").
528 """
529
530 def __init__(
531 self,
532 img_scale: Union[float, Tuple[int, int]],
533 keep_ratio: bool,
534 interpolation: str,
535 ):
536 super().__init__()
537 self.img_scale = img_scale
538 self.keep_ratio = keep_ratio
539 self.interpolation = interpolation
540
541 def __call__(self, img: np.ndarray):
542 if self.keep_ratio:
543 h, w = img.shape[:2]
544 img = imrescale(
545 img,
546 self.img_scale,
547 return_scale=False,
548 interpolation=self.interpolation,
549 )
550 new_h, new_w = img.shape[:2]
551 w_scale = new_w / w
552 h_scale = new_h / h
553 else:
554 img, w_scale, h_scale = imresize(
555 img, self.img_scale, return_scale=True, interpolation=self.interpolation
556 )
557
558 return img
559
560
561@preprocess_fnc
563 """
564 @brief Perform a centered crop on the image.
565
566 @param size list[int]. Target height and width.
567 """
568
569 def __init__(self, size: List[int]):
570 super().__init__()
571 assert has_same_type(size), "size must consist of same type elements."
572 type_check(size[0], (int))
573 if len(size) == 1:
574 self.size = size * 2
575 elif len(size) == 2:
576 self.size = size
577 else:
578 raise AssertionError(f"Invalid shape of size; {size}")
579
580 def __call__(self, img: np.ndarray):
581 # img_tensor = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0)
582 # img_tensor = F.center_crop(img_tensor, self.size)
583 # result.img = img_tensor.squeeze(0).permute(1, 2, 0).numpy().astype(np.uint8)
584
585 # h, w = img.shape[:2] # h, w, c
586 # h_target = min(self.size[0], h)
587 # w_target = min(self.size[1], w)
588
589 # if h_target == h and w_target == w:
590 # return result
591
592 # x_begin = round(w - w_target/2)
593 # x_end = x_begin + w_target
594 # y_begin = round(h - h_target/2)
595 # y_end = y_begin + h_target
596
597 # result.img = img[y_begin:y_end, x_begin:x_end, :]
598 h, w = img.shape[:2]
599 if (self.size[1] > w) or (self.size[0] > h):
600 padding = [
601 (self.size[1] - w) // 2 if self.size[1] > w else 0,
602 (self.size[0] - h) // 2 if self.size[0] > h else 0,
603 (self.size[1] - w + 1) // 2 if self.size[1] > w else 0,
604 (self.size[0] - h + 1) // 2 if self.size[0] > h else 0,
605 ]
606 img = impad(img, padding=tuple(padding))
607 h, w = img.shape[:2]
608 if (self.size[1] == w) and (self.size[0] == h):
609 return img
610 crop_top = round((h - self.size[0]) / 2.0)
611 crop_left = round((w - self.size[1]) / 2.0)
612 img = img[
613 crop_top : crop_top + self.size[0], crop_left : crop_left + self.size[1], :
614 ]
615 return img
616
617
618@preprocess_fnc
619class YoloPre:
620 """@class YoloPre
621 @brief Apply YOLO-style resize, padding, and normalization to images.
622 @param size Target image size provided as [height, width].
623 """
624
625 def __init__(self, size: List[int]):
626 super().__init__()
627 assert has_same_type(size), "size must consist of same type elements."
628 type_check(size[0], (int))
629 if len(size) == 1:
630 self.size = size * 2
631 elif len(size) == 2:
632 self.size = size
633 else:
634 raise AssertionError(f"Invalid shape of size; {size}")
635
636 def __call__(self, img: np.ndarray):
637 if not isinstance(img, np.ndarray):
638 raise TypeError(f"Expected numpy.ndarray as input, but got {type(img)}.")
639
640 h0, w0 = img.shape[:2]
641 r = min(self.size[0] / h0, self.size[1] / w0)
642 new_unpad = (int(round(w0 * r)), int(round(h0 * r)))
643 dh = self.size[0] - new_unpad[1]
644 dw = self.size[1] - new_unpad[0]
645
646 dw /= 2
647 dh /= 2
648
649 if (img.shape[1], img.shape[0]) != new_unpad:
650 img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
651
652 top = int(round(dh - 0.1))
653 bottom = int(round(dh + 0.1))
654 left = int(round(dw - 0.1))
655 right = int(round(dw + 0.1))
656
657 img = cv2.copyMakeBorder(
658 img,
659 top,
660 bottom,
661 left,
662 right,
663 cv2.BORDER_CONSTANT,
664 value=(114, 114, 114),
665 )
666
667 img = (img / 255.0).astype(np.float32)
668 return img
669
670
671@preprocess_fnc
673 def __init__(self, shape: str = "HWC"):
674 super().__init__()
675 assert shape.lower() in ["hwc", "chw", "bhwc", "bchw"]
676 self.shape = shape
677
678 def __call__(self, img: np.ndarray):
679 if "hwc" in self.shape.lower():
680 pass
681 elif "chw" in self.shape.lower():
682 img = img.transpose(2, 0, 1)
683 if "b" in self.shape.lower():
684 img = np.expand_dims(img, axis=0)
685 return img
686
687
689 """
690 @brief Build a calibration preprocessing pipeline from a YAML or dict configuration.
691
692 @details Supported preprocessing types:
693 - `Datatype`: Selects how input data is loaded. Supported value: `Image`.
694 - `GetImage`: Loads an image tensor via OpenCV or PIL. Must appear before other operators.
695 - `Pre-Order`: Explicit list of preprocessing stages to execute sequentially; entries must match the
696 preprocessing definitions.
697 - `Pad`: Adds padding to the tensor.
698 - `Normalize`: Applies mean/std normalization.
699 - `ResizeTorch`: Resizes images using `torchvision.transforms.functional.resize`.
700 - `Resize`: Resizes images using `cv2.resize`.
701 - `CenterCrop`: Performs a center crop.
702 - `SetOrder`: Reorders axes; must be the final operator.
703
704 @par YAML structure
705 @code{.yaml}
706 [Pre-processing Type]
707 [Parameter]: [Argument]
708 ...
709
710 # Example
711 Datatype: Image
712 GetImage:
713 to_float32: false
714 channel_order: RGB
715 Pre-Order: [ResizeTorch, CenterCrop, Normalize, SetOrder]
716 Pre-processing:
717 ResizeTorch:
718 size: [256, 256]
719 interpolation: blinear
720 CenterCrop:
721 size: [224, 224]
722 Normalize:
723 mean: [0.485, 0.456, 0.406]
724 std: [0.229, 0.224, 0.225]
725 to_float_div255: true
726 SetOrder:
727 shape: HWC
728 @endcode
729
730 @par Operator parameters
731 - `GetImage`: `to_float32` (bool, default False); `channel_order` (string, default "bgr").
732 - `Pad`: `shape` (tuple[int]); `size_divisor` (int); `pad_val` (float, default 0); `right_bottom` (bool, default False).
733 - `Normalize`: `mean` (list/ndarray); `std` (list/ndarray); `to_float_div255` (bool, default False).
734 - `ResizeTorch`: `size` (list[int]); `interpolation` (nearest|bilinear|bicubic|box|hamming|lanczos).
735 - `Resize`: `img_scale` (float or tuple[int, int]); `keep_ratio` (bool, default False); `interpolation`
736 (nearest|bilinear|bicubic|area|lanczos).
737 - `CenterCrop`: `size` (list[int]).
738 - `SetOrder`: `shape` (HWC|CHW|BHWC|BCHW, default HWC).
739
740 @param args dict. Parsed preprocessing configuration.
741 @param to_tensor bool. When True, converts the resulting NumPy array to a Torch tensor.
742 """
743
744 def __init__(self, args, to_tensor=False):
745 self.preprocess = []
746 self.to_tensor = to_tensor
747 self.args = args
748 self.get_pre_list(args)
749
750 def __call__(self, img):
751 for fnc in self.preprocess:
752 img = fnc(img)
753 if self.to_tensor:
754 img = img_npy_to_torch_tensor(img)
755 return img
756
757 def __repre__(self):
758 msg = "Preprocess Function\n"
759 for fnc_type, fnc_args in self.args.items():
760 msg += f"- {fnc_type}\n"
761 for k, v in fnc_args.items():
762 msg += f"\t{k}: {v}\n"
763 msg += f"- Return as Tensor: {self.to_tensor}"
764 return msg
765
766 def __str__(self):
767 return self.__repre__()
768
769 def get_pre_list(self, args):
770 order = self.check_yaml_pre(args)
771 self.preprocess.append(
772 __preprocs__[self.datatype][self.loadtype.lower()](**args[self.loadtype])
773 )
774 for fnc_type in order:
775 fnc_args = args["Pre-processing"][fnc_type]
776 fnc_type = fnc_type.lower()
777 if fnc_type not in __preprocs__[self.datatype].keys():
778 raise KeyError(
779 f"{fnc_type} is already registered in {__preprocs__.keys()}."
780 )
781 fnc = (
782 __preprocs__[self.datatype][fnc_type](**fnc_args)
783 if inspect.isclass(__preprocs__[self.datatype][fnc_type])
784 else partial(__preprocs__[self.datatype][fnc_type], **fnc_args)
785 )
786 self.preprocess.append(fnc)
787
788 def check_yaml_pre(self, args):
789 assert (
790 "Pre-processing" in args
791 ), "Write the Pre-processing block in the yaml file."
792
793 preproc_cfg = args["Pre-processing"]
794 pre_order = args.get("Pre-Order")
795
796 if pre_order is None:
797 pre_order = list(preproc_cfg.keys())
798 else:
799 assert not set(pre_order) - set(
800 preproc_cfg.keys()
801 ), "All elements of Pre-Order must be included in pre-processing. check the yaml file."
802 assert args.get(
803 "Datatype", 0
804 ), "Write the Datatype in the yaml file, Datatype is not writen in the yaml file and we support 'Image' and 'Npy'."
805
806 self.datatype = args["Datatype"]
807 self.loadtype = DTYPE_GETDATA_MAP[self.datatype]
808
809 pre_set = set([name.lower() for name in pre_order])
810 pre_not_include = pre_set - set(__preprocs__[self.datatype].keys())
811
812 if self.loadtype not in args.keys():
813 raise Exception(
814 f"Datatype is {self.datatype}, but getting data method is not {self.loadtype}"
815 )
816 elif pre_not_include:
817 raise Exception(
818 f"{pre_not_include} is not supported if data type is {self.datatype}"
819 )
820 else:
821 return pre_order
822
823
824
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.