Dynamic Framing
Dynamic framing manages timestamp ranges for dynamic reconstructions.
The framing defines the time ranges used for assigning a dynamic frame to each PET list-mode event. This is used for dynamic reconstruction as well as simple forward-backward projection operations.
This is used along the fourth dimension of the Image class, which only defines the number of dynamic frames, not the time range associated to each one.
The DynamicFraming object is used by the OSEM or the OperatorProjector
to correctly map each list-mode event to the appropriate frame in the fourth
dimension of the image space, allowing 4D reconstruction or projection.
The timestamps provided are in milliseconds and are in the same referrential as the timestamps of the list-mode or the motion file.
Python Usage
import pyyrtpet as yrt
import numpy as np
# ============================================================================
# Method 1: Create dynamic framing with a specified number of frames
# ============================================================================
# Create a DynamicFraming with 10 frames (requires setting timestamps afterwards)
framing = yrt.DynamicFraming(num_frames=10)
# Set frame start times - timestamps must be in chronological order
# Frame 0 starts at 0 ms
framing.setStartingTimestamp(frame=0, timestamp=0)
# Frame 1 starts at 10 ms
framing.setStartingTimestamp(frame=1, timestamp=10)
# Frame 2 starts at 20 ms
framing.setStartingTimestamp(frame=2, timestamp=20)
# Continue for all frames...
framing.setStartingTimestamp(frame=3, timestamp=30)
framing.setStartingTimestamp(frame=4, timestamp=45)
framing.setStartingTimestamp(frame=5, timestamp=60)
framing.setStartingTimestamp(frame=6, timestamp=80)
framing.setStartingTimestamp(frame=7, timestamp=100)
framing.setStartingTimestamp(frame=8, timestamp=125)
framing.setStartingTimestamp(frame=9, timestamp=135)
# The last timestamp marks the end of the last frame
framing.setLastTimestamp(timestamp=150)
# Verify the framing is valid (timestamps in chronological order)
# This will also fail if some frames were left unset.
assert framing.isValid(), "Timestamps must be in chronological order"
# Get the number of frames
num_frames = framing.getNumFrames()
assert num_frames == 10, f"Expected 10 frames, got {num_frames}"
# ============================================================================
# Method 2: Create dynamic framing from a numpy array of timestamps
# ============================================================================
# Frame timestamps: start of each frame + end timestamp
# This creates 3 frames: [0-10ms], [10-30ms], [30-60ms]
# The dtype of the numpy array must be `uint32`
timestamps = np.array([0, 10, 30, 60], dtype=np.uint32)
framing_from_array = yrt.DynamicFraming(frame_timestamps=timestamps)
assert framing_from_array.getNumFrames() == 3
# Note that the array has 4 elements, but we defined 3 frames.
# This is because the last timestamp defines the end of the last frame.
# ============================================================================
# Method 3: Load dynamic framing from a file (.dyn extension)
# ============================================================================
# For this documentation's purposes only, we use a tempfile
import tempfile
with tempfile.NamedTemporaryFile(suffix='.dyn', delete=False) as f:
my_file = f.name
# Framing data is stored as a text file containing timestamps separated by a
# whitespace
# To save:
framing.writeToFile(my_file)
# To load:
framing_from_file = yrt.DynamicFraming(my_file)
# Check integrity
assert framing_from_file.getNumFrames() == framing.getNumFrames()
for i in range(framing.getNumFrames()):
assert framing_from_file.getStartingTimestamp(i) == framing.getStartingTimestamp(i)
assert framing_from_file.getLastTimestamp() == framing.getLastTimestamp()
# ============================================================================
# Query methods
# ============================================================================
# Get total duration of the dynamic framing (last timestamp - first timestamp)
total_duration = framing.getTotalDuration()
assert total_duration == 150, f"Expected 150ms, got {total_duration}"
# Get duration of a specific frame
frame_duration = framing.getDuration(frame=0) # Duration of frame 0 (10 ms)
assert frame_duration == 10, f"Expected 10ms, got {frame_duration}"
# Get the timestamp when a specific frame starts
start_ts = framing.getStartingTimestamp(frame=0) # 0
start_ts = framing.getStartingTimestamp(frame=5) # 60
# Get the timestamp when a specific frame ends (i.e., next frame starts)
stop_ts = framing.getStoppingTimestamp(frame=0) # 10 (start of frame 1)
stop_ts = framing.getStoppingTimestamp(frame=4) # 80 (start of frame 5)
# Get number of timestamps (frames + 1 for the final timestamp)
num_timestamps = framing.getNumTimestamps() # 11 (10 frames + 1 end)
File Format
The dynamic framing can be saved to a text file with the .dyn extension.
Each line contains a single timestamp (in ms):
0
10
30
60
120
180
The file must contain at least 2 timestamps (defining 1 frame).
Notes
Timestamps must be strictly increasing (each frame must start after the previous, without overlap)
The number of timestamps equals
num_frames + 1(frame starts + final timestamp)