Skip to content

Spatial registration (66.1)

A Spatial Registration object says how one Frame of Reference sits inside another — where the PET is relative to the CT, where today's MR is relative to last month's. Read one to place a fusion or propagate contours across frames; author one from your own 4x4 matrices. Deformable Spatial Registration (66.3) carries a deformation grid rather than a matrix and is a different object; it is refused here rather than read as if it were this one.

Read a registration

import pydcm

reg = pydcm.read_registration("reg.dcm")
reg.frame_of_reference_uid   # the object's OWN frame — the one every item maps into
reg.frames                   # the Frames of Reference this object registers
reg.items                    # the RegistrationItem entries
reg.incomplete               # an item could not be recorded (short by omission)
len(reg)                     # number of items; reg is iterable over its items

The object's own frame is the fixed image of a fusion — a registration authored in the CT frame registers PET (and MR, and…) into it. An object conventionally includes an identity item for its own frame, and that item is kept: "these two frames are the same" is an answer, not an item to drop.

Resolve a transform

transform(from_uid, to_uid) returns the (4, 4) float64 that maps a point in from_uid into to_uid, in the M @ [x, y, z, 1] convention. This is what a fusion actually asks for — neither series is necessarily the frame the object was authored in, and the reverse direction is the inverse, not a second stored matrix.

m = reg.transform(pet_frame_uid, ct_frame_uid)   # PET point -> CT point
world_ct = m @ [x, y, z, 1.0]

reg.transform(pet_frame_uid)              # to_uid omitted -> maps into the object's own frame
reg.transform(pet_frame_uid, pet_frame_uid)   # identical frames -> the identity

Identical frames answer with the identity, so a caller need not decide beforehand whether a registration is even involved.

None is not identity

transform returns None — never a silent identity — when the object does not state the transform: an unregistered frame it never mentions, an item whose matrix was present but unusable, or a composition that would need a singular inverse.

m = reg.transform(some_frame_uid, ct_frame_uid)
if m is None:
    ...   # this object cannot place that frame — do NOT fall back to identity
else:
    apply(m)

Applying identity where a registration was intended puts the overlay in the wrong place with nothing to show that it did. The falsy return forces the caller to decide, which is the whole point of the object.

Inspect the items

Each entry is a pydcm.registration.RegistrationItem, one Item of the Registration Sequence:

for item in reg.items:
    item.frame_of_reference_uid   # the frame this item's matrix maps FROM
    item.matrix                   # (4, 4) float64 into the object's own frame, or None
    item.matrix_invalid           # a matrix was present and cannot be used
    item.matrix_type              # "RIGID", "RIGID_SCALE", "AFFINE" or "UNKNOWN"
    item.matrix_count             # how many matrices were composed into `matrix`

matrix is None with matrix_invalid is False means the item carried no matrix — a different fact from one that was present and failed (a Decimal String that would not convert, the wrong number of values, or a bottom row that is not [0, 0, 0, 1]), which reports matrix_invalid is True. Absent, unusable, and identity are three distinct answers. matrix_type is the producer's claim about its own pipeline — all three named types are affine — and nothing checks it against the matrix; a consumer may want to trust each differently.

Author a registration

write_registration is the inverse of read_registration: give it your 4x4 matrices in the same M @ [x, y, z, 1] convention and the frame they map into.

import numpy as np

pydcm.write_registration(
    [{"frame_of_reference_uid": pet_frame_uid, "matrix": pet_into_ct,
      "matrix_type": "RIGID"}],   # RIGID default; "RIGID_SCALE" or "AFFINE" also
    ct_frame_uid,                 # THIS object's own frame — the frame each matrix maps INTO
    output="reg.dcm",             # omit `output` to get Part-10 bytes instead
    patient_name="DOE^JANE", patient_id="MRN-001",
    study_uid="1.2.3.4.5", series_uid="1.2.3.4.5.9",
)

Each item's matrix maps its frame_of_reference_uid into the object's own frame; that direction is what the whole object means and reversing it is not detectable later. An identity item for the own frame is written automatically unless items already contains one — without it the file parses and then refuses every query, because resolving A to B composes inverse(item_B) @ item_A and needs an item for B.

The writer refuses inputs that would produce a file that parses and then answers its own question wrongly (or not at all): a matrix whose bottom row is not [0, 0, 0, 1], a singular matrix, a non-finite element, two items naming one frame, an unknown matrix_type, an empty own frame, or no items at all. Each is raised rather than written.

Scope

Rigid and affine Spatial Registration (66.1) — read the transform between any two registered frames (with the None-not-identity contract), inspect the per-frame items, and author an object from your own 4x4 matrices. Deformable Spatial Registration (66.3) is out of scope. Not for clinical or diagnostic use.