COMODO-AnyMo-Bench / processing_comodo.py
Breezelled's picture
Add direct variant loading and simplify model cards
3f224a9 verified
Raw History Blame Contribute Delete
5.35 kB
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Baiyu Chen and the COMODO-AnyMo-Bench contributors
"""Preprocessing that matches the original Nymeria COMODO loader and collate."""
import json
from pathlib import Path
import numpy as np
import torch
from torch.nn import functional as F
from transformers import BatchFeature, ProcessorMixin
from transformers.utils.hub import cached_file
class ComodoProcessor(ProcessorMixin):
attributes = []
model_input_names = ["imu_values"]
def __init__(self, mean, std, sampling_rate=60, sequence_length=512):
super().__init__()
self.mean = np.asarray(mean, dtype=np.float32)
self.std = np.asarray(std, dtype=np.float32)
if self.mean.shape != (18,) or self.std.shape != (18,):
raise ValueError("COMODO requires normalization statistics for 18 channels")
self.std = np.where(self.std < 1e-6, 1.0, self.std).astype(np.float32)
self.sampling_rate = sampling_rate
self.sequence_length = sequence_length
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
variant = kwargs.pop("variant", None)
hub_kwargs = {key: kwargs[key] for key in
("subfolder", "revision", "token", "cache_dir", "local_files_only",
"force_download") if key in kwargs}
if variant is not None:
if variant not in ("core50", "fine150"):
raise ValueError("variant must be 'core50' or 'fine150'")
if hub_kwargs.get("subfolder") not in (None, "", variant):
raise ValueError("variant conflicts with subfolder")
hub_kwargs["subfolder"] = variant
elif not hub_kwargs.get("subfolder"):
metadata_path = cached_file(str(pretrained_model_name_or_path), "config.json",
_raise_exceptions_for_missing_entries=False, **hub_kwargs)
metadata = json.loads(Path(metadata_path).read_text()) if metadata_path else {}
if metadata.get("is_variant_bundle", False):
hub_kwargs["subfolder"] = metadata.get("variant", "core50")
config = json.loads(Path(cached_file(str(pretrained_model_name_or_path),
"processor_config.json", **hub_kwargs)).read_text())
stats = json.loads(Path(cached_file(str(pretrained_model_name_or_path),
"normalization_stats.json", **hub_kwargs)).read_text())
return cls(stats["mean"], stats["std"], config["sampling_rate"], config["sequence_length"])
def save_pretrained(self, save_directory, **kwargs):
target = Path(save_directory)
target.mkdir(parents=True, exist_ok=True)
config = {"processor_class": "ComodoProcessor", "sampling_rate": self.sampling_rate,
"sequence_length": self.sequence_length,
"auto_map": {"AutoProcessor": "processing_comodo.ComodoProcessor"}}
(target / "processor_config.json").write_text(json.dumps(config, indent=2) + "\n")
(target / "normalization_stats.json").write_text(json.dumps({
"mean": self.mean.tolist(), "std": self.std.tolist()}, indent=2) + "\n")
source = Path(__file__)
if source.resolve() != (target / source.name).resolve():
(target / source.name).write_text(source.read_text())
return [str(target / "processor_config.json")]
def __call__(self, imu, sampling_rate=60, return_tensors="pt", **kwargs):
if kwargs:
raise TypeError(f"Unknown preprocessing arguments: {sorted(kwargs)}")
if sampling_rate != self.sampling_rate:
raise ValueError(f"Provide {self.sampling_rate} Hz IMU windows; got {sampling_rate} Hz")
if return_tensors != "pt":
raise ValueError("Only return_tensors='pt' is supported")
if isinstance(imu, torch.Tensor):
imu = imu.detach().cpu().numpy()
try:
values = np.asarray(imu, dtype=np.float32)
except ValueError:
values = None # A batch of windows with different lengths.
if values is not None and values.ndim == 2:
windows = [values]
elif values is not None and values.ndim == 3:
windows = list(values)
elif values is None and isinstance(imu, (list, tuple)):
windows = [np.asarray(row, dtype=np.float32) for row in imu]
else:
raise ValueError("Expected [T, 18], [B, T, 18], or a list of [T, 18] windows")
resized = []
for window in windows:
if window.ndim != 2 or window.shape[1] != 18 or window.shape[0] == 0:
raise ValueError("Each IMU window must have shape [T, 18] with T > 0")
window = np.nan_to_num(window, copy=True)
window = (window - self.mean.reshape(1, -1)) / self.std.reshape(1, -1)
tensor = torch.from_numpy(np.ascontiguousarray(window.T)).unsqueeze(0)
resized.append(F.interpolate(tensor, size=self.sequence_length, mode="linear",
align_corners=False).squeeze(0))
if not resized:
raise ValueError("The input batch must contain at least one window")
return BatchFeature({"imu_values": torch.stack(resized)})