{
  "file": "field.f32",
  "dtype": "float32, little-endian",
  "shape": [
    194,
    50,
    50,
    3
  ],
  "layout": "C order [nz][ny][nx][3]: node (k, j, i) = (z, y, x index); the last axis is the displacement (dx, dy, dz)",
  "origin_xyz": [
    608.0000,
    608.0000,
    876.3000
  ],
  "spacing": 96.0000,
  "node_position": "node (k, j, i) sits at fixed level-0 voxel (x, y, z) = origin_xyz + spacing * (i, j, k)",
  "frame": "fixed volume 20250804134230-9.362um-1.2m-113keV-masked, level-0 voxels, x y z",
  "units": "fixed level-0 voxels",
  "interpolation": "trilinear between nodes; outside the grid the edge nodes are used (clamped)",
  "max_abs_displacement": 3.6279,
  "direction": "D is defined on the fixed grid and pulls back to the moving scan: for a fixed point p, the moving point is m = T^-1 (p + D(p)), T = transform.json transformation_matrix (moving -> fixed)",
  "compose": {
    "fixed_to_moving": "m = T^-1 (p + D(p))",
    "moving_to_fixed": "p = T(m) - D(p): iterate p_0 = T(m), p_{k+1} = T(m) - D(p_k) (|grad D| << 1: converges in a few steps)",
    "affine_only": "p ~ T(m); error up to max_abs_displacement"
  },
  "worked_example": {
    "fixed_p": [
      2176.00,
      1920.00,
      1036.18
    ],
    "D(p)": [
      -0.8098547266836249,
      -0.2028704147096505,
      -0.06796302104294227
    ],
    "p + D(p)": [
      2175.1901452733164,
      1919.7971295852903,
      1036.1120369789571
    ],
    "moving_m": [
      14558.2419,
      17521.9815,
      3233.4783
    ],
    "check": "T(moving_m) = p + D(p); ufsm reg map reg.json x,y,z gives moving_m, ufsm reg map reg.json --f2c mx,my,mz gives p back"
  },
  "tool": "ufsm reg map reg.json [--f2c] x,y,z (github.com/SuperOptimizer/ufsm, src/reg.h)"
}
