65 mean: Union[List[float], np.ndarray],
66 std: Union[List[float], np.ndarray],
67 to_float_div255: bool =
False,
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
77 def __call__(self, img: np.ndarray):
78 img = img.copy().astype(np.float32)
82 normalize_(img, self.
mean, self.
std)
85 f
"Calling the '{self.__class__.__name__}', normalization process faild. check the img shape:{img.shape}, mean:{self.mean}, and std:{self.std}"
89@partial(preprocess_fnc, Datatype="Npy")
92 @brief Pad NumPy arrays to a desired shape.
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.
100 self, shape: List[int], padding_type: str, pad_val: Optional[float] = 0
102 assert padding_type
in [
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)
110 def __call__(self, data: np.ndarray):
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)
118 ).min() >= 0,
"Padded data shape must be larger than original shape"
122 residual_pad.max() // 2 + 1,
127 data = np.pad(data, residual_pad.max() // 2 + 1,
"reflect")
129 raise NotImplementedError(
130 f
"padding_type: {self.padding_type}, padding_type must be 'constant' or 'reflect'"
135 def slice_padded_data(self, data):
136 residual_slice = np.array(data.shape) - self.
shape
138 slice(residual_slice[i] // 2, residual_slice[i] // 2 + self.
shape[i],
None)
139 for i
in range(data.ndim)
142 return data[tuple(slice_list)]
148 @brief Retrieve an image tensor from a file path or NumPy array using OpenCV.
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".
154 def __init__(self, to_float32: bool =
False, channel_order: str =
"bgr"):
157 assert channel_order.lower()
in [
"bgr",
"rgb",
"bw",
"rgba",
"brga"]
160 from cv2
import cvtColor, COLOR_BGR2GRAY, COLOR_BGRA2GRAY, COLOR_BGRA2BGR, COLOR_BGR2RGB, COLOR_BGRA2RGB, COLOR_BGRA2RGBA, COLOR_BGR2BGRA, COLOR_BGR2RGBA
163 "Please install Python OpenCV (cv2) to use the resize preprocessing."
175 def __call__(self, img_input: Union[np.ndarray, str]) -> np.ndarray:
178 img_input (Union[np.ndarray, str]): Input image path or numpy tensor.
181 keepdims: Contains image numpy tensor and image meta such as "ori_shape".
183 if isinstance(img_input, str):
193 img = np.expand_dims(img, axis=-1)
197 "The input image is grayscale with single channel. Therefore, data cannot be loaded in 'bgr' format."
206 "The input image is grayscale with single channel. Therefore, data cannot be loaded in 'rgb' format."
215 "The input image is grayscale with single channel. Therefore, data cannot be loaded in 'rgba' format."
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."
227 "The input image is grayscale with single channel. Therefore, data cannot be loaded in 'bgra' format."
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."
237 raise NotImplementedError(
238 f
"Got unsupported channel_order: {self.channel_order}."
240 elif isinstance(img_input, np.ndarray):
244 f
"Expected str or np.ndarray img_input, but got {type(img_input)}."
248 img = img.astype(np.float32)
252 def get_image_data(self, img_input: str):
254 from cv2
import imread, IMREAD_COLOR
257 "Please install Python OpenCV (cv2) to use the resize preprocessing."
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:
264 elif len(img.shape) == 3:
265 if img.shape[-1] == 3:
267 elif img.shape[-1] == 4:
270 raise ValueError(
"the number of image channel must be 1 or 3 or 4.")
275@partial(preprocess_fnc, Datatype="LLM")
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.
283 def __init__(self, text_key: Optional[str] =
None, strip: bool =
True):
285 self.text_key = text_key
288 def __call__(self, text_input: Union[str, Dict[str, str]]):
289 if isinstance(text_input, dict):
291 if self.text_key
not in text_input:
293 f
"Key '{self.text_key}' not found in text input dictionary."
295 text = text_input[self.text_key]
297 candidates = [v
for v
in text_input.values()
if isinstance(v, str)]
300 "No string value found in input dictionary for text extraction."
303 elif isinstance(text_input, str):
304 if os.path.isfile(text_input):
305 with open(text_input,
"r", encoding=
"utf-8")
as f:
311 f
"Expected str or dict[str, str] text_input, but got {type(text_input)}."
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.
336 model_dir: Optional[str] =
None,
337 device: Optional[str] =
None,
338 trust_remote_code: bool =
False,
340 max_seqlen: Optional[int] =
None,
341 pad_to_max: bool =
False,
342 dtype: str =
"float32",
345 self.tokenizer_dir = tokenizer_dir
347 from transformers
import AutoTokenizer, AutoModelForCausalLM
350 "Failed to import transformers. Please refer to the Mobilint Docker environment and install an appropriate version of transformers."
352 self.tokenizer = AutoTokenizer.from_pretrained(
353 tokenizer_dir, trust_remote_code=trust_remote_code
355 model_path = model_dir
or tokenizer_dir
356 llm_model = AutoModelForCausalLM.from_pretrained(
357 model_path, trust_remote_code=trust_remote_code
360 device =
"cuda" if torch.cuda.is_available()
else "cpu"
361 self.device = torch.device(device)
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.")
367 embedding_copy = copy.deepcopy(embedding_module)
369 embedding_copy.to(self.device)
370 embedding_copy.weight.requires_grad_(
False)
371 self.embedding_layer = embedding_copy.eval()
375 if self.device.type ==
"cuda":
376 torch.cuda.empty_cache()
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.")
385 "float32": np.float32,
386 "float16": np.float16,
388 if dtype
not in supported_dtypes:
390 f
"Unsupported dtype '{dtype}'. Supported values: {sorted(supported_dtypes)}"
392 self.np_dtype = supported_dtypes[dtype]
393 self.model_name = Path(model_path).name
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)}.")
399 token_ids = self.tokenizer(text, return_tensors=
"pt")[
"input_ids"]
400 if token_ids.numel() == 0:
403 token_ids = token_ids.squeeze(0).to(self.device)
404 embedded_text = self.embedding_layer(token_ids)
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)
411 seq_len = embedded_text.shape[1]
412 if seq_len < self.min_seqlen:
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]
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],
424 embedded_text.shape[2],
425 device=embedded_text.device,
426 dtype=embedded_text.dtype,
428 embedded_text = torch.cat([embedded_text, pad_tensor], dim=1)
431 embedded_text.detach().to(
"cpu").numpy().astype(self.np_dtype, copy=
False)
484 @brief Apply mean/std normalization to images.
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.
493 mean: Union[List[float], np.ndarray],
494 std: Union[List[float], np.ndarray],
495 to_float_div255: bool =
False,
498 if not isinstance(mean, (np.ndarray, list, tuple)):
500 f
"Expected mean to be np.ndarray or List[float], but got {type(mean)}."
503 if not isinstance(std, (np.ndarray, list, tuple)):
505 f
"Expected std to be np.ndarray or List[float], but got {type(std)}."
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))
514 def __call__(self, img: np.ndarray):
515 img = img.copy().astype(np.float32)
519 normalize_(img, self.
mean, self.
std)
525 def __init__(self, size: List[int], interpolation: str):
530 def __call__(self, img: np.ndarray):
531 img_tensor = img_npy_to_torch_tensor(img)
532 resized_img_tensor = F.resize(
537 img = torch_tensor_to_img_npy(resized_img_tensor)
544 @brief Resize images with optional aspect-ratio preservation.
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").
553 img_scale: Union[float, Tuple[int, int]],
562 def __call__(self, img: np.ndarray):
571 new_h, new_w = img.shape[:2]
575 img, w_scale, h_scale = imresize(
585 @brief Perform a centered crop on the image.
587 @param size list[int]. Target height and width.
590 def __init__(self, size: List[int]):
592 assert has_same_type(size),
"size must consist of same type elements."
593 type_check(size[0], (int))
599 raise AssertionError(f
"Invalid shape of size; {size}")
601 def __call__(self, img: np.ndarray):
620 if (self.
size[1] > w)
or (self.
size[0] > h):
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,
627 img = impad(img, padding=tuple(padding))
629 if (self.
size[1] == w)
and (self.
size[0] == h):
631 crop_top = round((h - self.
size[0]) / 2.0)
632 crop_left = round((w - self.
size[1]) / 2.0)
634 crop_top : crop_top + self.
size[0], crop_left : crop_left + self.
size[1], :
642 @brief Apply YOLO-style resize, padding, and normalization to images.
643 @param size Target image size provided as [height, width].
646 def __init__(self, size: List[int]):
648 assert has_same_type(size),
"size must consist of same type elements."
649 type_check(size[0], (int))
655 raise AssertionError(f
"Invalid shape of size; {size}")
657 from cv2
import resize, copyMakeBorder, INTER_LINEAR, BORDER_CONSTANT
660 "Please install Python OpenCV (cv2) to use the resize preprocessing."
663 self.copyMakeBorder = copyMakeBorder
664 self.INTER_LINEAR = INTER_LINEAR
665 self.BORDER_CONSTANT = BORDER_CONSTANT
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)}.")
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]
680 if (img.shape[1], img.shape[0]) != new_unpad:
681 img = self.resize(img, new_unpad, interpolation=self.INTER_LINEAR)
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))
688 img = self.copyMakeBorder(
694 self.BORDER_CONSTANT,
695 value=(114, 114, 114),
698 img = (img / 255.0).astype(np.float32)
721 @brief Build a calibration preprocessing pipeline from a YAML or dict configuration.
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.
737 [Pre-processing Type]
738 [Parameter]: [Argument]
746 Pre-Order: [ResizeTorch, CenterCrop, Normalize, SetOrder]
750 interpolation: blinear
754 mean: [0.485, 0.456, 0.406]
755 std: [0.229, 0.224, 0.225]
756 to_float_div255: true
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).
771 @param args dict. Parsed preprocessing configuration.
772 @param to_tensor bool. When True, converts the resulting NumPy array to a Torch tensor.
775 def __init__(self, args, to_tensor=False):
781 def __call__(self, img):
785 img = img_npy_to_torch_tensor(img)
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}"
800 def get_pre_list(self, args):
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():
810 f
"{fnc_type} is already registered in {__preprocs__.keys()}."
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)
819 def check_yaml_pre(self, args):
821 "Pre-processing" in args
822 ),
"Write the Pre-processing block in the yaml file."
824 preproc_cfg = args[
"Pre-processing"]
825 pre_order = args.get(
"Pre-Order")
827 if pre_order
is None:
828 pre_order = list(preproc_cfg.keys())
830 assert not set(pre_order) - set(
832 ),
"All elements of Pre-Order must be included in pre-processing. check the yaml file."
835 ),
"Write the Datatype in the yaml file, Datatype is not writen in the yaml file and we support 'Image' and 'Npy'."
840 pre_set = set([name.lower()
for name
in pre_order])
841 pre_not_include = pre_set - set(__preprocs__[self.
datatype].keys())
843 if self.
loadtype not in args.keys():
845 f
"Datatype is {self.datatype}, but getting data method is not {self.loadtype}"
847 elif pre_not_include:
849 f
"{pre_not_include} is not supported if data type is {self.datatype}"
Extract text strings from raw inputs.