cleanup and documenting more code
Build Debian Packages / build (bookworm, debian12) (push) Successful in 7m43s
Build Debian Packages / build (bullseye, debian11) (push) Failing after 6m57s
Build Debian Packages / build (trixie, debian13) (push) Successful in 8m44s

This commit is contained in:
2026-03-19 14:57:12 +01:00
parent 1d492e940a
commit cfdb01800a
+86 -72
View File
@@ -14,8 +14,6 @@ from .Drawable import Drawable
from .DamarisFFT import DamarisFFT
from .Signalpath import Signalpath
#from DataPool import DataPool
import sys
import threading
import tables
@@ -25,7 +23,30 @@ import ctypes # added by Oleg Petrov
import struct # added by Oleg Petrov
import os # added by Oleg Petrov
class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
"""
Represents an accumulation of sampled data with various attributes and methods
for data manipulation, analysis, and exporting.
This class handles statistical computations and provides mechanisms for thread-safe operations
and data interaction.
Attributes:
xlabel (str): Label for the x-axis.
ylabel (str): Label for the y-axis.
lock (threading.RLock): A reentrant lock to ensure thread-safe operations.
common_descriptions (dict|None): A dictionary containing common descriptions of the data.
time_period (list): A list to store the periods of time relevant to the accumulation.
job_id (int|None): An identifier for the job associated with this accumulation.
use_error (bool): Indicates whether statistical error calculations are enabled.
sampling_rate (float): Sampling frequency of the data.
n (int): Number of accumulations.
cont_data (bool): Indicates whether the object contains valid data.
index (list): List of index bounds for data segments.
x (list): The x-axis data points.
y (list): The y-axis data points.
"""
def __init__(self, x = None, y = None, y_2 = None, n = None, index = None, sampl_freq = None, error = False):
Errorable.__init__(self)
Drawable.__init__(self)
@@ -33,8 +54,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
# Title of this accumulation (plotted in GUI -> look Drawable)
self.__title_pattern = "Accumulation: n = %d"
# Axis-Labels (inherited from Drawable)
self.xlabel = "Time (s)"
# Axis-Labels (inherited from Drawable, overridden)
self.xlabel = "Time / s"
self.ylabel = "Avg. Samples [Digits]"
self.lock=threading.RLock()
@@ -43,7 +64,6 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.job_id = None
self.use_error = error
if self.uses_statistics():
if (y_2 is not None):
self.y_square = y_2
@@ -74,12 +94,10 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.index = index
self.cont_data = True
else:
raise ValueError("Wrong usage of __init__!")
def get_accu_by_index(self, index):
def get_accu_by_index(self, index: int):
self.lock.acquire()
try:
start = self.index[index][0]
@@ -101,27 +119,27 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
def get_ysquare(self, channel):
if self.uses_statistics():
try:
return (self.y_square)[channel]
return self.y_square[channel]
except:
raise
else:
return None
def contains_data(self):
def contains_data(self) -> bool:
return self.cont_data
def get_sampling_rate(self):
"Returns the samplingfrequency"
return self.sampling_rate + 0
def get_sampling_rate(self) -> float:
"""Returns the sampling frequency"""
return self.sampling_rate
def get_index_bounds(self, index):
"Returns a tuple with (start, end) of the wanted result"
def get_index_bounds(self, index) -> tuple[int,int]:
"""Returns a tuple with (start, end) of the wanted result"""
return self.index[index]
def uses_statistics(self):
def uses_statistics(self) -> bool:
return self.use_error
# external interface --------------------------------------------------------------------
@@ -185,8 +203,10 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
return min(tmp_min)
def get_ymax(self):
def get_ymax(self) -> float|int:
"""
Returns the global maximum value of all channels.
"""
if not self.contains_data():
return 0
@@ -201,17 +221,14 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.lock.release()
return max(tmp_max)
def get_job_id(self):
# return None
def get_job_id(self) -> int:
return self.job_id # modified by Oleg Petrov
def write_to_csv(self, destination=sys.stdout, delimiter=" "):
"""
writes the data to a file or to sys.stdout
destination can be a file or a filename
suitable for further processing
writes the data to a file.
destination can be a filehandle or a filename, default sys.stdout
"""
the_destination=destination
if isinstance(destination, str):
the_destination=open(destination, "w")
@@ -254,9 +271,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
for further processing with the NMRnotebook software;
destination can be a file or a filename
"""
# write sorted
the_destination=destination
if type(destination) in (str,):
if isinstance(destination, str):
the_destination=open(destination, "w")
self.lock.acquire()
@@ -275,8 +291,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
the_destination.write("%g%s"%(ydata[j][i], delimiter))
the_destination.write("\n")
the_destination.write("END\n")
the_destination=None
xdata=ydata=None
the_destination.close()
finally:
self.lock.release()
@@ -409,6 +424,17 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
# -----------------------------------------------------------------------
def write_to_hdf(self, hdffile, where, name, title, complib=None, complevel=None):
"""
Write the accumulation data to an HDF5 file.
Parameters:
- hdffile: The HDF5 file object to write to.
- where: The location within the HDF5 file where the data should be stored.
- name: The name of the dataset within the HDF5 file.
- title: The title or description of the dataset.
- complib: Compression library to use for the dataset.
- complevel: Compression level for the dataset.
"""
accu_group=hdffile.create_group(where=where,name=name,title=title)
accu_group._v_attrs.damaris_type="Accumulation"
if self.contains_data():
@@ -416,20 +442,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
try:
# save time stamps
if self.time_period is not None and len(self.time_period)>0:
accu_group._v_attrs.earliest_time="%04d%02d%02d %02d:%02d:%02d.%03d"%(self.time_period[0].year,
self.time_period[0].month,
self.time_period[0].day,
self.time_period[0].hour,
self.time_period[0].minute,
self.time_period[0].second,
self.time_period[0].microsecond/1000)
accu_group._v_attrs.oldest_time="%04d%02d%02d %02d:%02d:%02d.%03d"%(self.time_period[1].year,
self.time_period[1].month,
self.time_period[1].day,
self.time_period[1].hour,
self.time_period[1].minute,
self.time_period[1].second,
self.time_period[1].microsecond/1000)
accu_group._v_attrs.earliest_time = self.time_period[0].isoformat(sep=' ', timespec='milliseconds')
accu_group._v_attrs.oldest_time = self.time_period[1].isoformat(sep=' ', timespec='milliseconds')
if self.common_descriptions is not None:
for (key,value) in self.common_descriptions.items():
accu_group._v_attrs.__setattr__("description_"+key,str(value))
@@ -519,7 +533,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
# External interfaces ------------------------------------------------------------------
# Overloaded operators -----------------------------------------------------------------
def __len__(self):
def __len__(self) -> int:
"""
return number of samples per channel, 0 if empty
"""
@@ -527,8 +541,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
return len(self.y[0])
return 0
def __repr__(self):
"Redefining repr(Accumulation)"
def __repr__(self) -> str:
"""Redefining repr(Accumulation)"""
if not self.contains_data():
return "Empty"
@@ -551,17 +565,22 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
def __add__(self, other):
"Redefining self + other"
# Float or int
"""
Redefining self + other
We define this for different types of other:
1) Accumulation + number
2) Accumulation + ADC_Result
3) Accumulation + Accumulation
This returns a new Accumulation object.
"""
# Float or int: add to each channel
if isinstance(other, int) or isinstance(other, float):
if not self.contains_data():
raise ValueError("Accumulation: You cant add integers/floats to an empty accumulation")
else:
tmp_y = []
tmp_ysquare = []
self.lock.acquire()
for i in range(self.get_number_of_channels()):
# Dont change errors and mean value
@@ -579,15 +598,12 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
# ADC_Result
elif str(other.__class__) == "damaris.data.ADC_Result.ADC_Result":
# Other empty (return)
# todo: this is seems to be bugy!!!! (Achim)
if not other.contains_data():
return
# Self empty (copy)
if not self.contains_data():
tmp_y = []
tmp_ysquare = []
@@ -597,14 +613,13 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
tmp_y.append(numpy.array(other.y[i], dtype="float32"))
if self.uses_statistics():
tmp_ysquare.append(tmp_y[i] ** 2)
if self.uses_statistics():
r = Accumulation(x = numpy.array(other.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = 1, index = other.index, sampl_freq = other.sampling_rate, error = True)
else:
r = Accumulation(x = numpy.array(other.x, dtype="float32"), y = tmp_y, index = other.index, sampl_freq = other.sampling_rate, n = 1, error = False)
r.time_period=[other.job_date,other.job_date]
r.job_id = other.job_id # added by Oleg Petrov
r.job_id = other.job_id
r.common_descriptions=other.description.copy()
self.lock.release()
return r
@@ -614,14 +629,14 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.lock.acquire()
if self.sampling_rate != other.get_sampling_rate():
raise ValueError("Accumulation: You cant add ADC-Results with diffrent sampling-rates")
raise ValueError("Accumulation: You cant add ADC_Results with different sampling-rates")
if len(self.y[0]) != len(other):
raise ValueError("Accumulation: You cant add ADC-Results with diffrent number of samples")
raise ValueError("Accumulation: You cant add ADC_Results with different number of samples")
if len(self.y) != other.get_number_of_channels():
raise ValueError("Accumulation: You cant add ADC-Results with diffrent number of channels")
raise ValueError("Accumulation: You cant add ADC_Results with different number of channels")
for i in range(len(self.index)):
if self.index[i] != other.get_index_bounds(i):
raise ValueError("Accumulation: You cant add ADC-Results with diffrent indexing")
raise ValueError("Accumulation: You cant add ADC_Results with different indexing")
tmp_y = []
tmp_ysquare = []
@@ -637,7 +652,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n + 1, index = self.index, sampl_freq = self.sampling_rate, error = False)
r.time_period=[min(self.time_period[0],other.job_date),
max(self.time_period[1],other.job_date)]
r.job_id = other.job_id # added by Oleg Petrov
r.job_id = other.job_id
if self.common_descriptions is not None:
r.common_descriptions={}
for key in list(self.common_descriptions.keys()):
@@ -649,7 +664,6 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
# Accumulation
elif str(other.__class__) == "damaris.data.Accumulation.Accumulation":
# Other empty (return)
if not other.contains_data():
return
@@ -670,7 +684,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
tmp_y.append(other.y[i])
tmp_ysquare.append(other.y_square[i])
r.time_period=other.time_period[:]
r.job_id = other.job_id # added by Oleg Petrov
r.job_id = other.job_id
if other.common_descriptions is not None:
r.common_descriptions=other.common_descriptions.copy()
else:
@@ -684,14 +698,14 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.lock.acquire()
if self.sampling_rate != other.get_sampling_rate():
raise ValueError("Accumulation: You cant add accumulations with diffrent sampling-rates")
raise ValueError("Accumulation: You cant add accumulations with different sampling-rates")
if len(self.y[0]) != len(other):
raise ValueError("Accumulation: You cant add accumulations with diffrent number of samples")
raise ValueError("Accumulation: You cant add accumulations with different number of samples")
if len(self.y) != other.get_number_of_channels():
raise ValueError("Accumulation: You cant add accumulations with diffrent number of channels")
raise ValueError("Accumulation: You cant add accumulations with different number of channels")
for i in range(len(self.index)):
if self.index[i] != other.get_index_bounds(i):
raise ValueError("Accumulation: You cant add accumulations with diffrent indexing")
raise ValueError("Accumulation: You cant add accumulations with different indexing")
if self.uses_statistics() and not other.uses_statistics():
raise ValueError("Accumulation: You cant add non-error accumulations to accumulations with error")
@@ -722,22 +736,22 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
def __radd__(self, other):
"Redefining other + self"
"""Redefining other + self"""
return self.__add__(other)
def __sub__(self, other):
"Redefining self - other"
"""Redefining self - other"""
return self.__add__(-other)
def __rsub__(self, other):
"Redefining other - self"
"""Redefining other - self"""
return self.__neg__(self.__add__(-other))
def __iadd__(self, other):
"Redefining self += other"
"""Redefining self += other"""
# Float or int
if isinstance(other, int) or isinstance(other, float):
if not self.contains_data():
@@ -892,12 +906,12 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
def __isub__(self, other):
"Redefining self -= other"
"""Redefining self -= other"""
return self.__iadd__(-other)
def __neg__(self):
"Redefining -self"
"""Redefining -self"""
if not self.contains_data():
return