Source code for pytomography.io.PET.prd.types

# This file was generated by the "yardl" tool. DO NOT EDIT.

# pyright: reportUnusedImport=false
# pyright: reportUnknownArgumentType=false
# pyright: reportUnknownMemberType=false
# pyright: reportUnknownVariableType=false

import datetime
import enum
import types
import typing

import numpy as np
import numpy.typing as npt

from . import yardl_types as yardl
from . import _dtypes

[docs]class CoincidenceEvent: """All information about a coincidence event specified as identifiers or indices (i.e. discretized). TODO: this might take up too much space, so some/all of these could be combined in a single index if necessary. """
[docs] detector_1_id: yardl.UInt32
[docs] detector_2_id: yardl.UInt32
[docs] tof_idx: yardl.UInt32
[docs] energy_1_idx: yardl.UInt32
[docs] energy_2_idx: yardl.UInt32
def __init__(self, *, detector_1_id: yardl.UInt32 = 0, detector_2_id: yardl.UInt32 = 0, tof_idx: yardl.UInt32 = 0, energy_1_idx: yardl.UInt32 = 0, energy_2_idx: yardl.UInt32 = 0, ): self.detector_1_id = detector_1_id self.detector_2_id = detector_2_id self.tof_idx = tof_idx self.energy_1_idx = energy_1_idx self.energy_2_idx = energy_2_idx
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, CoincidenceEvent) and self.detector_1_id == other.detector_1_id and self.detector_2_id == other.detector_2_id and self.tof_idx == other.tof_idx and self.energy_1_idx == other.energy_1_idx and self.energy_2_idx == other.energy_2_idx )
[docs] def __str__(self) -> str: return f"CoincidenceEvent(detector1Id={self.detector_1_id}, detector2Id={self.detector_2_id}, tofIdx={self.tof_idx}, energy1Idx={self.energy_1_idx}, energy2Idx={self.energy_2_idx})"
[docs] def __repr__(self) -> str: return f"CoincidenceEvent(detector1Id={repr(self.detector_1_id)}, detector2Id={repr(self.detector_2_id)}, tofIdx={repr(self.tof_idx)}, energy1Idx={repr(self.energy_1_idx)}, energy2Idx={repr(self.energy_2_idx)})"
[docs]class Subject:
[docs] name: typing.Optional[str]
[docs] id: str
def __init__(self, *, name: typing.Optional[str] = None, id: str = "", ): self.name = name self.id = id
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, Subject) and self.name == other.name and self.id == other.id )
[docs] def __str__(self) -> str: return f"Subject(name={self.name}, id={self.id})"
[docs] def __repr__(self) -> str: return f"Subject(name={repr(self.name)}, id={repr(self.id)})"
[docs]class Institution:
[docs] name: str
[docs] address: str
def __init__(self, *, name: str = "", address: str = "", ): self.name = name self.address = address
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, Institution) and self.name == other.name and self.address == other.address )
[docs] def __str__(self) -> str: return f"Institution(name={self.name}, address={self.address})"
[docs] def __repr__(self) -> str: return f"Institution(name={repr(self.name)}, address={repr(self.address)})"
[docs]class ExamInformation: """Items describing the exam (incomplete)"""
[docs] subject: Subject
[docs] institution: Institution
[docs] protocol: typing.Optional[str]
[docs] start_of_acquisition: typing.Optional[yardl.DateTime]
def __init__(self, *, subject: typing.Optional[Subject] = None, institution: typing.Optional[Institution] = None, protocol: typing.Optional[str] = None, start_of_acquisition: typing.Optional[yardl.DateTime] = None, ): self.subject = subject if subject is not None else Subject() self.institution = institution if institution is not None else Institution() self.protocol = protocol self.start_of_acquisition = start_of_acquisition
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, ExamInformation) and self.subject == other.subject and self.institution == other.institution and self.protocol == other.protocol and self.start_of_acquisition == other.start_of_acquisition )
[docs] def __str__(self) -> str: return f"ExamInformation(subject={self.subject}, institution={self.institution}, protocol={self.protocol}, startOfAcquisition={self.start_of_acquisition})"
[docs] def __repr__(self) -> str: return f"ExamInformation(subject={repr(self.subject)}, institution={repr(self.institution)}, protocol={repr(self.protocol)}, startOfAcquisition={repr(self.start_of_acquisition)})"
[docs]class Detector: """Detector ID and location. Units are in mm TODO: this is currently just a sample implementation with "point" detectors. We plan to have full shape information here. """
[docs] id: yardl.UInt32
[docs] x: yardl.Float32
[docs] y: yardl.Float32
[docs] z: yardl.Float32
def __init__(self, *, id: yardl.UInt32 = 0, x: yardl.Float32 = 0.0, y: yardl.Float32 = 0.0, z: yardl.Float32 = 0.0, ): self.id = id self.x = x self.y = y self.z = z
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, Detector) and self.id == other.id and self.x == other.x and self.y == other.y and self.z == other.z )
[docs] def __str__(self) -> str: return f"Detector(id={self.id}, x={self.x}, y={self.y}, z={self.z})"
[docs] def __repr__(self) -> str: return f"Detector(id={repr(self.id)}, x={repr(self.x)}, y={repr(self.y)}, z={repr(self.z)})"
[docs]class ScannerInformation:
[docs] model_name: typing.Optional[str]
[docs] detectors: list[Detector]
[docs] tof_bin_edges: npt.NDArray[np.float32]
"""edge information for TOF bins in mm (given as from first to last edge, so there is one more edge than the number of bins) TODO: this currently assumes equal size for each TOF bin, but some scanners "stretch" TOF bins depending on length of LOR """
[docs] tof_resolution: yardl.Float32
"""TOF resolution in mm"""
[docs] energy_bin_edges: npt.NDArray[np.float32]
"""edge information for energy windows in keV (given as from first to last edge, so there is one more edge than the number of bins)"""
[docs] energy_resolution_at_511: yardl.Float32
"""FWHM of photopeak for incoming gamma of 511 keV, expressed as a ratio w.r.t. 511"""
[docs] listmode_time_block_duration: yardl.UInt32
"""duration of each time block in ms""" def __init__(self, *, model_name: typing.Optional[str] = None, detectors: typing.Optional[list[Detector]] = None, tof_bin_edges: typing.Optional[npt.NDArray[np.float32]] = None, tof_resolution: yardl.Float32 = 0.0, energy_bin_edges: typing.Optional[npt.NDArray[np.float32]] = None, energy_resolution_at_511: yardl.Float32 = 0.0, listmode_time_block_duration: yardl.UInt32 = 0, ): self.model_name = model_name self.detectors = detectors if detectors is not None else [] self.tof_bin_edges = tof_bin_edges if tof_bin_edges is not None else np.zeros((0), dtype=np.dtype(np.float32)) self.tof_resolution = tof_resolution self.energy_bin_edges = energy_bin_edges if energy_bin_edges is not None else np.zeros((0), dtype=np.dtype(np.float32)) self.energy_resolution_at_511 = energy_resolution_at_511 self.listmode_time_block_duration = listmode_time_block_duration
[docs] def number_of_detectors(self) -> yardl.Size: return len(self.detectors)
[docs] def number_of_tof_bins(self) -> yardl.Size: return self.tof_bin_edges.size - 1
[docs] def number_of_energy_bins(self) -> yardl.Size: return self.energy_bin_edges.size - 1
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, ScannerInformation) and self.model_name == other.model_name and self.detectors == other.detectors and yardl.structural_equal(self.tof_bin_edges, other.tof_bin_edges) and self.tof_resolution == other.tof_resolution and yardl.structural_equal(self.energy_bin_edges, other.energy_bin_edges) and self.energy_resolution_at_511 == other.energy_resolution_at_511 and self.listmode_time_block_duration == other.listmode_time_block_duration )
[docs] def __str__(self) -> str: return f"ScannerInformation(modelName={self.model_name}, detectors={self.detectors}, tofBinEdges={self.tof_bin_edges}, tofResolution={self.tof_resolution}, energyBinEdges={self.energy_bin_edges}, energyResolutionAt511={self.energy_resolution_at_511}, listmodeTimeBlockDuration={self.listmode_time_block_duration})"
[docs] def __repr__(self) -> str: return f"ScannerInformation(modelName={repr(self.model_name)}, detectors={repr(self.detectors)}, tofBinEdges={repr(self.tof_bin_edges)}, tofResolution={repr(self.tof_resolution)}, energyBinEdges={repr(self.energy_bin_edges)}, energyResolutionAt511={repr(self.energy_resolution_at_511)}, listmodeTimeBlockDuration={repr(self.listmode_time_block_duration)})"
[docs]class TimeBlock:
[docs] id: yardl.UInt32
"""number of the block. Multiply with listmodeTimeBlockDuration to get time since startOfAcquisition"""
[docs] prompt_events: list[CoincidenceEvent]
"""list of prompts in this time block TODO might be better to use !array """
[docs] delayed_events: typing.Optional[list[CoincidenceEvent]]
"""list of delayed coincidences in this time block""" def __init__(self, *, id: yardl.UInt32 = 0, prompt_events: typing.Optional[list[CoincidenceEvent]] = None, delayed_events: typing.Optional[list[CoincidenceEvent]] = None, ): self.id = id self.prompt_events = prompt_events if prompt_events is not None else [] self.delayed_events = delayed_events
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, TimeBlock) and self.id == other.id and self.prompt_events == other.prompt_events and self.delayed_events == other.delayed_events )
[docs] def __str__(self) -> str: return f"TimeBlock(id={self.id}, promptEvents={self.prompt_events}, delayedEvents={self.delayed_events})"
[docs] def __repr__(self) -> str: return f"TimeBlock(id={repr(self.id)}, promptEvents={repr(self.prompt_events)}, delayedEvents={repr(self.delayed_events)})"
[docs]class TimeInterval: """Time interval in milliseconds since start of acquisition"""
[docs] start: yardl.UInt32
[docs] stop: yardl.UInt32
def __init__(self, *, start: yardl.UInt32 = 0, stop: yardl.UInt32 = 0, ): self.start = start self.stop = stop
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, TimeInterval) and self.start == other.start and self.stop == other.stop )
[docs] def __str__(self) -> str: return f"TimeInterval(start={self.start}, stop={self.stop})"
[docs] def __repr__(self) -> str: return f"TimeInterval(start={repr(self.start)}, stop={repr(self.stop)})"
[docs]class TimeFrameInformation: """A sequence of time intervals (could be consecutive)"""
[docs] time_frames: list[TimeInterval]
def __init__(self, *, time_frames: typing.Optional[list[TimeInterval]] = None, ): self.time_frames = time_frames if time_frames is not None else []
[docs] def number_of_time_frames(self) -> yardl.Size: return len(self.time_frames)
[docs] def __eq__(self, other: object) -> bool: return ( isinstance(other, TimeFrameInformation) and self.time_frames == other.time_frames )
[docs] def __str__(self) -> str: return f"TimeFrameInformation(timeFrames={self.time_frames})"
[docs] def __repr__(self) -> str: return f"TimeFrameInformation(timeFrames={repr(self.time_frames)})"
[docs]def _mk_get_dtype(): dtype_map: dict[typing.Union[type, types.GenericAlias], typing.Union[np.dtype[typing.Any], typing.Callable[[tuple[type, ...]], np.dtype[typing.Any]]]] = {} get_dtype = _dtypes.make_get_dtype_func(dtype_map) dtype_map.setdefault(CoincidenceEvent, np.dtype([('detector_1_id', np.dtype(np.uint32)), ('detector_2_id', np.dtype(np.uint32)), ('tof_idx', np.dtype(np.uint32)), ('energy_1_idx', np.dtype(np.uint32)), ('energy_2_idx', np.dtype(np.uint32))], align=True)) dtype_map.setdefault(Subject, np.dtype([('name', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True)), ('id', np.dtype(np.object_))], align=True)) dtype_map.setdefault(Institution, np.dtype([('name', np.dtype(np.object_)), ('address', np.dtype(np.object_))], align=True)) dtype_map.setdefault(ExamInformation, np.dtype([('subject', get_dtype(Subject)), ('institution', get_dtype(Institution)), ('protocol', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True)), ('start_of_acquisition', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.datetime64))], align=True))], align=True)) dtype_map.setdefault(Detector, np.dtype([('id', np.dtype(np.uint32)), ('x', np.dtype(np.float32)), ('y', np.dtype(np.float32)), ('z', np.dtype(np.float32))], align=True)) dtype_map.setdefault(ScannerInformation, np.dtype([('model_name', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True)), ('detectors', np.dtype(np.object_)), ('tof_bin_edges', np.dtype(np.object_)), ('tof_resolution', np.dtype(np.float32)), ('energy_bin_edges', np.dtype(np.object_)), ('energy_resolution_at_511', np.dtype(np.float32)), ('listmode_time_block_duration', np.dtype(np.uint32))], align=True)) dtype_map.setdefault(Header, np.dtype([('scanner', get_dtype(ScannerInformation)), ('exam', np.dtype([('has_value', np.dtype(np.bool_)), ('value', get_dtype(ExamInformation))], align=True))], align=True)) dtype_map.setdefault(TimeBlock, np.dtype([('id', np.dtype(np.uint32)), ('prompt_events', np.dtype(np.object_)), ('delayed_events', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True))], align=True)) dtype_map.setdefault(TimeInterval, np.dtype([('start', np.dtype(np.uint32)), ('stop', np.dtype(np.uint32))], align=True)) dtype_map.setdefault(TimeFrameInformation, np.dtype([('time_frames', np.dtype(np.object_))], align=True)) return get_dtype
[docs]get_dtype = _mk_get_dtype()