diff --git a/src/data/Accumulation.py b/src/data/Accumulation.py index 4e220e0..e17933f 100644 --- a/src/data/Accumulation.py +++ b/src/data/Accumulation.py @@ -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