559 lines
23 KiB
Python
559 lines
23 KiB
Python
# -*- 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
|