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 f
"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'bgr' format."
206 f
"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'rgb' format."
215 f
"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'rgba' format."
220 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."
227 f
"The input image is grayscale with single channel. Therefore, data cannot be loaded in 'bgra' format."
232 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."
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(f
"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.