66 mean: Union[List[float], np.ndarray],
67 std: Union[List[float], np.ndarray],
68 to_float_div255: bool =
False,
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
78 def __call__(self, img: np.ndarray):
79 img = img.copy().astype(np.float32)
83 normalize_(img, self.
mean, self.
std)
86 f
"Calling the '{self.__class__.__name__}', normalization process faild. check the img shape:{img.shape}, mean:{self.mean}, and std:{self.std}"
90@partial(preprocess_fnc, Datatype="Npy")
93 @brief Pad NumPy arrays to a desired shape.
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.
101 self, shape: List[int], padding_type: str, pad_val: Optional[float] = 0
103 assert padding_type
in [
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)
111 def __call__(self, data: np.ndarray):
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)
119 ).min() >= 0,
"Padded data shape must be larger than original shape"
123 residual_pad.max() // 2 + 1,
128 data = np.pad(data, residual_pad.max() // 2 + 1,
"reflect")
130 raise NotImplementedError(
131 f
"padding_type: {self.padding_type}, padding_type must be 'constant' or 'reflect'"
136 def slice_padded_data(self, data):
137 residual_slice = np.array(data.shape) - self.
shape
139 slice(residual_slice[i] // 2, residual_slice[i] // 2 + self.
shape[i],
None)
140 for i
in range(data.ndim)
143 return data[tuple(slice_list)]
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.
262 def __init__(self, text_key: Optional[str] =
None, strip: bool =
True):
264 self.text_key = text_key
267 def __call__(self, text_input: Union[str, Dict[str, str]]):
268 if isinstance(text_input, dict):
270 if self.text_key
not in text_input:
272 f
"Key '{self.text_key}' not found in text input dictionary."
274 text = text_input[self.text_key]
276 candidates = [v
for v
in text_input.values()
if isinstance(v, str)]
279 "No string value found in input dictionary for text extraction."
282 elif isinstance(text_input, str):
283 if os.path.isfile(text_input):
284 with open(text_input,
"r", encoding=
"utf-8")
as f:
290 f
"Expected str or dict[str, str] text_input, but got {type(text_input)}."
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.
315 model_dir: Optional[str] =
None,
316 device: Optional[str] =
None,
317 trust_remote_code: bool =
False,
319 max_seqlen: Optional[int] =
None,
320 pad_to_max: bool =
False,
321 dtype: str =
"float32",
324 self.tokenizer_dir = tokenizer_dir
326 from transformers
import AutoTokenizer, AutoModelForCausalLM
329 "Failed to import transformers. Please refer to the Mobilint Docker environment and install an appropriate version of transformers."
331 self.tokenizer = AutoTokenizer.from_pretrained(
332 tokenizer_dir, trust_remote_code=trust_remote_code
334 model_path = model_dir
or tokenizer_dir
335 llm_model = AutoModelForCausalLM.from_pretrained(
336 model_path, trust_remote_code=trust_remote_code
339 device =
"cuda" if torch.cuda.is_available()
else "cpu"
340 self.device = torch.device(device)
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.")
346 embedding_copy = copy.deepcopy(embedding_module)
348 embedding_copy.to(self.device)
349 embedding_copy.weight.requires_grad_(
False)
350 self.embedding_layer = embedding_copy.eval()
354 if self.device.type ==
"cuda":
355 torch.cuda.empty_cache()
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.")
364 "float32": np.float32,
365 "float16": np.float16,
367 if dtype
not in supported_dtypes:
369 f
"Unsupported dtype '{dtype}'. Supported values: {sorted(supported_dtypes)}"
371 self.np_dtype = supported_dtypes[dtype]
372 self.model_name = Path(model_path).name
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)}.")
378 token_ids = self.tokenizer(text, return_tensors=
"pt")[
"input_ids"]
379 if token_ids.numel() == 0:
382 token_ids = token_ids.squeeze(0).to(self.device)
383 embedded_text = self.embedding_layer(token_ids)
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)
390 seq_len = embedded_text.shape[1]
391 if seq_len < self.min_seqlen:
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]
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],
403 embedded_text.shape[2],
404 device=embedded_text.device,
405 dtype=embedded_text.dtype,
407 embedded_text = torch.cat([embedded_text, pad_tensor], dim=1)
410 embedded_text.detach().to(
"cpu").numpy().astype(self.np_dtype, copy=
False)
463 @brief Apply mean/std normalization to images.
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.
472 mean: Union[List[float], np.ndarray],
473 std: Union[List[float], np.ndarray],
474 to_float_div255: bool =
False,
477 if not isinstance(mean, (np.ndarray, list, tuple)):
479 f
"Expected mean to be np.ndarray or List[float], but got {type(mean)}."
482 if not isinstance(std, (np.ndarray, list, tuple)):
484 f
"Expected std to be np.ndarray or List[float], but got {type(std)}."
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))
493 def __call__(self, img: np.ndarray):
494 img = img.copy().astype(np.float32)
498 normalize_(img, self.
mean, self.
std)
504 def __init__(self, size: List[int], interpolation: str):
509 def __call__(self, img: np.ndarray):
510 img_tensor = img_npy_to_torch_tensor(img)
511 resized_img_tensor = F.resize(
516 img = torch_tensor_to_img_npy(resized_img_tensor)
523 @brief Resize images with optional aspect-ratio preservation.
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").
532 img_scale: Union[float, Tuple[int, int]],
541 def __call__(self, img: np.ndarray):
550 new_h, new_w = img.shape[:2]
554 img, w_scale, h_scale = imresize(
564 @brief Perform a centered crop on the image.
566 @param size list[int]. Target height and width.
569 def __init__(self, size: List[int]):
571 assert has_same_type(size),
"size must consist of same type elements."
572 type_check(size[0], (int))
578 raise AssertionError(f
"Invalid shape of size; {size}")
580 def __call__(self, img: np.ndarray):
599 if (self.
size[1] > w)
or (self.
size[0] > h):
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,
606 img = impad(img, padding=tuple(padding))
608 if (self.
size[1] == w)
and (self.
size[0] == h):
610 crop_top = round((h - self.
size[0]) / 2.0)
611 crop_left = round((w - self.
size[1]) / 2.0)
613 crop_top : crop_top + self.
size[0], crop_left : crop_left + self.
size[1], :
621 @brief Apply YOLO-style resize, padding, and normalization to images.
622 @param size Target image size provided as [height, width].
625 def __init__(self, size: List[int]):
627 assert has_same_type(size),
"size must consist of same type elements."
628 type_check(size[0], (int))
634 raise AssertionError(f
"Invalid shape of size; {size}")
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)}.")
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]
649 if (img.shape[1], img.shape[0]) != new_unpad:
650 img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
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))
657 img = cv2.copyMakeBorder(
664 value=(114, 114, 114),
667 img = (img / 255.0).astype(np.float32)
690 @brief Build a calibration preprocessing pipeline from a YAML or dict configuration.
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.
706 [Pre-processing Type]
707 [Parameter]: [Argument]
715 Pre-Order: [ResizeTorch, CenterCrop, Normalize, SetOrder]
719 interpolation: blinear
723 mean: [0.485, 0.456, 0.406]
724 std: [0.229, 0.224, 0.225]
725 to_float_div255: true
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).
740 @param args dict. Parsed preprocessing configuration.
741 @param to_tensor bool. When True, converts the resulting NumPy array to a Torch tensor.
744 def __init__(self, args, to_tensor=False):
750 def __call__(self, img):
754 img = img_npy_to_torch_tensor(img)
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}"
769 def get_pre_list(self, args):
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():
779 f
"{fnc_type} is already registered in {__preprocs__.keys()}."
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)
788 def check_yaml_pre(self, args):
790 "Pre-processing" in args
791 ),
"Write the Pre-processing block in the yaml file."
793 preproc_cfg = args[
"Pre-processing"]
794 pre_order = args.get(
"Pre-Order")
796 if pre_order
is None:
797 pre_order = list(preproc_cfg.keys())
799 assert not set(pre_order) - set(
801 ),
"All elements of Pre-Order must be included in pre-processing. check the yaml file."
804 ),
"Write the Datatype in the yaml file, Datatype is not writen in the yaml file and we support 'Image' and 'Npy'."
809 pre_set = set([name.lower()
for name
in pre_order])
810 pre_not_include = pre_set - set(__preprocs__[self.
datatype].keys())
812 if self.
loadtype not in args.keys():
814 f
"Datatype is {self.datatype}, but getting data method is not {self.loadtype}"
816 elif pre_not_include:
818 f
"{pre_not_include} is not supported if data type is {self.datatype}"
Extract text strings from raw inputs.