preprocess.py Source File

preprocess.py Source File#

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