Custom Processing¶
rddac preprocess is the reference implementation of the processed layout, not the only allowed one. The raw dataset is immutable and readable through the public API, so anyone with a better algorithm can produce their own processed layer: the contract is the schema of the output files, not a Python interface.
Levels of adjustment¶
| Level | When | How |
|---|---|---|
| 1. Change parameters | the algorithm is fine, a threshold or window is not | --config my.toml, no code |
| 2a. Replace one step | you want a different filter for one modality, the reference stages for the rest | write that modality's group yourself, let rddac preprocess do the others |
| 2b. Add a step | the reference output is right, you need something derived on top | append new datasets to the processed files |
| 3. Replace everything | a different pipeline altogether | produce files matching the schema in your own directory |
Level 1: parameters¶
Every threshold of every stage is a TOML key (rddac preprocess --dump-config prints them all with their defaults). A stricter oil-film cleaning, for example:
# my.toml
[oil]
hampel_window = 10 # wider neighbourhood (mm)
hampel_k = 2.5 # flag outliers earlier
The values used are stamped into oil_thickness attributes of every output file (hampel_window, hampel_k, ...), and the docs figure can be redrawn for the variant with the same overrides: python -m rddac._preprocess.visualize oil --id 0 --config my.toml --out ..
Level 2a: replace one step, keep the rest¶
Say you prefer a plain running-median filter to the Hampel filter for the oil film. Write the oil_thickness group yourself and leave the other modalities to the reference stages. The output directory is shared: rddac preprocess appends to existing files and never touches groups it did not create unless --overwrite is given.
from pathlib import Path
import h5py
import numpy as np
import pandas as pd
from scipy.signal import medfilt
import rddac
from rddac._preprocess.h5_access import available_ids
DATA_DIR, OUT_DIR = Path("./data"), Path("./data/processed")
OUT_DIR.mkdir(exist_ok=True)
def median_oil(raw: np.ndarray, kernel: int = 7) -> np.ndarray:
"""Running median instead of Hampel: truncate, drop NaN, filter, average duplicates, fill the grid."""
df = pd.DataFrame(raw, columns=["pos", "val"]).dropna()
df["pos"] = df["pos"].round().astype(int)
df = df[df["pos"] < 200].sort_values("pos")
df["val"] = medfilt(df["val"].to_numpy(), kernel)
grid = pd.DataFrame({"pos": range(200)}).merge(df.groupby("pos", as_index=False)["val"].mean(), how="left")
grid["val"] = grid["val"].interpolate().bfill().ffill()
return grid.to_numpy(dtype=np.float32)
for exp_id in sorted(available_ids(DATA_DIR) or []):
with rddac.open_h5(exp_id, data_dir=DATA_DIR) as raw:
cleaned = median_oil(raw["oil_thickness/data"][:])
with h5py.File(OUT_DIR / f"{exp_id:04d}.h5", "a") as out:
if "oil_thickness" in out:
del out["oil_thickness"]
g = out.create_group("oil_thickness")
g.create_dataset("data", data=cleaned)
g.attrs["columns"], g.attrs["units"] = ["sensor_position", "oil_value"], ["mm", "g/m^2"]
g.attrs["producer"], g.attrs["median_kernel"] = "median_oil v1", 7 # record what produced this
The result is one processed layer: your oil profiles next to the reference force and sheet tables, streamable through the same views (iter_view("oil-thickness", data_dir="./data/processed", source="./data/metadata.json")). To compare against the reference, run rddac preprocess oil --out ./data/reference and plot both.
Level 2b: add a step on top of a reference stage¶
A step that extends the reference output: let rddac preprocess pointcloud do the
full reference processing, then derive from it.
Example: a fixed-size random subsample (4,096 points) of every processed cloud, the typical input size for point-cloud networks:
from pathlib import Path
import h5py
import numpy as np
OUT_DIR = Path("./data/processed")
N, SEED = 4_096, 42
for path in sorted(OUT_DIR.glob("*.h5")):
with h5py.File(path, "a") as f:
if "pointcloud" not in f:
continue
for op_i, op in enumerate(("op10", "op20")):
pts = f[f"pointcloud/{op}/z"][:] # (M, 3) processed cloud
# seed per (file, op): rerunning any single file reproduces its sample,
# independent of processing order or which other files are present
rng = np.random.default_rng([SEED, int(path.stem), op_i])
pick = rng.choice(len(pts), size=N, replace=False)
name = f"pointcloud/{op}/z_{N}"
if name in f:
del f[name]
d = f.create_dataset(name, data=pts[np.sort(pick)])
d.attrs["producer"] = "random_subsample v1" # record what produced this
d.attrs["subsample_n"], d.attrs["subsample_seed"] = N, SEED
Two points: add a new dataset (z_4096) instead of replacing z, since downstream
tooling expects the full cloud, and record the parameters (n, seed, producer) in the
attributes. The same pattern applies to any derived quantity: normals, voxel grids,
per-point features.
Level 3: your own pipeline¶
Read the raw files through the public API, run your own processing, and write the results into a separate directory in the same file schema. There is no Python interface to implement; output files only need to follow the processed schema:
import h5py
import rddac
for exp_id in range(9000):
with rddac.open_h5(exp_id, data_dir="./data") as raw: # immutable input
oil_raw = raw["oil_thickness/data"][:]
cleaned = my_better_oil(oil_raw) # your algorithm -> (200, 2) float32
with h5py.File(f"./my_processed/{exp_id:04d}.h5", "a") as out:
group = out.create_group("oil_thickness")
group.create_dataset("data", data=cleaned)
group.attrs["columns"] = ["sensor_position", "oil_value"]
group.attrs["producer"] = "my_better_oil v1" # record what produced this
Files in this schema work with the same views and downstream tooling as the reference output. The raw data is fixed and checksummed through the published Croissant manifest, so a processed layer is reproducible from its code alone; there is no server-side component involved.
Two conventions:
- Do not write into the raw directory. The published checksums define the dataset.
- Record the parameters and code version in the group attributes (the reference stages store every parameter they used), so a processed file can be interpreted without the code that produced it.
Internal reference¶
Internal API, may change without notice
The implementation lives in the private rddac._preprocess package. It is CLI-only by design; the signatures below are shown for orientation (e.g. for a notebook walking through the pipeline), not as a stable interface.
process(data: np.ndarray, *, max_sensor_position: int = OIL_MAX_SENSOR_POSITION, output_length: int = OIL_OUTPUT_LENGTH, hampel_window: int = OIL_HAMPEL_WINDOW, hampel_k: float = OIL_HAMPEL_K, value_quantization: float = OIL_VALUE_QUANTIZATION) -> tuple[np.ndarray, dict]
¶
Process one experiment's raw oil table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
ndarray
|
|
required |
max_sensor_position
|
int
|
Positions at/after this (mm) are edge artifacts. |
OIL_MAX_SENSOR_POSITION
|
output_length
|
int
|
Fixed output row count (one row per integer mm). |
OIL_OUTPUT_LENGTH
|
hampel_window
|
int
|
Hampel half-window in mm. |
OIL_HAMPEL_WINDOW
|
hampel_k
|
float
|
Hampel threshold in robust sigmas. |
OIL_HAMPEL_K
|
value_quantization
|
float
|
Logging LSB; floors the flat-window fallback scale. |
OIL_VALUE_QUANTIZATION
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
dict
|
on the integer mm grid; |
tuple[ndarray, dict]
|
parameter values used (self-describing output). |
Source code in rddac/_preprocess/oil.py
run(names: list[str], data_dir: str, out_dir: str, ids: str | None = None, split: str | None = None, workers: int = 1, overwrite: bool = False, quiet: bool = False, console=None, config: dict | None = None, skip_unavailable: bool = False, rebuild_models: bool = False) -> dict
¶
Process the selected modalities for all selected experiments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
list[str]
|
Modalities to run, in order. |
required |
data_dir
|
str
|
Directory holding the raw dataset (zips or loose |
required |
out_dir
|
str
|
Output directory for processed files (created if missing). |
required |
ids
|
str | None
|
Optional id selection, e.g. |
None
|
split
|
str | None
|
Optional predefined split name to restrict to. |
None
|
workers
|
int
|
Number of parallel worker processes. |
1
|
overwrite
|
bool
|
Recompute modality groups that already exist. |
False
|
quiet
|
bool
|
Suppress progress bar and summary output. |
False
|
console
|
rich console to print to; a new one is created if None. |
None
|
|
config
|
dict | None
|
Modality name → parameter overrides (see :mod: |
None
|
skip_unavailable
|
bool
|
Skip (instead of fail on) modalities whose optional dependencies are missing — used when the user did not name modalities explicitly. |
False
|
rebuild_models
|
bool
|
Force retraining of cached models in preflight. |
False
|
Returns:
| Type | Description |
|---|---|
dict
|
Stats dict |
Source code in rddac/_preprocess/runner.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | |