# -*- coding: iso-8859-1 -*- from .Resultable import Resultable from .Drawable import Drawable from .Signalpath import Signalpath from .DamarisFFT import DamarisFFT import threading import numpy import sys import datetime import tables ############################################################################# # # # Name: Class ADC_Result # # # # Purpose: Specialised class of Resultable and Drawable # # Contains recorded ADC Data # # # ############################################################################# class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath): """ Represents the result of an ADC, encapsulating data and metadata for processing, visualization, and export. This class combines data storage and manipulation functionality with interfaces for drawing and result processing. It manages time-series data across multiple channels, supports dynamic resizing of datasets, and provides mechanisms to export data in various formats. Its key roles include ADC result storage, metadata management, and integration with external systems. Attributes: xlabel: Label for the x-axis, used in visualization. ylabel: Label for the y-axis, used in visualization. lock: A threading lock for synchronizing access to the data. nChannels: Number of data channels in the result set. sampling_rate: Sampling frequency of the ADC data. job_id: identifier for the job that generated this result. """ def __init__(self, x = None, y:list = None, index = None, sampl_freq = None, desc = None, job_id = None, job_date = None): Resultable.__init__(self) Drawable.__init__(self) # Title of this accumulation: set Values: Job-ID and Description (plotted in GUI -> look Drawable) # Is set in ResultReader.py (or in copy-construktor) self.__title_pattern = "ADC-Result: job_id = %s, desc = %s" # Axis-Labels (inherited from Drawable) self.xlabel = "Time (s)" self.ylabel = "Samples [Digits]" self.lock=threading.RLock() self.nChannels = 0 # using no argument for initialization if (x is None) and (y is None) and (index is None) and (sampl_freq is None) and (desc is None) and (job_id is None) and (job_date is None): self.cont_data = False self.sampling_rate = 0 self.index = [] self.x = [] self.y = [] # using all arguments for initialization elif (x is not None) and (y is not None) and (index is not None) and (sampl_freq is not None) and (desc is not None) and (job_id is not None) and (job_date is not None): # TODO: insure integer calculations for ADC_Result operations. #for ch in y: # if not numpy.issubdtype(ch.dtype, numpy.integer): # raise TypeError("TypeError: ADC_Result y data must be a list with integer type channels") self.x = x self.y = y self.index = index self.sampling_rate = sampl_freq self.cont_data = True self.description = desc self.job_id = job_id self.job_date = job_date title="ADC-Result: job-id=%d"%int(self.job_id) if len(self.description)>0: for k,v in self.description.items(): # string keys can be made invisible by adding two underscores in front of them if not (type(k) in (str,) and k[0] == '_' and k[1] == '_'): title+=", %s=%s"%(k,v) self.set_title(title) else: raise ValueError("Wrong usage of __init__!") def create_data_space(self, channels, samples): "Initialises the internal data-structures" if self.contains_data(): print("Warning ADC-Result: Tried to run \"create_data_space()\" more than once.") return raise ValueError("ValueError: You cant create an ADC-Result with less than 1 sample!") for i in range(channels): self.y.append(numpy.zeros((samples,), dtype="int16")) self.x = numpy.zeros((samples,), dtype="float32") self.index.append((0, samples-1)) self.cont_data = True def contains_data(self): """Returns true if ADC_Result contains data. (-> create_data_space() was called)""" return self.cont_data def add_sample_space(self, samples): "Adds space for n samples, where n can also be negative (deletes space). New space is filled up with \"0\"" self.lock.acquire() if not self.cont_data: print("Warning ADC-Result: Tried to resize empty array!") return length = len(self.y[0]) self.x = numpy.resize(self.x, (length+samples)) for i in range(self.get_number_of_channels()): self.y[i] = numpy.resize(self.y[i], (length+samples)) self.index.append((length, len(self.y[0])-1)) self.lock.release() def get_result_by_index(self, index): self.lock.acquire() try: start = self.index[index][0] end = self.index[index][1] except: self.lock.release() raise tmp_x = self.x[start:end+1].copy() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(self.y[i][start:end+1].copy()) r = ADC_Result(x = tmp_x, y = tmp_y, index = [(0,len(tmp_y[0])-1)], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r def get_sampling_rate(self): "Returns the samplingfrequency" return self.sampling_rate + 0 def set_sampling_rate(self, hz): "Sets the samplingfrequency in hz" self.sampling_rate = float(hz) def get_nChannels(self): "Gets the number of channels" return self.nChannels + 0 def set_nChannels(self, channels): "Sets the number of channels" self.nChannels = int(channels) def get_index_bounds(self, index): "Returns a tuple with (start, end) of the wanted result" return self.index[index] def uses_statistics(self): return False 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 """ # write sorted the_destination=destination if type(destination) in (str,): the_destination=open(destination, "w") the_destination.write("# adc_result\n") the_destination.write("# t y0 y1 ...\n") self.lock.acquire() try: xdata=self.get_xdata() ch_no=self.get_number_of_channels() ydata=list(map(self.get_ydata, range(ch_no))) #yerr=map(self.get_yerr, xrange(ch_no)) for i in range(len(xdata)): the_destination.write("%e"%xdata[i]) for j in range(ch_no): the_destination.write("%s%e"%(delimiter, ydata[j][i])) the_destination.write("\n") the_destination=None xdata=ydata=None finally: self.lock.release() def write_to_simpson(self, destination=sys.stdout, delimiter=" "): """ writes the data to a text file or sys.stdout in Simpson format, 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,): the_destination=open(destination, "w") self.lock.acquire() try: xdata=self.get_xdata() the_destination.write("SIMP\n") the_destination.write("%s%i%s"%("NP=", len(xdata), "\n")) the_destination.write("%s%i%s"%("SW=", self.get_sampling_rate(), "\n")) the_destination.write("TYPE=FID\n") the_destination.write("DATA\n") ch_no=self.get_number_of_channels() ydata=list(map(self.get_ydata, range(ch_no))) for i in range(len(xdata)): for j in range(ch_no): the_destination.write("%g%s"%(ydata[j][i], delimiter)) the_destination.write("\n") the_destination.write("END\n") the_destination=None xdata=ydata=None finally: self.lock.release() def write_to_hdf(self, hdffile, where, name, title, complib=None, complevel=None): accu_group=hdffile.create_group(where=where,name=name,title=title) accu_group._v_attrs.damaris_type="ADC_Result" if self.contains_data(): self.lock.acquire() try: # save time stamps if "job_date" in dir(self) and self.job_date is not None: accu_group._v_attrs.time="%04d%02d%02d %02d:%02d:%02d.%03d"%(self.job_date.year, self.job_date.month, self.job_date.day, self.job_date.hour, self.job_date.minute, self.job_date.second, self.job_date.microsecond/1000) if self.description is not None: for (key,value) in self.description.items(): accu_group._v_attrs.__setattr__("description_"+key,str(value)) accu_group._v_attrs.__setattr__("sampling_rate",self.sampling_rate) # save interval information filter=None if complib is not None: if complevel is None: complevel=9 filter=tables.Filters(complevel=complevel,complib=complib,shuffle=1) index_table=hdffile.create_table(where=accu_group, name="indices", description={"start": tables.UInt64Col(), "length": tables.UInt64Col(), "start_time": tables.Float32Col(), "dwelltime": tables.Float32Col()}, title="indices of adc data intervals", filters=filter, expectedrows=len(self.index)) index_table.flavor="numpy" # save channel data new_row=index_table.row for i in range(len(self.index)): new_row["start"]=self.index[i][0] new_row["dwelltime"]=1.0/self.sampling_rate new_row["start_time"]=1.0/self.sampling_rate*self.index[i][0] new_row["length"]=self.index[i][1]-self.index[i][0]+1 new_row.append() index_table.flush() new_row=None index_table=None # prepare saving data channel_no=len(self.y) timedata=numpy.empty((len(self.y[0]),channel_no), dtype = "int16") for ch in range(channel_no): timedata[:,ch]=self.get_ydata(ch) # save data time_slice_data=None if filter is not None: chunkshape = numpy.shape(timedata) if len(chunkshape) <= 1: chunkshape = (min(chunkshape[0],1024*8),) else: chunkshape = (min(chunkshape[0],1024*8), chunkshape[1]) if tables.__version__[0]=="1": time_slice_data=hdffile.create_carray(accu_group, name="adc_data", shape=timedata.shape, atom=tables.Int16Atom(shape=chunkshape, flavor="numpy"), filters=filter, title="adc data") else: time_slice_data=hdffile.create_carray(accu_group, name="adc_data", shape=timedata.shape, chunkshape=chunkshape, atom=tables.Int16Atom(), filters=filter, title="adc data") time_slice_data[:]=timedata else: time_slice_data=hdffile.create_array(accu_group, name="adc_data", obj=timedata, title="adc data") finally: timedata=None time_slice_data=None accu_group=None self.lock.release() # Ueberladen von Operatoren und Built-Ins ------------------------------------------------------- def __len__(self): "Redefining len(ADC_Result obj), returns the number of samples in one channel and 0 without data" if len(self.y)>0: return len(self.y[0]) return 0 def __repr__(self): """ writes job meta data and data to string returned """ tmp_string = "Job ID: " + str(self.job_id) + "\n" tmp_string += "Job Date: " + str(self.job_date) + "\n" tmp_string += "Description: " + str(self.description) + "\n" if len(self.y)>0: tmp_string += "Indexes: " + str(self.index) + "\n" tmp_string += "Samples per Channel: " + str(len(self.y[0])) + "\n" tmp_string += "Samplingfrequency: " + str(self.sampling_rate) + "\n" tmp_string += "X: " + repr(self.x) + "\n" for i in range(self.get_number_of_channels()): tmp_string += ("Y(%d): " % i) + repr(self.y[i]) + "\n" return tmp_string def __add__(self, other): "Redefining self + other (scalar)" if isinstance(other, int) or isinstance(other, float): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(numpy.array(self.y[i], dtype="float32") + other) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot add \"{other.__class__}\" to ADC-Result!") def __radd__(self, other): "Redefining other (scalar) + self" return self.__add__(other) def __sub__(self, other): "Redefining self - other (scalar)" if isinstance(other, int) or isinstance(other, float): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(numpy.array(self.y[i], dtype="float32") - other) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot subtract \"{other.__class__}\" to ADC-Result!") def __rsub__(self, other): "Redefining other (scalar) - self" if isinstance(other, int) or isinstance(other, float): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(other - numpy.array(self.y[i], dtype="float32")) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot subtract \"{other.__class__}\" to ADC-Result!") def __mul__(self, other): "Redefining self * other (scalar)" if isinstance(other, int) or isinstance(other, float): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(numpy.array(self.y[i], dtype="float32") * other) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot multiply \"{other.__class__}\" to ADC-Result!") def __rmul__(self, other): "Redefining other (scalar) * self" return self.__mul__(other) def __pow__(self, other): "Redefining self ** other (scalar)" if isinstance(other, int) or isinstance(other, float): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(numpy.array(self.y[i], dtype="float32") ** other) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot power raise \"{other.__class__}\" to ADC-Result!") def __truediv__(self, other): "Redefining other (scalar) / self" if isinstance(other, int) or isinstance(other, float): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(other / numpy.array(self.y[i], dtype="float32")) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot divide \"{other.__class__}\" to ADC-Result!") def __rtruediv__(self, other): "Redefining other (scalar) / self" return self.__truediv__(other) def __floordiv__(self, other): "Redefining other (scalar) / self" if isinstance(other, float): raise ValueError("ValueError: Cannot use floor division (//) on floats! Use \"//\" instead of \"/\"! ") if isinstance(other, int): self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(other / numpy.array(self.y[i], dtype="float32")) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r else: raise ValueError(f"ValueError: Cannot divide \"{other.__class__}\" to ADC-Result!") def __rfloordiv__(self, other): "Redefining other (scalar) / self" return self.__floordiv__(other) def __neg__(self): "Redefining -self" self.lock.acquire() tmp_y = [] for i in range(self.get_number_of_channels()): tmp_y.append(numpy.array(-self.y[i])) r = ADC_Result(x = self.x[:], y = tmp_y, index = self.index[:], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date) self.lock.release() return r def read_from_hdf(hdf_node): """ read accumulation data from HDF node and return it. """ # formal checks first if not isinstance(hdf_node, tables.Group): return None if hdf_node._v_attrs.damaris_type!="ADC_Result": return None if not (hdf_node.__contains__("indices") and hdf_node.__contains__("adc_data")): return None # job id and x,y titles are missing adc=ADC_Result() # populate description dictionary adc.description={} for attrname in hdf_node._v_attrs._v_attrnamesuser: if attrname.startswith("description_"): adc.description[attrname[12:]]=hdf_node._v_attrs.__getattr__(attrname) if "time" in dir(hdf_node._v_attrs): timestring=hdf_node._v_attrs.__getattr__("time") adc.job_date=datetime.datetime(int(timestring[:4]), # year int(timestring[4:6]), # month int(timestring[6:8]), # day int(timestring[9:11]), # hour int(timestring[12:14]), # minute int(timestring[15:17]), # second int(timestring[18:21])*1000 # microsecond ) # start with indices for r in hdf_node.indices.iterrows(): adc.index.append((r["start"],r["start"]+r["length"]-1)) adc.sampling_rate=1.0/r["dwelltime"] # now really belief there are no data if len(adc.index)==0: adc.cont_data=False return adc adc.cont_data=True # now do the real data adc_data=hdf_node.adc_data.read() adc.x=numpy.arange(adc_data.shape[0], dtype="float32")/adc.sampling_rate for ch in range(adc_data.shape[1]): adc.y.append(adc_data[:,ch]) return adc