Space Fundamentals

Space: Complete 3D Geometry

Space objects describe the sampling grid via shape, origin, spacing, and three orientation vectors.

import numpy as np
import matplotlib.pyplot as plt
from spacetransformer.core import Space

standard_space = Space(
    shape=(1,10,10),
    origin=(0.0,0.0,0.0),
    spacing=(1.0,1.0,1.0),
    x_orientation=(1.0,0.0,0.0),
    y_orientation=(0.0,1.0,0.0),
    z_orientation=(0.0,0.0,1.0)
)

Helper to visualize Y–Z slices of the sampling grid.

def visualize_sampling_grid(space, color='blue', show_axes=True, label_suffix=''):
    shape_yz = space.shape[1:3]
    y_idx, z_idx = np.meshgrid(np.arange(shape_yz[0]), np.arange(shape_yz[1]), indexing='ij')
    index_points = np.stack([
        np.zeros_like(y_idx.flatten()),
        y_idx.flatten(),
        z_idx.flatten()
    ], axis=1)
    world = space.to_world_transform.apply_point(index_points)
    plt.scatter(world[:,2], world[:,1], c=color, alpha=0.4, s=20, label=f'Sampling Grid{label_suffix}')
    if show_axes:
        origin_world = space.to_world_transform.apply_point([[0,0,0]])[0]
        plt.plot(origin_world[2], origin_world[1], 'o', color=color, markersize=8, alpha=0.8)
        axis_len = 1
        y_end = origin_world[1:3] + np.array(space.y_orientation[1:3]) * axis_len
        plt.arrow(origin_world[2], origin_world[1], y_end[1]-origin_world[2], y_end[0]-origin_world[1],
                  head_width=axis_len*0.1, head_length=axis_len*0.1, fc=color, ec=color, alpha=0.7)
        z_end = origin_world[1:3] + np.array(space.z_orientation[1:3]) * axis_len
        plt.arrow(origin_world[2], origin_world[1], z_end[1]-origin_world[2], z_end[0]-origin_world[1],
                  head_width=axis_len*0.1, head_length=axis_len*0.1, fc=color, ec=color, alpha=0.7)

def setup_plot(figsize=(10,8)):
    plt.figure(figsize=figsize)

def finalize_plot(title):
    plt.xlabel('Z (mm)'); plt.ylabel('Y (mm)'); plt.title(title)
    plt.grid(True, alpha=0.3); plt.axis('equal'); plt.legend(); plt.tight_layout(); plt.show()

setup_plot()
visualize_sampling_grid(standard_space)
finalize_plot('Standard Sampling Grid (10×10, 1 mm)')

Transform Examples

Shape (Resize)

high_res = standard_space.apply_shape((1,20,20))
low_res = standard_space.apply_shape((1,5,5))
setup_plot(); visualize_sampling_grid(standard_space, 'blue', label_suffix=' (10×10)')
visualize_sampling_grid(low_res, 'red', label_suffix=' (5×5)')
finalize_plot('Resize')

Flip

flipped = standard_space.apply_flip(axis=1)
setup_plot(); visualize_sampling_grid(standard_space, 'blue', label_suffix=' (orig)')
visualize_sampling_grid(flipped, 'red', label_suffix=' (flip Y)')
finalize_plot('Flip (Y axis)')

Axis Swap

rect_space = Space(shape=(1,6,12), origin=(0,0,0), spacing=(1,1,1),
                   x_orientation=(1,0,0), y_orientation=(0,1,0), z_orientation=(0,0,1))
swapped = rect_space.apply_swap(1, 2)
setup_plot(); visualize_sampling_grid(rect_space, 'blue', label_suffix=' (6×12)')
visualize_sampling_grid(swapped, 'red', label_suffix=' (swap Y↔Z)')
finalize_plot('Axis Swap')

Rotation

rotated = standard_space.apply_rotate(axis=0, angle=30, unit='degree', center='center')
setup_plot(); visualize_sampling_grid(standard_space, 'blue', label_suffix=' (orig)')
visualize_sampling_grid(rotated, 'red', label_suffix=' (rotate 30°)')
finalize_plot('Rotation (around X)')

Bounding Box Crop

crop_bbox = np.array([[0,1],[2,8],[2,8]])
cropped = standard_space.apply_bbox(crop_bbox)
setup_plot(); visualize_sampling_grid(standard_space, 'blue', label_suffix=' (orig)')
visualize_sampling_grid(cropped, 'red', label_suffix=' (crop 6×6)')
finalize_plot('Bounding Box Crop')

Complex Transform Chain

complex_target = (standard_space
    .apply_bbox(np.array([[0,1],[2,8],[2,8]]))
    .apply_shape((1,12,12))
    .apply_rotate(axis=0, angle=45, unit='degree'))
setup_plot(); visualize_sampling_grid(standard_space, 'blue', label_suffix=' (orig)')
visualize_sampling_grid(complex_target, 'red', label_suffix=' (Crop→Resample→Rotate)')
finalize_plot('Complex Chain')

Float Bounding Box Sampling

float_bbox_space = standard_space.apply_float_bbox(np.array([[0,1],[2.5,5.5],[2.5,5.5]]), (12,12,12))
setup_plot(); visualize_sampling_grid(standard_space, 'blue', label_suffix=' (orig)')
visualize_sampling_grid(float_bbox_space, 'red', label_suffix=' (float bbox)')
finalize_plot('Float Bounding Box')

Why Space-Centric Design?

Traditional “transform-centric” APIs (e.g., torchvision) provide relative transforms. SpaceTransformer is “space-centric”: every object (image, mask, points) carries an absolute Space descriptor. Aligning two objects means comparing spaces and deriving the needed transform automatically.

Planning vs Execution

  • Planning: chain methods on Space (apply_shape, apply_bbox, apply_rotate, …) to describe the desired geometry without touching data.
  • Execution: warp_image, warp_point, etc., analyze the chain and perform one optimized resample.

Benefits

  • Geometric accuracy: transform order (resize→rotate vs rotate→resize) yields the same result; avoids sampling artifacts.
  • Memory efficiency: no intermediate arrays; only final output is allocated.
  • Simpler architecture: one warp_* family handles images, masks, points without bespoke logic per type.
  • Ease of use: declarative API, yet advanced users can still access low-level controls.