# Copyright (c) 2025. Pedro Encarnação . Universidade de Aveiro LICENSE: CC BY-NC-SA 4.0 # ****************************
#
# *******************************************************
# * FILE: scanning_body
# * AUTHOR: Pedro Encarnação
# * DATE: 26/03/2025
# * LICENSE: "CC BY-NC-SA 4.0"
# *******************************************************
"""
ListMode body of the TOR file
"""
import numpy as np
[docs]
class ListModeBody:
"""
Class to store the statistics of the listmode data
Attributes:
listmode: numpy array
listmodeFields: list of strings
mean: numpy array with the mean for each field
std: numpy array with the standard deviation for each field
min: numpy array with the minimum for each field
max: numpy array with the maximum for each field
median: numpy array with the median for each field
numberOfEvents: int
numberOfEventsPerSecond: int
numberOfEventsPerFrame: list of int
"""
def __init__(self):
self._listmode = None
self._listmodeFields = None
self._mean = None
self._std = None
self._min = None
self._max = None
self._median = None
self._numberOfEvents = None
self._numberOfEventsPerSecond = None
self._numberOfEventsPerFramePerSecond = []
self._minDiff = []
self._uniqueValues = []
self._uniqueValuesCount = []
self._numberOfMotors = None
self._frameStartIndexes = None
self._coincidenceTimeDiff = None
self._globalDetectorID = None
self._countsPerGlobalID = None
self._indexCountsPerGlobalID = None
@property
def countsPerGlobalID(self):
return self._countsPerGlobalID
@property
def indexCountsPerGlobalID(self):
return self._indexCountsPerGlobalID
[docs]
def setCountsPerGlobalID(self):
globalID, indexCountsPerGlobalID, counts = np.unique(self._globalDetectorID, return_index=True, return_counts=True)
self._indexCountsPerGlobalID = indexCountsPerGlobalID
self._countsPerGlobalID = np.ascontiguousarray(counts, dtype=np.int32)
[docs]
def setListModeHistogramHybridMode(self):
originalListmode = self._listmode[self._indexCountsPerGlobalID, :]
self._listmode = np.zeros((self._countsPerGlobalID.shape[0], len(self._listmodeFields) + 1))
self._listmode[:, :-1] = originalListmode
self._listmode[:, -1] = self._countsPerGlobalID
self._listmodeFields.append("Counts")
@property
def globalDetectorID(self):
return self._globalDetectorID
[docs]
def setGlobalDetectorID(self, globalDetectorID=None):
# Needs to run after generateStatistics
if globalDetectorID is None:
print("Global detector ID not set. Automatically setting is going to run. Note that incomplete data could set a wrong global ID leading to incorrect reconstruction")
print("Number of motors detected: ", self._numberOfMotors)
acceptableFields = ["motor", "Motor", "MOTOR"]
keys_with_motor = [key for key in self._listmodeFields if any(acceptableField in key for acceptableField in acceptableFields)]
step_size = []
range_motor = []
for motor in keys_with_motor:
step_ = self._minDiff[self._listmodeFields.index(motor)]
range_ = self.uniqueValuesCount[self._listmodeFields.index(motor)]
print(motor, " step", step_)
print(motor, " range", range_)
step_size.append(step_)
range_motor.append(range_)
acceptableFields = ["ID", "id", "Id"]
keys_with_ID = [key for key in self._listmodeFields if any(acceptableField in key for acceptableField in acceptableFields)]
range_ID = []
for ID in keys_with_ID:
range_ = self.uniqueValuesCount[self._listmodeFields.index(ID)]
print(ID, " number of detectors", range_)
range_ID.append(range_)
motor_angle_normalized_0 = self._listmode[:, self._listmodeFields.index(keys_with_motor[0])] + np.abs(self._listmode[:, self._listmodeFields.index(keys_with_motor[0])].min())
motor_angle_normalized_1 = self._listmode[:, self._listmodeFields.index(keys_with_motor[1])] + np.abs(self._listmode[:, self._listmodeFields.index(keys_with_motor[1])].min())
globalID = (motor_angle_normalized_0 / step_size[0] +
range_motor[0] * motor_angle_normalized_1 / step_size[1] +
range_motor[1] * range_motor[0] * self._listmode[:, self._listmodeFields.index(keys_with_ID[0])] +
range_ID[0] * range_motor[1] * range_motor[0] * (self._listmode[:, self._listmodeFields.index(keys_with_ID[1])]))
self._globalDetectorID = globalID.astype(int)
print("GlobalID_maximum: ", self._globalDetectorID.max())
print("GlobalID_minimum: ", self._globalDetectorID.min())
print("Expected GlobalID maximum: ", range_motor[0]*range_motor[1]*range_ID[0]*range_ID[1])
@property
def listmode(self):
return self._listmode
[docs]
def setListmode(self, listmode, regenerateStats=False):
"""
Set the listmode data
"""
self._listmode = listmode
if regenerateStats:
self.regenerateStats()
[docs]
def regenerateStats(self):
# listModeBody.setFrameStartIndexes(scanHeader.indexesOfFrames) " FuTure implementation
self.generateStatistics()
self.printStatistics()
self.setGlobalDetectorID()
self.setCountsPerGlobalID()
[docs]
def resetListmode(self):
"""
Call this method before store the listmode data to reduce memory usage
"""
self._listmode = None
@property
def listmodeFields(self):
return self._listmodeFields
[docs]
def setListmodeFields(self, listmodeFields):
self._listmodeFields = listmodeFields
@property
def mean(self):
return self._mean
[docs]
def setMeanValues(self):
self._mean = np.mean(self._listmode, axis=0)
@property
def std(self):
return self._std
[docs]
def setStdValues(self):
self._std = np.std(self._listmode, axis=0)
@property
def min(self):
return self._min
[docs]
def setMinValues(self):
self._min = np.min(self._listmode, axis=0)
@property
def max(self):
return self._max
[docs]
def setMaxValues(self):
self._max = np.max(self._listmode, axis=0)
@property
def median(self):
return self._median
[docs]
def setMedianValues(self):
self._median = np.median(self._listmode, axis=0)
@property
def numberOfEvents(self):
return self._numberOfEvents
[docs]
def setNumberOfEvents(self):
self._numberOfEvents = len(self._listmode)
@property
def numberOfEventsPerSecond(self):
# find indexes of the collumn time array in self._listmodefields
return self._numberOfEventsPerSecond
[docs]
def setNumberOfEventsPerSecond(self):
acceptableFields = ["TIME", "time", "Time"]
timeIndex = None
for i, field in enumerate(self._listmodeFields):
if field in acceptableFields:
timeIndex = i
break
if timeIndex is None:
raise ValueError("Time field not found in listmode fields")
self._numberOfEventsPerSecond = self._numberOfEvents / self._listmode[-1, timeIndex]
@property
def numberOfEventsPerFramePerSecond(self):
return self._numberOfEventsPerFramePerSecond
[docs]
def setNumberOfEventsPerFramePerSecond(self):
if self._frameStartIndexes is None:
raise ValueError("Frame start indexes not set")
for i in range(len(self._frameStartIndexes) - 1):
time_frame = self._listmode[self._frameStartIndexes[i + 1], self._listmodeFields.index("TIME")] - \
self._listmode[self._frameStartIndexes[i], self._listmodeFields.index("TIME")]
self._numberOfEventsPerFramePerSecond.append((self._frameStartIndexes[i + 1] - self._frameStartIndexes[
i]) / time_frame)
@property
def frameStartIndexes(self) -> list:
return self._frameStartIndexes
[docs]
def setFrameStartIndexes(self, frameStartIndexes):
self._frameStartIndexes = frameStartIndexes
@property
def numberOfMotors(self):
return self._numberOfMotors
[docs]
def setNumberOfMotors(self):
# check if th fields include "motor" string
self._numberOfMotors = 0
acceptableFields = ["motor", "Motor", "MOTOR"]
for field in self._listmodeFields:
for acceptableField in acceptableFields:
if acceptableField in field:
self._numberOfMotors += 1
@property
def minDiff(self):
return self._minDiff
[docs]
def setMinDiff(self):
for i in range(len(self._listmodeFields)):
if len(self._uniqueValues[i]) > 1:
self._minDiff.append(np.abs(np.min(np.diff(self._uniqueValues[i]))))
elif len(self._uniqueValues[i]) <= 1:
self._minDiff.append(None)
@property
def uniqueValues(self):
return self._uniqueValues
[docs]
def setUniqueValues(self):
for i in range(len(self._listmodeFields)):
unique_values, counts = np.unique(np.round(self._listmode[:, i], 4), return_counts=True)
self._uniqueValues.append(unique_values)
@property
def uniqueValuesCount(self):
return self._uniqueValuesCount
[docs]
def setUniqueValuesCount(self):
for i in range(len(self._listmodeFields)):
self._uniqueValuesCount.append(len(self._uniqueValues[i]))
[docs]
def generateStatistics(self):
"""
Generate statistics for the listmode data
"""
self.setMeanValues()
self.setStdValues()
self.setMinValues()
self.setMaxValues()
self.setMedianValues()
self.setNumberOfEvents()
self.setNumberOfMotors()
self.setUniqueValues()
self.setUniqueValuesCount()
self.setMinDiff()
self.setNumberOfEventsPerFramePerSecond()
self.setNumberOfEventsPerSecond()
[docs]
def printStatistics(self):
"""
Print the statistics of the listmode data
"""
for field in self._listmodeFields:
print(f"Field: {field}")
print("...............................")
print(f"Mean: {np.round(self._mean[self._listmodeFields.index(field)],4)}")
print(f"Std: {np.round(self._std[self._listmodeFields.index(field)], 4)}")
print(f"Range: {np.round(self._min[self._listmodeFields.index(field)],5)} to {np.round(self._max[self._listmodeFields.index(field)],5)}")
print(f"Median: {np.round(self._median[self._listmodeFields.index(field)],5)}")
if self._minDiff[self._listmodeFields.index(field)] is not None:
print(f"Min diff: {np.round(self._minDiff[self._listmodeFields.index(field)],10)}")
else:
print("Min diff: None")
# print(f"Unique values: {self._uniqueValues[self._listmodeFields.index(field)]}")
print(f"Number of unique values: {self._uniqueValuesCount[self._listmodeFields.index(field)]}")
print("-------------------------------\n")
print(f"Number of events: {self._numberOfEvents}")
print(f"Number of events per second: {self._numberOfEventsPerSecond}")
print(f"Number of events per frame per second: {self._numberOfEventsPerFramePerSecond}")
print(f"Number of motors: {self._numberOfMotors}")
[docs]
def saveVarsToFile(self, filename):
"""
"""
with open(filename, "w") as file:
self.printStatistics()
def __str__(self):
"""
String representation of the ListModeBody object
"""
return f"ListModeBody with {self.numberOfEvents} events"
def __repr__(self):
return f"ListModeBody with {self.numberOfEvents} events"
def __len__(self):
"""
Get the number of events in the listmode data
"""
return self.numberOfEvents
def __getitem__(self, key):
"""
Get an item from the listmode data
"""
if isinstance(key, str):
return self._listmode[:, self._listmodeFields.index(key)]
return self._listmode[key]
def __iter__(self):
"""
Iterate over the listmode data
"""
return iter(self._listmode)
def __contains__(self, item):
"""
Check if the item is in the listmode data
"""
return item in self._listmode