move to a more standard python packaging structure
Build Debian Packages / build (bookworm, debian12) (push) Successful in 13m3s
Build Debian Packages / build (trixie, debian13) (push) Successful in 13m46s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled

This commit is contained in:
2026-04-07 17:09:10 +02:00
parent 1322fd3835
commit 6abb338c4a
43 changed files with 245 additions and 83 deletions
+18
View File
@@ -0,0 +1,18 @@
##\mainpage DArmstadt MAgnetic Resonance Instrument Software
#
#Python Frontend based on
# - Python/GTK
# - Matplotlib
# - Numpy
# - PyTables
#
#Written by
# - Achim Gaedke
# - Christopher Schmitt
# - Markus Rosenstihl
# - Holger Stork
# - Christian Tacke
## module contents
#
__all__=["experiments", "data", "gui"]
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""
DAMARISi3 command-line interface.
"""
import sys
import os
import argparse
import sqlite3
# for numpy-1.1 and later: check the environment for LANG and LC_NUMERIC
# see: http://projects.scipy.org/scipy/numpy/ticket/902
if os.environ.get("LANG", "").startswith("de") or os.environ.get("LC_NUMERIC", "").startswith("de"):
os.environ["LC_NUMERIC"] = "C"
def main():
"""Main entry point for DAMARIS."""
parser = argparse.ArgumentParser(description='DArmstadt MAgnetic Resonance Instrumentation Software')
parser.add_argument("--run", action="store_true", help="run DAMARIS immediately with given scripts")
parser.add_argument("--clean", action="store_true", help="cleanup DAMARIS run files")
parser.add_argument("--debug", action="store_true", help="run DAMARIS with DEBUG flag set")
parser.add_argument("--mpl", help="run DAMARIS with matplotlib backend",
choices=["GTK3Agg", "GTK3Cairo"], default="GTK3Agg")
parser.add_argument("exp_script", help="experiment script", nargs="?", metavar="EXP.py")
parser.add_argument("res_script", help="result script", nargs="?", metavar="RES.py")
args = parser.parse_args()
import matplotlib
if args.mpl:
matplotlib.use(args.mpl)
import damaris.gui.DamarisGUI
lockfile = os.path.expanduser('~/.damaris.lockdb')
if args.clean:
if os.path.exists(lockfile):
print("Removing lockfile: %s" % lockfile)
os.remove(lockfile)
else:
print("Lockfile does not exists: %s" % lockfile)
lockdb = sqlite3.connect(lockfile)
c = lockdb.cursor()
c.execute("CREATE TABLE IF NOT EXISTS damaris (uuid text, status text)")
lockdb.commit()
if args.debug:
damaris.gui.DamarisGUI.debug = True
print("debug flag set")
try:
import resource
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
except ImportError:
pass
print(args)
d = damaris.gui.DamarisGUI.DamarisGUI(args.exp_script, args.res_script, start_immediately=args.run)
d.run()
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
if __name__ == "__main__":
main()
+558
View File
@@ -0,0 +1,558 @@
# -*- 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
File diff suppressed because it is too large Load Diff
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: iso-8859-1 -*-
from .Resultable import Resultable
#############################################################################
# #
# Name: Class Error_Result #
# #
# Purpose: Specialised class of Resultable #
# Contains occured error-messages from the core #
# #
#############################################################################
class Config_Result(Resultable):
def __init__(self, config = None, desc = None, job_id = None, job_date = None):
Resultable.__init__(self)
if config is None:
self.config = { }
if desc is None:
self.description = { }
self.job_id = job_id
self.job_date = job_date
def get_config_dictionary(self):
return self.config
def set_config_dictionary(self, config):
self.config = config
def get_config(self, key):
if key in self.config:
return self.config[key]
else:
return None
def set_config(self, key, value):
if key in self.config:
print("Warning Config_Result: Key \"%s\" will be overwritten with \"%s\"" % (key, value))
self.config[key] = value
# Ueberladen von Operatoren und Built-Ins -------------------------------------------------------
def __repr__(self):
return str(self.config)
def __str__(self):
return str(self.config)
+215
View File
@@ -0,0 +1,215 @@
import numpy
from . import autophase
class DamarisFFT:
"""
Class for Fourier transforming data.
Provides several helper and apodization functions
"""
def clip( self, start=None, stop=None ):
"""
Method for clipping data, returns only the data between start and stop
start and stop can be either time or frequency.
The unit is automatically determined (Hz or s).
:param float start: beginning of clipping in s
:param float stop: end of clipping in s
"""
# check if start/stop order is properly
if start > stop:
start, stop = stop, start
# do nothing if one uses clip as a "placeholder"
if start is None and stop is None:
return self
if start is None:
start = self.x[ 0 ]
if stop is None:
stop = self.x[ -1 ]
# check if data is fft which changes the start/stop units
if self.xlabel == "Frequency / Hz":
start = self.x.size * (0.5 + start / self.sampling_rate)
stop = self.x.size * (0.5 + stop / self.sampling_rate)
else:
# get the corresponding indices
start *= self.sampling_rate
stop *= self.sampling_rate
# check if boundaries make sense, raise exception otherwise
if numpy.abs( int( start ) - int( stop ) ) <= 0:
raise ValueError( "start stop too close: There are no samples in the given boundaries!" )
# clip the data for each channel
for ch in range( len( self.y ) ):
self.y[ ch ] = self.y[ ch ][ int( start ):int( stop ) ]
self.x = self.x[ int( start ):int( stop ) ]
return self
def baseline( self, last_part=0.1 ):
"""
Correct the baseline of your data by subtracting the mean of the
last_part fraction of your data.
:param float last_part: last section of your timesignal used to calculate baseline
last_part defaults to 0.1, i.e. last 10% of your data
"""
# TODO baseline correction for spectra after:
# Heuer, A; Haeberlen, U.: J. Mag. Res.(1989) 85, Is 1, 79-94
n = int( self.x.size * last_part )
for ch in range( len( self.y ) ):
self.y[ ch ] -= self.y[ ch ][ -n: ].mean( )
return self
def exp_window( self, line_broadening=10 ):
"""
Exponential window function
:param float line_broadening: default 10, line broadening factor in Hz
.. math::
\\exp\\left(-\\pi\\cdot \\textsf{line_broadening} \\cdot t\\right)
"""
apod = numpy.exp( -self.x * numpy.pi * line_broadening )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def gauss_window( self, line_broadening=10 ):
"""
Gaussian window function
:param float line_broadening: default 10, line broadening factor in Hz
.. math:: \\exp\\left(- (\\textsf{line_broadening} \\cdot t)^2\\right)
"""
apod = numpy.exp( -(self.x * line_broadening) ** 2 )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def dexp_window( self, line_broadening=-10, gaussian_multiplicator=0.3 ):
apod = numpy.exp( -(self.x * line_broadening - gaussian_multiplicator * self.x.max( )) ** 2 )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def traf_window( self, line_broadening=10 ):
apod = (numpy.exp( -self.x * line_broadening )) ** 2 / ( (numpy.exp( -self.x * line_broadening )) ** 3
+ (
numpy.exp( -self.x.max( ) * line_broadening )) ** 3 )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def hanning_window( self ):
"""
Symmetric centered window (hanning)
"""
apod = numpy.hanning( self.x.size )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def hamming_window( self ):
"""
Symmetric centered window (hamming)
"""
apod = numpy.hamming( self.x.size )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def blackman_window( self ):
"""
Symmetric centered window (blackmann)
"""
apod = numpy.blackman( self.x.size )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def bartlett_window( self ):
"""
Symmetric centered window (bartlett)
"""
apod = numpy.bartlett( self.x.size )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def kaiser_window( self, beta=4, use_scipy=None ):
"""
Symmetric centered window (kaiser)
"""
apod = numpy.kaiser( self.x.size, beta )
for i in range( 2 ):
self.y[ i ] = self.y[ i ] * apod
return self
def autophase( self ):
"""
Automatically phases the data to maximize real part.
Works nice with a SNR above 20 dB, i.e.
10 V signal to 0.1 V noise amplitude.
"""
autophase.get_phase( self )
return self
def fft( self, samples=None ):
"""
Calculate the Fourier transform of the data inplace.
For zero filling set **samples** to a value higher than your data length,
smaller values will truncate your data.
:param int samples: default=None, if given, number of samples returned
"""
fft_of_signal = numpy.fft.fft( self.y[ 0 ] + 1j * self.y[ 1 ], n=samples )
fft_of_signal = numpy.fft.fftshift( fft_of_signal )
dwell = 1.0 / self.sampling_rate
n = fft_of_signal.size
fft_frequencies = numpy.fft.fftfreq( n, dwell )
self.x = numpy.fft.fftshift( fft_frequencies )
self.y[ 0 ] = fft_of_signal.real
self.y[ 1 ] = fft_of_signal.imag
self.set_xlabel( "Frequency / Hz" )
return self
def magnitude( self ):
"""
Return absolute signal, i.e.:
.. math::
y[0] &= \\sqrt{y[0]^2 + y[1]^2} \\\\
y[1] &= 0
"""
# this should calculate the absolute value, and set the imag channel to zero
self.y[ 0 ] = numpy.sqrt( self.y[ 0 ] ** 2 + self.y[ 1 ] ** 2 )
self.y[ 1 ] *= 0 # self.y[0].copy()
return self
def ppm(self, f_ref):
"""
Return result scaled to PPM compared to f_ref
:param f_ref: larmor frequency in MHz
:return:
"""
self.x /= f_ref
self.set_xlabel( "PPM" )
return self
+188
View File
@@ -0,0 +1,188 @@
# data pool collects data from data handling script
# provides data to experiment script and display
import sys
import tables
import collections
import threading
import traceback
import io
class DataPool(collections.abc.MutableMapping):
"""
dictionary with sending change events
"""
# supports tranlation from dictionary keys to pytables hdf node names
# taken from: Python Ref Manual Section 2.3: Identifiers and keywords
# things are always prefixed by "dir_" or "dict_"
translation_table=""
for i in range(256):
c=chr(i)
if (c>="a" and c<="z") or \
(c>="A" and c<="Z") or \
(c>="0" and c<="9"):
translation_table+=c
else:
translation_table+="_"
class Event:
access=0
updated_value=1
new_key=2
deleted_key=3
destroy=4
def __init__(self, what, subject="", origin=None):
self.what=what
self.subject=subject
self.origin=origin
def __repr__(self):
return "<DataPool.Event origin=%s what=%d subject='%s'>"%(self.origin, self.what,self.subject)
def copy(self):
return DataPool.Event(self.what+0, self.subject+"", self.origin)
def __init__(self):
self.__mydict={}
self.__dictlock=threading.Lock()
self.__registered_listeners=[]
def __getitem__(self, name):
try:
self.__dictlock.acquire()
return self.__mydict[name]
finally:
self.__dictlock.release()
def __setitem__(self, name, value):
try:
self.__dictlock.acquire()
if name in self.__mydict:
e=DataPool.Event(DataPool.Event.updated_value,name,self)
else:
e=DataPool.Event(DataPool.Event.new_key, name,self)
self.__mydict[name]=value
finally:
self.__dictlock.release()
self.__send_event(e)
def __delitem__(self, name):
try:
self.__dictlock.acquire()
del self.__mydict[name]
finally:
self.__dictlock.release()
self.__send_event(DataPool.Event(DataPool.Event.deleted_key,name,self))
def __iter__(self):
try:
self.__dictlock.acquire()
return iter(self.__mydict)
finally:
self.__dictlock.release()
def __len__(self):
try:
self.__dictlock.acquire()
return len(self.__mydict)
finally:
self.__dictlock.release()
def keys(self):
try:
self.__dictlock.acquire()
return list(self.__mydict.keys())
finally:
self.__dictlock.release()
def __send_event(self, _event):
for listeners in self.__registered_listeners:
listeners(_event.copy())
def __del__(self):
self.__send_event(DataPool.Event(DataPool.Event.destroy))
self.__registered_listeners=None
def write_hdf5(self,hdffile,where="/",name="data_pool", complib=None, complevel=None):
if type(hdffile) is bytes:
dump_file=tables.open_file(hdffile, mode="a")
elif isinstance(hdffile,tables.File):
dump_file=hdffile
else:
raise Exception("expecting hdffile or string")
dump_group=dump_file.create_group(where, name, "DAMARIS data pool")
self.__dictlock.acquire()
dict_keys=list(self.__mydict.keys())
self.__dictlock.release()
try:
for key in dict_keys:
if key.startswith("__"):
continue
dump_dir=dump_group
# walk along the given path and create groups if necessary
namelist = key.split("/")
for part in namelist[:-1]:
dir_part="dir_"+str(part).translate(DataPool.translation_table)
if dir_part not in dump_dir:
dump_dir=dump_file.create_group(dump_dir,name=dir_part,title=part)
else:
if dump_dir._v_children[dir_part]._v_title==part:
dump_dir=dump_dir._v_children[dir_part]
else:
extension_count=0
while dir_part+"_%03d"%extension_count in dump_dir:
extension_count+=1
dump_dir=dump_file.create_group(dump_dir,
name=dir_part+"_%03d"%extension_count,
title=part)
# convert last part of key to a valid name
group_keyname="dict_"+str(namelist[-1]).translate(DataPool.translation_table)
# avoid double names by adding number extension
if group_keyname in dump_dir:
extension_count=0
while group_keyname+"_%03d"%extension_count in dump_dir:
extension_count+=1
group_keyname+="_%03d"%extension_count
self.__dictlock.acquire()
if key not in self.__mydict:
# outdated ...
self.__dictlock.release()
continue
value=self.__mydict[key]
self.__dictlock.release()
# now write data, assuming, the object is constant during write operation
if "write_to_hdf" in dir(value):
try:
value.write_to_hdf(hdffile=dump_file,
where=dump_dir,
name=group_keyname,
title=key,
complib=complib,
complevel=complevel)
except Exception as e:
print("failed to write data_pool[\"%s\"]: %s"%(key,str(e)))
traceback_file=io.StringIO()
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
print("detailed traceback: %s\n"%str(e)+traceback_file.getvalue())
traceback_file=None
else:
print("don't know how to store data_pool[\"%s\"]"%key)
value=None
finally:
dump_group=None
if type(hdffile) is bytes:
dump_file.close()
dump_file=None
def register_listener(self, listening_function):
self.__registered_listeners.append(listening_function)
def unregister_listener(self, listening_function):
if listening_function in self.__registered_listeners:
self.__registered_listeners.remove(listening_function)
+188
View File
@@ -0,0 +1,188 @@
# -*- coding: iso-8859-1 -*-
#############################################################################
# #
# Name: Class Drawable #
# #
# Purpose: Base class of everything plottable #
# #
#############################################################################
class Drawable:
def __init__(self):
# Will be set correctly in one of the subclasses
self.x = []
self.y = []
self.styles = { }
self.xlabel = None
self.ylabel = None
self.title = None
self.legend = { }
self.text = {}
self.xmin = 0
self.xmax = 0
self.ymin = 0
self.ymax = 0
def get_xdata(self):
"Returns a reference to the x-Plotdata (array)"
return self.x
def set_xdata(self, pos, value):
"Sets a point in x"
try:
self.x[pos] = value
except:
raise
def get_ydata(self, channel):
"Returns the y-Plotdata of channel n (array)"
try:
return self.y[channel]
except:
raise
def set_ydata(self, channel, pos, value):
"Sets a point in y"
try:
self.y[channel][pos] = value
except:
raise
def get_number_of_channels(self):
"Returns the number of channels in y"
return len(self.y)
def get_style(self):
"Returns a reference to plot-styles (dictionary)"
return self.styles
def set_style(self, channel, value):
"Sets a channel to a certain plot-style"
if channel in self.styles:
print("Drawable Warning: Style key \"%s\" will be overwritten with \"%s\"" % (str(channel), str(value)))
self.styles[channel] = str(value)
def get_xlabel(self):
"Returns the label for the x-axis"
return self.xlabel
def set_xlabel(self, label):
"Sets the label for the x-axis"
self.xlabel = str(label)
def get_ylabel(self):
"Gets the label for the y-axis"
return self.ylabel
def set_ylabel(self, label):
"Sets the label for the y-axis"
self.ylabel = str(label)
def get_text(self, index):
"Returns labels to be plotted (List)"
if index in self.text:
return self.text[index]
else:
return None
def set_text(self, index, text):
"Sets labels to be plotted "
self.text[index] = str(text)
def get_title(self):
"Returns the title of the plot"
return self.title
def set_title(self, title):
"Sets the title of the plot"
self.title = str(title)
def get_legend(self):
"Returns the legend of the plot (Dictionary)"
return self.legend
def set_legend(self, channel, value):
"Sets the legend of the plot"
if channel in self.legend:
print("Drawable Warning: Legend key \"%s\" will be overwritten with \"%s\"" % (str(channel), str(value)))
self.legend[channel] = str(value)
def get_xmin(self):
"Returns minimun of x"
return self.x.min()
def get_xminpos(self):
"Returns smallest positive value of x"
mask = self.x > 0
return self.x[mask].min()
def set_xmin(self, xmin):
"Sets minimum of x"
self.xmin = xmin
def get_xmax(self):
"Returns maximum of x"
return self.x.max()
def set_xmax(self, xmax):
"Sets maximum of x"
self.xmax = xmax
def get_ymin(self):
"Returns minimum of y"
if isinstance(self.y, list):
return min([yarr.min() for yarr in self.y])
else:
return self.y.min()
def get_yminpos(self):
"Returns smallest positive value of y"
if isinstance(self.y, list):
ymins = []
for ys in self.y:
mask = ys > 0
ymins.append(ys[mask].min())
ymin = min(ymins)
else:
mask = self.y > 0
ymin = self.y[mask].min()
return ymin
def set_ymin(self, ymin):
"Sets minimum of y"
self.ymin = ymin
def get_ymax(self):
"Returns maximimum of y"
if isinstance(self.y, list):
return max([yarr.max() for yarr in self.y])
else:
return self.y.max()
def set_ymax(self, ymax):
"Sets maximum of y"
self.ymax = ymax
+75
View File
@@ -0,0 +1,75 @@
# -*- coding: iso-8859-1 -*-
from .Resultable import Resultable
from .Drawable import Drawable
#############################################################################
# #
# Name: Class Error_Result #
# #
# Purpose: Specialised class of Resultable #
# Contains occured error-messages from the core #
# #
#############################################################################
class Error_Result(Resultable, Drawable):
"""
Specialised class of Resultable
Contains error-messages from the core
"""
def __init__(self, error_msg = None, desc = {}, job_id = None, job_date = None):
Resultable.__init__(self)
Drawable.__init__(self)
if error_msg is not None:
self.error_message = error_msg
self.set_title("Error-Result: %s" % error_msg)
else:
self.error_message = error_msg
self.description = desc
self.job_id = job_id
self.job_date = job_date
def get_error_message(self):
return self.error_message
def set_error_message(self, error_msg):
self.set_title("Error-Result: %s" % error_msg)
self.error_message = error_msg
# No statistics
def uses_statistics(self):
return False
# Nothing to plot
def get_ydata(self):
return [0.0]
# Nothing to plot
def get_xdata(self):
return [0.0]
#overload of operators und built-ins -------------------------------------------------------
def __repr__(self):
tmp_string = "Core error-message: %s" % self.error_message
return tmp_string
def __len__(self):
return len(self.error_message)
def __str__(self):
return self.error_message
# Preventing an error when adding something to an error-result (needed for plotting error-results)
def __add__(self, other):
return self
+78
View File
@@ -0,0 +1,78 @@
# -*- coding: iso-8859-1 -*-
#############################################################################
# #
# Name: Class Errorable #
# #
# Purpose: Base class of everything what could contain a statistic error #
# #
#############################################################################
class Errorable:
"""
Base class for data objects that can have data with errors.
"""
def __init__(self):
# Will be determined in one of the subclasses
self.xerr = []
self.yerr = []
self.error_color = ""
self.bars_above = False
self.n = 0
def get_xerr(self):
"""Returns a reference to x-Error (array)"""
return self.xerr
def set_xerr(self, pos, value):
"""Sets a point in x-Error"""
try:
self.xerr[pos] = value
except:
raise
def get_yerr(self, channel):
"""Returns a list of y-Errors (list of arrays, corresponding channels)"""
try:
return self.yerr[channel]
except:
raise
def set_yerr(self, channel, pos, value):
"""Sets a point in y-Error"""
try:
self.yerr[channel][pos] = value
except:
raise
def get_error_color(self):
"""Returns the error-bar color"""
return self.error_color
def set_error_color(self, color):
"""Sets the error-bar color"""
self.error_color = color
def get_bars_above(self):
"""Gets bars-above property of errorplot"""
return self.bars_above
def set_bars_above(self, bars_above):
"""Sets bars-above property of errorplot"""
self.bars_above = bool(bars_above)
def ready_for_drawing_error(self):
"""Returns true if more than one result have been accumulated"""
return self.n >= 2
+287
View File
@@ -0,0 +1,287 @@
import threading
import math
import types
import sys
import tables
import numpy
import collections
from . import Drawable
## provide gaussian statistics for a series of measured data points
#
# AccumulatedValue provides mean and error of mean after being fed with measured data
# internally it keeps the sum, the sum of squares and the number of data points
class AccumulatedValue:
def __init__(self, mean=None, mean_err=None, n=None):
"""
one value with std. deviation
can be initialized by:
No argument: no entries
one argument: first entry
two arguments: mean and its error, n is set 2
three arguments: already existing statistics defined by mean, mean's error, n
"""
if mean is None:
self.y=0.0
self.y2=0.0
self.n=0
elif mean_err is None and n is None:
self.y=float(mean)
self.y2=self.y**2
self.n=1
elif mean_err is None:
self.n=max(1, int(n))
self.y=float(mean)*self.n
self.y2=(float(mean)**2)*self.n
elif n is None:
self.n=2
self.y=float(mean)*2
self.y2=(float(mean_err)**2+float(mean)**2)*2
else:
self.n=int(n)
self.y=float(mean)*self.n
self.y2=float(mean_err)**2*n*(n-1.0)+float(mean)**2*n
def __add__(self,y):
new_one=AccumulatedValue()
if (type(y) is types.InstanceType and isinstance(y, AccumulatedValue)):
new_one.y=self.y+y.y
new_one.y2=self.y2+y.y2
new_one.n=self.n+y.n
else:
new_one.y=self.y+float(y)
new_one.y2=self.y2+float(y)**2
new_one.n=self.n+1
return new_one
def __iadd__(self,y):
if (type(y) is types.InstanceType and isinstance(y, AccumulatedValue)):
self.y+=y.y
self.y2+=y.y2
self.n+=y.n
else:
self.y+=float(y)
self.y2+=float(y)**2
self.n+=1
return self
def copy(self):
a=AccumulatedValue()
a.y=self.y
a.y2=self.y2
a.n=self.n
return a
def mean(self):
"""
returns the mean of all added/accumulated values
"""
if self.n is None or self.n==0:
return None
else:
return self.y/self.n
def sigma(self):
"""
returns the standard deviation added/accumulated values
"""
if self.n>1:
variance=(self.y2-(self.y**2)/float(self.n))/(self.n-1.0)
if variance<0:
if variance<-1e-20:
print("variance=%g<0! assuming 0"%variance)
return 0.0
return math.sqrt(variance)
elif self.n==1:
return 0.0
else:
return None
def mean_error(self):
"""
returns the mean's error (=std.dev/sqrt(n)) of all added/accumulated values
"""
if self.n>1:
variance=(self.y2-(self.y**2)/float(self.n))/(self.n-1.0)
if variance<0:
if variance<-1e-20:
print("variance=%g<0! assuming 0"%variance)
return 0.0
return math.sqrt(variance/self.n)
elif self.n==1:
return 0.0
else:
return None
def __str__(self):
if self.n==0:
return "no value"
elif self.n==1:
return str(self.y)
else:
return "%g +/- %g (%d accumulations)"%(self.mean(),self.mean_error(),self.n)
def __repr__(self):
return str(self)
class MeasurementResult(Drawable.Drawable, collections.UserDict):
def __init__(self, quantity_name):
"""
convenient accumulation and interface to plot functions
The dictionary must not contain anything but AccumulatedValue instances
"""
Drawable.Drawable.__init__(self)
collections.UserDict.__init__(self)
self.quantity_name=quantity_name
self.lock = threading.RLock()
# get the selected item, if it does not exist, create an empty one
def __getitem__(self, key):
if key not in self:
a=AccumulatedValue()
self.data[float(key)]=a
return a
else:
return self.data[float(key)]
def __setitem__(self,key,value):
if not isinstance(value, AccumulatedValue):
value=AccumulatedValue(float(value))
return collections.UserDict.__setitem__(self,
float(key),
value)
def __add__(self, right_value):
if right_value==0:
return self.copy()
else:
raise Exception("not implemented")
def get_title(self):
return self.quantity_name
def get_xdata(self):
"""
sorted array of all dictionary entries without Accumulated Value objects with n==0
"""
keys=numpy.array([k for k in list(self.data.keys()) if not (isinstance(self.data[k], AccumulatedValue) and self.data[k].n==0)],
dtype="float64")
keys.sort()
return keys
def get_ydata(self):
return self.get_xydata()[1]
def get_xydata(self):
k=self.get_xdata()
v=numpy.array([self.data[key].mean() for key in k], dtype="float64")
return [k,v]
def get_errorplotdata(self):
k=self.get_xdata()
v=numpy.array([self.data[key].mean() for key in k], dtype="float64")
e=numpy.array([self.data[key].mean_error() for key in k], dtype="float64")
return [k,v,e]
def get_lineplotdata(self):
k=self.get_xdata()
v=numpy.array(self.y, dtype="float64")
return [k, v]
def uses_statistics(self):
"""
drawable interface method, returns True
"""
return True
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("# quantity:"+str(self.quantity_name)+"\n")
the_destination.write("# x y ysigma n\n")
for x in self.get_xdata():
y=self.data[x]
if type(y) in [float, int, int]:
the_destination.write("%e%s%e%s0%s1\n"%(x, delimiter, y, delimiter, delimiter))
else:
the_destination.write("%e%s%e%s%e%s%d\n"%(x,
delimiter,
y.mean(),
delimiter,
y.mean_error(),
delimiter,
y.n))
the_destination=None
def write_to_hdf(self, hdffile, where, name, title, complib=None, complevel=None):
h5_table_format= {
"x" : tables.Float32Col(),
"y" : tables.Float32Col(),
"y_err" : tables.Float32Col(),
"n" : tables.Int64Col()
}
filter=None
if complib is not None:
if complevel is None:
complevel=9
filter=tables.Filters(complevel=complevel,complib=complib,shuffle=1)
mr_table=hdffile.create_table(where=where,name=name,
description=h5_table_format,
title=title,
filters=filter,
expectedrows=len(self))
mr_table.flavor="numpy"
mr_table.attrs.damaris_type="MeasurementResult"
self.lock.acquire()
try:
mr_table.attrs.quantity_name=self.quantity_name
row=mr_table.row
xdata=self.get_xdata()
if xdata.shape[0]!=0:
for x in self.get_xdata():
y=self.data[x]
row["x"]=x
if type(y) in [float, int, int]:
row["y"]=y
row["y_err"]=0.0
row["n"]=1
else:
row["y"]=y.mean()
row["y_err"]=y.mean_error()
row["n"]=y.n
row.append()
finally:
mr_table.flush()
self.lock.release()
def read_from_hdf(hdf_node):
"""
reads a MeasurementResult object from the hdf_node
or None if the node is not suitable
"""
if not isinstance(hdf_node, tables.Table):
return None
if hdf_node._v_attrs.damaris_type!="MeasurementResult":
return None
mr=MeasurementResult(hdf_node._v_attrs.quantity_name)
for r in hdf_node.iterrows():
mr[r["x"]]=AccumulatedValue(r["y"],r["y_err"],r["n"])
return mr
+28
View File
@@ -0,0 +1,28 @@
class Persistance :
def __init__(self, shots):
self.shots = shots
self.accu = 0
self.counter = 0
self.result_list = []
def fade(self, res):
self.counter += 1
if self.accu == 0:
self.accu=res+0
self.result_list.append(res)
if self.counter < 1:
for i,ch in enumerate(self.accu.y):
ch += res.y[i]
elif len(self.result_list) == self.shots:
self.counter = len(self.result_list)
old_result = self.result_list.pop(0)
for i,ch in enumerate(self.accu.y):
ch *= self.shots
ch -= old_result.y[i]
ch += res.y[i]
else:
for i,ch in enumerate(self.accu.y):
ch *= self.counter-1
ch += res.y[i]
self.accu /= self.counter
return self.accu
+65
View File
@@ -0,0 +1,65 @@
# -*- coding: iso-8859-1 -*-
#############################################################################
# #
# Name: Class Resultable #
# #
# Purpose: Base class of everything what could be a core-result #
# #
#############################################################################
class Resultable:
def __init__(self):
self.job_id = None
self.job_date = None
self.description = { }
def get_job_id(self):
"Returns the job-id of this result"
return self.job_id
def set_job_id(self, _id):
"Sets the job-id of this result"
self.job_id = _id
def get_job_date(self):
"Gets the date of this result"
return self.job_date
def set_job_date(self, date):
"Sets the date of this result"
self.job_date = date
def get_description_dictionary(self):
"Returns a reference to the description (Dictionary)"
return self.description
def set_description_dictionary(self, dictionary):
"Sets the entire description"
self.description = dictionary
def get_description(self, key):
"Returns the description value for a given key"
if key in self.description:
return self.description[key]
else:
print("Warning Resultable: No value for key \"%s\". Returned None" % str(key))
return None
def set_description(self, key, value):
"Adds a attribute to the description"
if key in self.description:
print("Warning: Result key \"%s\" will be overwritten with \"%s\"." % (str(key), str(value)))
self.description[key] = value
+37
View File
@@ -0,0 +1,37 @@
import numpy as N
class Signalpath:
def phase(self, degrees):
"""
rotate signal by **degrees** for phase cycling, etc.
:param degrees: rotate signal by this value
:return:
"""
if self.get_number_of_channels() != 2:
raise Exception("rotation defined only for 2 channels")
# simple case 0, 90, 180, 270 degree
reduced_angle = divmod(degrees, 90)
if abs(reduced_angle[1]) < 1e-6:
reduced_angle = reduced_angle[0] % 4
if reduced_angle == 0:
return
elif reduced_angle == 1:
self.y[1] *= -1
self.y = [self.y[1], self.y[0]]
elif reduced_angle == 2:
self.y[0] *= -1
self.y[1] *= -1
elif reduced_angle == 3:
self.y[0] *= -1
self.y = [self.y[1], self.y[0]]
else:
sin_angle = N.sin(degrees / 180.0 * N.pi)
cos_angle = N.cos(degrees / 180.0 * N.pi)
self.y = [cos_angle * self.y[0] - sin_angle * self.y[1],
sin_angle * self.y[0] + cos_angle * self.y[1]]
return self
+31
View File
@@ -0,0 +1,31 @@
# -*- coding: iso-8859-1 -*-
from .Resultable import Resultable
from .Drawable import Drawable
#############################################################################
# #
# Name: Class Temp_Result #
# #
# Purpose: Specialised class of Resultable and Drawable #
# Contains recorded temperature data #
# #
#############################################################################
class TemperatureResult(Resultable, Drawable):
"""
Specialised class of Resultable and Drawable
Contains recorded temperature data
"""
def __init__(self, x = None, y = None, desc = None, job_id = None, job_date = None):
Resultable.__init__(self)
Drawable.__init__(self)
if (x is None) and (y is None) and (desc is None) and (job_id is None) and (job_date is None):
pass
elif (x is not None) and (y is not None) and (desc is not None) and (job_id is not None) and (job_date is not None):
pass
else:
raise ValueError("Wrong usage of __init__!")
+10
View File
@@ -0,0 +1,10 @@
from damaris.data.ADC_Result import ADC_Result
from damaris.data.Accumulation import Accumulation
from damaris.data.MeasurementResult import MeasurementResult, AccumulatedValue
from damaris.data.DataPool import DataPool
from damaris.data.Error_Result import Error_Result
from damaris.data.Config_Result import Config_Result
from damaris.data.Temperature import TemperatureResult
__all__=["ADC_Result", "Accumulation", "MeasurementResult", "AccumulatedValue", "DataPool", "FFT", "Error_Result", "Config_Result", "TemperatureResult" ]
+63
View File
@@ -0,0 +1,63 @@
from scipy.optimize import fmin_powell
import numpy as N
def calculate_entropy(phi, real, imag, gamma, dwell):
"""
Calculates the entropy of the spectrum (real part).
p = phase
gamma should be adjusted such that the penalty and entropy are in the same magnitude
"""
# This is first order phasecorrection
# corr_phase = phi[0]+phi[1]*arange(0,len(signal),1.0)/len(signal) # For 0th and 1st correction
# Zero order phase correction
real_part = real*N.cos(phi)-imag*N.sin(phi)
# Either this for calculating derivatives:
# Zwei-Punkt-Formel
# real_diff = (Re[1:]-Re[:-1])/dwell
# Better this:
# Drei-Punkte-Mittelpunkt-Formel (Ränder werden nicht beachtet)
# real_diff = abs((Re[2:]-Re[:-2])/(dwell*2))
# Even better:
# Fünf-Punkte-Mittelpunkt-Formel (ohne Ränder)
real_diff = N.abs((real_part[:-4]-8*real_part[1:-3]
+8*real_part[3:-1]-2*real_part[4:])/(12*dwell))
# TODO Ränder, sind wahrscheinlich nicht kritisch
# Calculate the entropy
h = real_diff/real_diff.sum()
# Set all h with 0 to 1 (log would complain)
h[h==0]=1
entropy = N.sum(-h*N.log(h))
# My version, according the paper
#penalty = gamma*sum([val**2 for val in Re if val < 0])
# calculate penalty value: a real spectrum should have positive values
if real_part.sum() < 0:
tmp = real_part[real_part<0]
penalty = N.dot(tmp,tmp)
if gamma == 0:
gamma = entropy/penalty
penalty = N.dot(tmp,tmp)*gamma
else:
penalty = 0
#print "Entropy:",entrop,"Penalty:",penalty # Debugging
shannon = entropy+penalty
return shannon
def get_phase(result_object):
global gamma
gamma=0
real = result_object.y[0].copy()
imag = result_object.y[1].copy()
dwell = 1.0/result_object.sampling_rate
# fmin also possible
xopt = fmin_powell( func=calculate_entropy,
x0=N.array([0.0]),
args=(real, imag, gamma, dwell),
disp=0)
result_object.y[0] = real*N.cos(xopt) - imag*N.sin(xopt)
result_object.y[1] = real*N.sin(xopt) + imag*N.cos(xopt)
return result_object
+768
View File
@@ -0,0 +1,768 @@
# -*- coding: utf-8 -*-
import types
import numpy
class StateBase(object):
def __init__(self):
pass
def to_xml(self, indent = ""):
return indent + "<!-- " + repr(self) + " -->"
class StateSimple(StateBase):
def __init__(self, time, content=None):
super(StateSimple, self).__init__()
if time < 0:
raise AssertionError("time for state is negative!")
self.time = time
self.content = content
def to_xml(self, indent = ""):
s = indent + '<state time="%s"' % repr(self.time)
if self.content is None:
return s + '/>\n'
s += '>\n'
s += indent + ' ' + str(self.content) + '\n'
s += indent + '</state>\n'
return s
def __repr__(self):
return 'StateSimple(%s, %s)' % (self.time, repr(self.content))
class StateList(StateBase):
def __init__(self):
super(StateList, self).__init__()
self.list = []
def to_xml(self, indent = " "):
s = ""
for k in self.list:
if hasattr(k, "to_xml"):
s += k.to_xml(indent)
else:
s += indent + str(k)
return s
def append(self, val):
self.list.append(val)
class StateLoop(StateList):
"""Represents a loop in the state tree"""
def __init__(self, repeat):
super(StateLoop, self).__init__()
self.repeat = repeat
def to_xml(self, indent = ""):
s = indent + ('<sequent repeat="%d">\n' % self.repeat)
s += super(StateLoop, self).to_xml(indent + " ")
s += indent + '</sequent>\n'
return s
def __repr__(self):
return 'StateLoop(repeat=%d, %s)' \
% (self.repeat, repr(self.list))
#############################################################
# #
# Class: Experiment #
# #
# Purpose: Represents one full experiment (one program on #
# the pulse-card; one file) #
# #
#############################################################
from . import dac
class Experiment:
"""
Class holding the complete state tree for a single experiment. This state tree represents one
program on the pulse card. It is written in a single file and picked up by the backend.
:param Gating gating: gate length in s
:param list rf_sources: list of rf sources ttl channels
:param list rf_gates: list of rf gates ttl channels len(rf_gates) == len(rf_sources)
"""
job_id = 0
def __init__(self, gating=None, rf_sources=[], rf_gates=[]):
self.job_id = Experiment.job_id
Experiment.job_id += 1
self.state_list = StateList()
self.list_stack = []
self.description = { }
self.gating = gating
assert type(rf_sources) == type(list()), "rf_sources needs to be a list with the channels"
assert type(rf_gates) == type(list()), "rf_gates needs to be a list with the channels"
assert len(rf_gates) == len(rf_sources), "rf_sources and rf_gates must have equal number of entries"
self.rf_sources = rf_sources
self.rf_gates = rf_gates
#for tracking the experiment length:
#because loops are possible we need to track the length for each loop level
self.total_time=[]
self.total_time.append(0.0)
#and we need to know the number of iterations of the loops to multiply the state.
self.loop_iterations=[]
self.loop_iterations.append(1)
# Commands -------------------------------------------------------------------------------------
def ttl_pulse(self, length, channel = None, value = None):
"""
Creates a state with length **length** and switches requested bits of the pulse programmer to HIGH.
This command generates a TTL pulse with the specified duration on a specific channel or multiple channels.
Parameters:
-----------
length : float
Pulse length in seconds.
channel : int, optional
Selects a single channel (No. 1 - 24). If provided, the value is calculated as 2^channel.
value : int, optional
Lines to set (integer). For example, value=3 selects channels 0 and 1 (2**0 + 2**1).
If both channel and value are None, the pulse is set to 0.
Examples:
---------
>>> e.ttl_pulse(length=5e-6, channel=1)
Creates a 5 microsecond pulse on channel 1.
>>> e.ttl_pulse(length=3e-6, value=3)
Creates a 3 microsecond pulse on channels 0 and 1.
>>> e.ttl_pulse(length=1e-6, value=0xffffff)
Creates a 1 microsecond pulse on all channels.
Notes:
------
Hexadecimal representation (number starts with *0x*) is convenient as the numbers are shorter.
To set all channels, value would be in decimal 16777215 and in hexadecimal 0xffffff.
One letter in hexadecimal represents four bits.
See Also:
---------
ttls : Same as ttl_pulse, but no channel keyword.
"""
the_value=0
if value is not None:
the_value=int(value)
elif channel is not None:
the_value=1<<channel
self.state_list.append(StateSimple(length, \
'<ttlout value="0x%06x"/>' % the_value))
self.total_time[-1] += length
## Same as ttl_pulse, but no *channel* keyword
def ttls(self, length = None, value = None):
"""
Same as ttl_pulse, but no *channel* keyword
:param float length:
:param int value: lines to set (integer)
:return:
"""
the_value=int(value)
s_content = '<ttlout value="0x%06x"/>' % the_value
if length is not None:
self.state_list.append(StateSimple(length, s_content))
self.total_time[-1] += length
else:
self.state_list.append(s_content)
def rf_pulse(self, length=None, phase=0, source=0):
"""
Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured.
:param float length: pulse length
:param float phase: pulse phase
:param int source: source id
"""
if not self.gating:
raise SyntaxError("Can not use rf_pulse without configuration: Experiment(gating=None, rf_sources=[], rf_gates=[]")
self.set_phase(phase, ttls=self.rf_gates[source])
self.ttls(length=self.gating-0.5e-6,value=self.rf_gates[source])
self.ttls(length=length, value=self.rf_gates[source]+self.rf_sources[source])
## Beginning of a new state
def state_start(self, time):
"""
Starts a state in the pulse programs with duration *time*.
This must be closed with :func:`state_end`.
Typically only used in very special cases.
"""
self.state_list.append('<state time="%s">\n' % repr(time))
self.total_time[-1] += time
## End of *state_start*
def state_end(self):
"""
Closes a previous :func:`state_start`
"""
self.state_list.append('</state>\n')
def wait(self, time, ttls=None, gating=False):
"""
Waits for a specified amount of time without performing any actions.
This command inserts a delay in the pulse sequence, allowing time for relaxation,
or synchronization with other events.
Parameters:
-----------
time : float
Time to wait in seconds.
The minimum time is 90 ns, and the maximum is essentially unlimited (years).
The backend driver circumvents the limit imposed by the pulse programmer by adding loops.
ttls : int, optional
Additional TTL lines to set (integer) during the wait period.
Allows simultaneous control of TTL lines while waiting.
For example, ttls=3 activates channels 0 and 1 (2^0 + 2^1).
gating : bool, optional
If True, reduces the wait time by the gating time (typically 2 µs).
This is useful for waiting in front of an rf_pulse to account for gating delays.
Default is False.
Returns:
--------
None
Examples:
---------
>>> e.wait(time=2e-3)
Waits for 2 milliseconds.
>>> e.wait(time=1e-6, ttls=3)
Waits for 1 microsecond while activating TTL channels 0 and 1.
>>> e.wait(time=5e-6, gating=True)
Waits for 5 microseconds, reduced by the gating time for rf_pulse synchronization.
"""
if gating:
time -= self.gating
if ttls is not None:
s_content = '<ttlout value="0x%06x"/>' % ttls
self.state_list.append(StateSimple(time,s_content))
else:
self.state_list.append(StateSimple(time))
self.total_time[-1] += time
def record(self, samples, frequency, timelength=None, sensitivity = None, ttls=None, channels = 3, offset = None, impedance = None):
"""
Records data with a given number of samples, sampling frequency, and sensitivity.
This command starts data acquisition from the ADC (Analog-to-Digital Converter).
Parameters:
-----------
samples : int
Number of samples to record. This determines the number of data points in the resulting signal.
frequency : float
Sampling frequency in Hz. This determines how often samples are taken from the analog signal.
The maximum sampling frequency is 20 MHz.
timelength : float, optional
Length of this state in seconds. If not specified (None), it is calculated automatically as samples/frequency.
This parameter allows overriding the automatic calculation for special timing requirements.
sensitivity : float or list, optional
Sensitivity in U_MAX/V. Specifies the input voltage range for the ADC.
Accepted values are 0.2, 0.5, 1, 2, 5, and 10 V.
Can be a single value (applied to all channels) or a list of values (one per channel).
ttls : int, optional
Additional TTL lines to set (integer) during recording. Allows simultaneous control of TTL lines.
For example, ttls=3 activates channels 0 and 1 (2^0 + 2^1).
channels : int, optional
Channels to activate. Default is 3 (channels 0 and 1).
Accepted values are 1 (channel 0), 3 (channels 0 and 1), 5 (channels 0, 1, and 2), and 15 (all 4 channels).
offset : int or list, optional
Voltage offset for the ADC input. Can be a single integer value (applied to all channels)
or a list of values (one per channel).
Normally not used.
impedance : float or list, optional
Input impedance for the ADC. Can be a single number (applied to all channels)
or a list of values (one per channel).
Normally set in the backend config and not changeable.
Returns:
--------
None
Examples:
---------
>>> e.record(samples=1024, frequency=2e6, sensitivity=2)
Records a signal with 1024 data points and 2 MHz sampling frequency.
The sensitivity will be ±2 V, providing a resolution of 0.2 mV with a 14-bit ADC card.
>>> e.record(samples=4096, frequency=10e6, sensitivity=[1, 2], channels=3)
Records a signal with 4096 data points and 10 MHz sampling frequency.
Channel 0 uses 1 V sensitivity, and channel 1 uses 2 V sensitivity.
>>> e.record(samples=2048, frequency=5e6, ttls=3)
Records a signal with 2048 data points and 5 MHz sampling frequency.
Simultaneously activates TTL channels 0 and 1.
Notes:
------
- Multiple record statements can be in a single scan (gated sampling) or in a loop.
- The onboard memory can hold 8M samples shared by all channels (depends on the board).
- The sensitivity setting affects the resolution of the ADC.
- The actual timing may vary slightly due to hardware limitationslike pre- and post-trigger delays.
"""
attributes='s="%d" f="%d"'%(samples,frequency)#%g
if channels != 1 and channels != 3 and channels != 5 and channels != 15:
raise ValueError("Channel definition is illegal")
attributes += ' channels="%i"'%(channels)
nchannels = 0
if channels == 1:
nchannels = 1
elif channels == 3 or channels == 5:
nchannels = 2
elif channels == 15:
nchannels = 4
if sensitivity is not None:
# float values are allowed and applied to all channels
if isinstance(sensitivity, float) or isinstance(sensitivity, int):
for i in range(nchannels):
attributes +=' sensitivity%i="%f"'%(i, float(sensitivity))
else:
for i in range(nchannels):
attributes +=' sensitivity%i="%f"'%(i, sensitivity[i])
if offset is not None:
# int values are allowed and applied to all channels
if isinstance(offset, int):
for i in range(nchannels):
attributes +=' offset%i="%f"'%(i, offset)
else:
for i in range(nchannels):
attributes +=' offset%i="%f"'%(i, offset[i])
if impedance is not None:
# float values are allowed and applied to all channels
if isinstance(impedance, float):
for i in range(nchannels):
attributes += ' impedance%i="%i"'%(i, impedance)
else:
for i in range(nchannels):
attributes += ' impedance%i="%i"'%(i, impedance[i])
s_content = '<analogin %s/>' % attributes
if ttls is not None:
s_content+='<ttlout value="0x%06x"/>' % ttls
if timelength is None:
timelength = samples / float(frequency)#*1.01
self.state_list.append(StateSimple(timelength, s_content))
self.total_time[-1] += timelength
## Create a loop on the pulse programmer. Loop contents can not change inside the loop.
# @params iterations Number of loop iterations
def loop_start(self, iterations):
"""
creates a loop of given number of iterations and has to be closed by loop_end().
Commands inside the loop can not change, i.e., the parameters are the same for each loop run.
This loop is created on the pulse programmer, thus saving commands.
Close the loop with :func:`loop_end`.
:param int iterations: number of iterations to loop
"""
l = StateLoop(iterations)
self.state_list.append(l)
# (These two lines could probably be guarded by a mutex)
self.list_stack.append(self.state_list)
self.state_list = l
self.total_time.append(0.0)
self.loop_iterations.append(iterations)
## End loop state
def loop_end(self):
"""
Closes a l:func:`loop_start` statement.
"""
# (This line could probably be guarded by a mutex)
self.state_list = self.list_stack.pop(-1)
looptime=self.total_time.pop(-1)
loopiterations=self.loop_iterations.pop(-1)
self.total_time[-1] += looptime*loopiterations
def set_frequency(self, frequency, phase, ttls=0):
"""
Sets the frequency and phase of the frequency source and optionally further channels.
The state length is 2 µs. Stabilisation time depends on RF source.
:param float frequency: frequency to set in Hz
:param float phase: phase to set
:param int ttls: default=0, additional ttl lines to set (integer)
"""
s_content = '<analogout id="0" f="%f" phase="%f"/>' % (frequency, phase)
if ttls != 0:
s_content += '<ttlout value="0x%06x"/>' % ttls
self.state_list.append(StateSimple(2e-6, s_content))
self.total_time[-1] += 2e-6
def set_pfg(self, dac_value=None, length=None, shape=('rec', 0), trigger=4, is_seq=False):
"""
This sets the value for the PFG, it also sets it back to dac_value=0 automatically.
If you don't wish to do so (i.e. line shapes) set is_seq=1
If you want to set a trigger, set trigger (default=4, i.e. channel 2)
If you want shaped gradients: shape=(ashape, resolution), ashape can be rec, sin2, sin
The default DAC ID is hardcoded (id=1)!
:param int dac_value: DAC value to set
:param float length: Duration of the state, minimum length is 42*90ns=3.78us (default)
:param tuple shape: Tuple of (shape, resolution/seconds), shape can be one of: rec (default), sin2, sin
:param bool is_seq: If set to *True*, do *NOT* set DAC to zero at the end of this state
:param int trigger: default=4, lines to set (integer)
"""
try:
form, resolution = shape
except:
raise SyntaxError("shape argument needs to be a tuple, i.e. ('shape',resolution), shape can be sin, sin2, rec")
if length == None:
# mimimum length
length=42*9e-8
if resolution >= length:
raise ValueError("Resolution %.3e of shaped gradients can not be longer than total length %.3e"%(resolution, length))
if resolution < 42*9e-8:
raise ValueError("Resulution %.3e can not be smaller than %.3e"%(resolution, 42*9e-8))
t_steps = numpy.arange(0,length,resolution)
if form == 'rec': # shape==None --> rectangular gradients
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac_value)
self.state_list.append(StateSimple(length, s_content))
self.total_time[-1] += length
if not is_seq:
s_content = '<analogout id="1" dac_value="0"/>'
self.state_list.append(StateSimple(42*9e-8, s_content))
self.total_time[-1] += 42*9e-8
elif form == 'sin2':
# sin**2 shape
for t in t_steps:
dac = int (dac_value*numpy.sin(numpy.pi/length*t)**2)
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac)
self.state_list.append(StateSimple(resolution, s_content))
# set it back to zero
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="0"/>' % (trigger)
self.state_list.append(StateSimple(resolution, s_content))
self.total_time[-1] += resolution*(len(t_steps)+1)
elif form == 'sin':
# sin shape
for t in t_steps:
dac = int (dac_value*numpy.sin(numpy.pi/length*t))
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="%i"/>' % (trigger, dac)
self.state_list.append(StateSimple(resolution, s_content))
# set it back to zero
s_content = '<ttlout value="%s"/><analogout id="1" dac_value="0"/>' % (trigger)
self.state_list.append(StateSimple(resolution, s_content))
self.total_time[-1] += resolution*(len(t_steps)+1)
else: # don't know what to do
raise SyntaxError("form is unknown: %s"%form)
def set_dac(self, dac_value, dac_id=1, length=None, is_seq=False, ttls=0):
"""
Sets the value for the DAC (Digital-to-Analog Converter) and optionally additional TTL lines.
This command is used to control analog output signals, such as pulsed field gradients.
Parameters:
-----------
dac_value : int
DAC value, between -2**19-1 (-524287) and +2**19 (524288).
This value determines the analog output voltage.
dac_id : int, optional
Specifies which DAC to control. Default is 1.
This allows control of multiple DAC channels if available.
length : float, optional
Length of this state in seconds. Default is None, which sets the length to 42*90ns=3.78µs.
If is_seq is False, the total length is doubled due to the automatic reset to zero.
is_seq : bool, optional
If True, the DAC value is not reset to zero after the pulse. Default is False.
Use is_seq=True for creating line shapes or sequential pulses.
ttls : int, optional
Lines to set (integer) for TTL output. Default is 0.
Allows simultaneous control of TTL lines along with the DAC.
Returns:
--------
None
Examples:
---------
>>> e.set_dac(dac_value=15040, dac_id=1, length=1e-3)
Sets DAC channel 1 to value 15040 for 1 millisecond and then resets to zero.
>>> e.set_dac(dac_value=10000, is_seq=True)
Sets DAC to value 10000 for 3.78µs without resetting to zero.
>>> e.set_dac(dac_value=20000, ttls=3)
Sets DAC to value 20000 and activates TTL channel 1 (2^1 + 2^0 = 3).
Notes:
------
- The minimum state length is 3.78 µs when is_seq=True.
- When is_seq=False, the DAC is automatically reset to zero, adding another 3.78 µs to the total length.
- The actual length may be longer than specified if additional time is required for DAC settling.
"""
if length==None:
length=42*9e-8
s_content = '<analogout id="%d" dac_value="%i"/><ttlout value="0x%06x"/>' \
% (dac_id, dac_value, ttls)
self.state_list.append(StateSimple(length, s_content))
self.total_time[-1] += length
if not is_seq:
s_content = '<analogout id="%d" dac_value="0"/><ttlout value="0x%06x"/>' \
% (dac_id, ttls)
self.state_list.append(StateSimple(42*9e-8, s_content))
self.total_time[-1] += 42*9e-8
def set_phase(self, phase, ttls=0):
"""
Sets the phase of the RF source to the specified value.
This command changes the phase of the frequency source. The phase is given in degrees and is
relative to the phase at the beginning of the experiment.
Parameters:
-----------
phase : float
Phase to set in degrees. This value determines the phase of the RF signal.
ttls : int, optional
Lines to set (integer) for TTL output. Default is 0.
Allows simultaneous control of TTL lines along with setting the phase.
Returns:
--------
None
Examples:
---------
>>> e.set_phase(phase=90)
Sets the receiver phase to 90 degrees.
>>> e.set_phase(phase=180, ttls=3)
Sets the phase to 180 degrees and activates TTL channels 0 and 1.
>>> e.set_phase(phase=45)
Sets the phase to 45 degrees.
Notes:
------
- The state length for this command is 0.5 µs.
- The stabilization time for the phase change is RF source dependent. A typical value for PTS310 is 2 µs.
- The phase is relative to the initial phase set at start of the experiment.
"""
s_content = '<analogout phase="%f" />' % (phase)
if ttls!=0:
s_content += '<ttlout value="%d"/>' % ttls
self.state_list.append(StateSimple(0.5e-6, s_content))
self.total_time[-1] += 0.5e-6
def set_description(self, key, value):
"""
Sets a description key-value pair in the experiment description dictionary.
This command creates an entry with the specified key and value in the description dictionary.
In case of data being stored in a HDF5 file, this dictionary is stored as well, allowing
parameter passing between the experiment script and the result script.
Parameters:
-----------
key : str
The key identifier for the description entry. This will be used to retrieve the value later.
value : any
The value to be associated with the key. Can be of any type (string, number, list, etc.).
Returns:
--------
None
Examples:
---------
>>> e.set_description(key="tau", value=2e-3)
Sets the description entry with key "tau" to the value 0.002 seconds.
>>> e.set_description(key="temperature", value=298.15)
Sets the description entry with key "temperature" to the value 298.15 Kelvin.
>>> e.set_description(key="pulse_sequence", value="CPMG")
Sets the description entry with key "pulse_sequence" to the string "CPMG".
Notes:
------
If the key already exists in the description dictionary, a warning message will be printed
indicating that the existing value will be overwritten.
"""
if key in list(self.description.keys()):
print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value))
self.description[key] = value
def set_pts_local(self):
"""
Sets the PTS310/PTS500 frequency source to local mode.
State length is 2 µs.
"""
self.state_list.append(StateSimple(1e-6, '<ttlout value="0xf000"/>'))
self.state_list.append(StateSimple(1e-6, '<ttlout value="0x8000"/>'))
self.total_time[-1] += 2e-6
# / Commands -----------------------------------------------------------------------------------
# Public Methods -------------------------------------------------------------------------------
def get_job_id(self) -> int:
"""
Returns the job-id of the current experiment
:returns: job_id
"""
return self.job_id
def write_xml_string(self):
"""
Returns the current program as a string
:returns: XML string
"""
# Standart XML-Kopf
xml_string = '<?xml version="1.0" encoding="ISO-8859-1"?>\n'
# Experiment-Start-Tag einfügen
xml_string += '<experiment no="%d">\n' % self.job_id
# Descriptions einfügen
if len(self.description)==0:
xml_string += ' <description/>\n'
else:
xml_string += ' <description>\n'
for key,value in self.description.items():
type_string="repr"
if value is None:
type_string="None"
value=""
elif type(value) is float or isinstance(value, numpy.floating):
type_string="Float"
value=repr(value)
elif type(value) is int or isinstance(value, numpy.integer):
type_string="Int"
value=repr(value)
elif type(value) is int:
type_string="Long"
value=repr(value)
elif type(value) is complex or isinstance(value, numpy.complexfloating):
type_string="Complex"
value=repr(value)
elif type(value) is bool or isinstance(value, numpy.bool_):
type_string="Boolean"
value=repr(value)
elif type(value) in (str,):
type_string="String"
else:
value=repr(value)
xml_string += ' <item key="%s" type="%s">%s</item>\n'%(key, type_string ,value)
xml_string += " </description>\n"
# Experiment-Inhalt einfügen
xml_string += self.state_list.to_xml(indent = " ")
# Experiment-End-Tag
xml_string += '</experiment>\n'
return xml_string
def write_quit_job(self):
"""
Returns a xml quit-job
:returns: XML quit-job
"""
return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit/>'
def get_length(self):
timelength = 0.0
#calculate the correct timelength also for unclosed loops, if there is no unclosed loop
#the timelength of this experiment is self.total_time[-1].
for i in range(len(self.total_time)):
timelength += self.total_time[i]
timelength *= self.loop_iterations[i]
return timelength
class Quit(Experiment):
def write_xml_string(self):
return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit no="%d"/>'%self.job_id
# /Public Methods ------------------------------------------------------------------------------
def self_test():
e = Experiment()
e.set_description("key", "value")
e.set_frequency(85e6, 90, ttls=16)
e.wait(1e-6)
#e.rf_pulse(1, 1e-6/3) # val = 1
e.ttl_pulse(1e-6/3, 1) # val = 2
e.ttl_pulse(1e-6/3, None, 7) # val = 7
if True:
e.loop_start(30)
e.set_pfg(dac_value=1024, length=5e-6, is_seq = True, shape=('rec', 42*9e-8))
e.loop_start(400)
e.set_phase(270, ttls = 32)
e.loop_end()
e.ttl_pulse(5e-6, channel = 6)
e.loop_end()
else:
l = StateLoop(3)
l.append(StateSimple(5e-6, '<ttlout value="1"/>'))
e.state_list.append(l)
e.set_dac(12345, dac_id=2, is_seq = True, ttls=16)
e.record(1024, 20e6)
try:
e.wait(-1)
except AssertionError:
pass
else:
raise AssertionError("An exception should happen")
e.set_pts_local()
print(e.write_xml_string())
print(e.get_length())
if __name__ == '__main__':
self_test()
+3
View File
@@ -0,0 +1,3 @@
from .Experiment import Experiment
from damaris.tools.ranges import *
#__all__=["Experiment"]
+12
View File
@@ -0,0 +1,12 @@
#import math
"""
This module holds everything connected with the DAC and PFG
"""
def conv(I_out=0):
"""
converts the demanded Output current in Integer
"""
V_dac=I_out/50.0
dac_value=-(V_dac-0.00983)/1.81413e-5
return int(dac_value)
+296
View File
@@ -0,0 +1,296 @@
import os
import os.path
import subprocess
import sys
import time
import re
import glob
from . import ExperimentWriter
from . import ResultReader
import threading
import types
import signal
if sys.platform=="win32":
import winreg
__doc__ = """
This class handles the backend driver
"""
class BackendDriver(threading.Thread):
def __init__(self, executable, spool, clear_jobs=False, clear_results=False):
threading.Thread.__init__(self, name="Backend Driver")
self.core_pid = None
self.core_input = None
self.core_output = None
self.statefilename = None
self.executable=str(executable)
self.spool_dir=spool
self.experiment_pattern="job.%09d"
self.result_pattern=self.experiment_pattern+".result"
if not os.path.isfile(self.executable):
raise AssertionError("could not find backend %s "%self.executable)
if not os.access(self.executable,os.X_OK):
raise AssertionError("insufficient rights for backend %s execution"%self.executable)
if not os.path.isdir(self.spool_dir):
try:
os.makedirs(os.path.abspath(self.spool_dir))
except OSError as e:
print(e)
raise AssertionError("could not create backend's spool directory %s "%self.spool_dir)
# remove stale state filenames
if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
old_state_files=glob.glob(os.path.join(self.spool_dir,"*.state"))
statelinepattern=re.compile("<state name=\"([^\"]+)\" pid=\"([^\"]+)\" starttime=\"([^\"]+)\">")
for statefilename in old_state_files:
statefile=open(statefilename,"r")
statelines=statefile.readlines()
statefile.close
del statefile
core_pid=None
for l in statelines:
matched=statelinepattern.match(l)
if matched:
core_pid=int(matched.group(2))
break
if core_pid is not None:
if os.path.isdir("/proc/%d"%core_pid):
raise AssertionError("found backend with pid %d (state file %s) in same spool dir"%(core_pid,statefilename))
else:
print("removing stale backend state file", statefilename)
os.remove(statefilename)
else:
print("todo: take care of existing backend state files")
self.result_reader = ResultReader.BlockingResultReader(self.spool_dir,
no=0,
result_pattern=self.result_pattern,
clear_jobs=clear_jobs,
clear_results=clear_results)
self.experiment_writer = ExperimentWriter.ExperimentWriterWithCleanup(self.spool_dir,
no=0,
job_pattern=self.experiment_pattern,
inform_last_job=self.result_reader)
self.quit_flag=threading.Event()
self.raised_exception=None
def run(self):
# take care of older logfiles
self.core_output_filename=os.path.join(self.spool_dir,"logdata")
if os.path.isfile(self.core_output_filename):
i=0
max_logs=100
while os.path.isfile(self.core_output_filename+".%02d"%i):
i+=1
while (i>=max_logs):
i-=1
os.remove(self.core_output_filename+".%02d"%i)
for j in range(i):
os.rename(self.core_output_filename+".%02d"%(i-j-1),self.core_output_filename+".%02d"%(i-j))
os.rename(self.core_output_filename, self.core_output_filename+".%02d"%0)
# create logfile
self.core_output=open(self.core_output_filename,"w")
# again look out for existing state files
state_files=glob.glob(os.path.join(self.spool_dir,"*.state"))
if state_files:
self.raised_exception="found other state file(s) in spool directory: "+",".join(state_files)
self.quit_flag.set()
return
# start backend
if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
self.core_input=subprocess.Popen([self.executable, "--spool", self.spool_dir],
stdout=self.core_output,
stderr=self.core_output)
if sys.platform=="win32":
cygwin_root_key=winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Cygnus Solutions\\Cygwin\\mounts v2\\/")
cygwin_path=winreg.QueryValueEx(cygwin_root_key,"native")[0]
os.environ["PATH"]+=";"+os.path.join(cygwin_path,"bin")+";"+os.path.join(cygwin_path,"lib")
self.core_input=subprocess.Popen("\"" + self.executable + "\"" + " --spool "+self.spool_dir,
stdout=self.core_output,
stderr=self.core_output)
# wait till state file shows up
timeout=10
# to do: how should I know core's state name????!!!!!
self.statefilename=None
state_files=glob.glob(os.path.join(self.spool_dir,"*.state"))
while len(state_files)==0:
if timeout<0 or self.core_input is None or self.core_input.poll() is not None or self.quit_flag.isSet():
# look into core log file and include contents
print(timeout, self.core_input, self.quit_flag.isSet())
if self.core_input is not None:
print(self.core_input.poll())
log_message=''
self.core_input=None
if os.path.isfile(self.core_output_filename):
# to do include log data
log_message='\n'+''.join(open(self.core_output_filename,"r", errors='replace').readlines()[:10])
if not log_message:
log_message=" no error message from core"
self.core_output.close()
self.raised_exception="no state file appeared or backend died away:"+log_message
print(self.raised_exception)
self.quit_flag.set()
return
time.sleep(0.05)
timeout-=0.05
state_files=glob.glob(os.path.join(self.spool_dir,"*.state"))
# save the one
if len(state_files)>1:
print("did find more than one state file, taking first one!")
self.statefilename=state_files[0]
# read state file
statefile=open(self.statefilename,"r")
statelines=statefile.readlines()
statefile=None
statelinepattern=re.compile("<state name=\"([^\"]+)\" pid=\"([^\"]+)\" starttime=\"([^\"]+)\">")
self.core_pid=-1
for l in statelines:
matched=statelinepattern.match(l)
if matched:
self.core_pid=int(matched.group(2))
break
# wait on flag and look after backend
while not self.quit_flag.isSet() and self.is_busy():
self.quit_flag.wait(0.1)
if self.quit_flag.isSet():
self.stop_queue()
while self.is_busy():
time.sleep(0.1)
if not self.is_busy():
if self.core_input is not None:
backend_result=self.core_input.poll()
wait_loop_counter=0
while backend_result is None:
# waiting in tenth of a second
time.sleep(0.1)
wait_loop_counter+=1
backend_result=self.core_input.poll()
if backend_result is not None: break
if wait_loop_counter==10:
print("sending termination signal to backend process")
self.send_signal("SIGTERM")
elif wait_loop_counter==20:
print("sending kill signal to backend process")
self.send_signal("SIGKILL")
elif wait_loop_counter>30:
print("no longer waiting for backend shutdown")
break
if backend_result is None:
print("backend dit not end properly, please stop it manually")
elif backend_result>0:
print("backend returned ", backend_result)
elif backend_result<0:
sig_name=[x for x in dir(signal) if x.startswith("SIG") and \
x[3]!="_" and \
(type(signal.__dict__[x])is int) and \
signal.__dict__[x]==-backend_result]
if sig_name:
print("backend was terminated by signal ",sig_name[0])
else:
print("backend was terminated by signal no",-backend_result)
self.core_input = None
self.core_pid = None
# the experiment handler should stop
if self.experiment_writer is not None:
# self.experiment_writer.
self.experiment_writer=None
# tell result reader, game is over...
#self.result_reader.stop_no=self.experiment_writer.no
if self.result_reader is not None:
self.result_reader.poll_time=-1
self.result_reader=None
def clear_job(self,no):
jobfilename=os.path.join(self.spool_dir,"job.%09d")
resultfilename=os.path.join(self.spool_dir,"job.%09d.result")
if os.path.isfile(jobfilename):
os.remove(jobfilename)
if os.path.isfile(resultfilename):
os.remove(resultfilename)
def get_messages(self):
# return pending messages
if self.core_output.tell()==os.path.getsize(self.core_output_filename):
return None
return self.core_output.read()
def restart_queue(self):
self.send_signal("SIGUSR1")
def stop_queue(self):
self.send_signal("SIGQUIT")
# assumes success
#self.core_pid=None
#self.core_input=None
def abort(self):
# abort execution
self.send_signal("SIGTERM")
# assumes success
#self.core_pid=None
#self.core_input=None
def send_signal(self, sig):
if self.core_pid is None:
print("BackendDriver.send_signal is called with core_pid=None")
return
try:
if sys.platform[:5]=="linux":
os.kill(self.core_pid,signal.__dict__[sig])
if sys.platform[:7]=="win32":
# reg_handle=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
cygwin_root_key=winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Cygnus Solutions\\Cygwin\\mounts v2\\/")
cygwin_path=winreg.QueryValueEx(cygwin_root_key,"native")[0]
kill_command=os.path.join(cygwin_path,"bin","kill.exe")
os.popen("%s -%s %d"%(kill_command,sig,self.core_pid))
except OSError as e:
print("could not send signal %s to core: %s"%(sig, str(e)))
def is_busy(self):
"Checks for state file"
return self.statefilename is not None and os.path.isfile(self.statefilename) and \
self.core_input is not None and self.core_input.poll() is None
#file_list = glob.glob(os.path.join(self.spool_dir, self.core_state_file))
#if len(file_list) != 0:
# return True
#else:
# return False
def get_exp_writer(self):
return self.experiment_writer
def get_res_reader(self):
return self.result_reader
def __del__(self):
# stop core and wait for it
if self.core_pid is not None:
try:
self.abort()
except OSError:
pass
self.core_input=None
if self.core_output:
self.core_output.close()
self.core_output=None
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
import threading
import io
import traceback
import sys
import time
from damaris.experiments.Experiment import Quit
from damaris.experiments import Experiment
class StopExperiment(Exception):
pass
class ExperimentHandling(threading.Thread):
"""
runs the experiment script in sandbox
"""
def __init__(self, script, exp_writer, data):
threading.Thread.__init__(self, name="experiment handler")
self.script=script
self.writer=exp_writer
self.data=data
self.quit_flag = threading.Event()
if self.data is not None:
self.data["__recentexperiment"]=-1
def synchronize(self, before=0, waitsteps=0.1):
while (self.data["__recentexperiment"]>self.data["__recentresult"]+before) and not self.quit_flag.isSet():
self.quit_flag.wait(waitsteps)
if self.quit_flag.isSet():
raise StopExperiment
def sleep(self, seconds):
self.quit_flag.wait(seconds)
if self.quit_flag.isSet():
raise StopExperiment
def run(self):
dataspace={}
exp_classes = __import__('damaris.experiments', dataspace, dataspace, ['Experiment'])
for name in dir(exp_classes):
if name[:2]=="__" and name[-2:]=="__": continue
dataspace[name]=exp_classes.__dict__[name]
del exp_classes
dataspace["data"]=self.data
dataspace["synchronize"]=self.synchronize
dataspace["sleep"]=self.sleep
self.raised_exception = None
self.location = None
exp_iterator=None
try:
exec(self.script, dataspace)
except Exception as e:
self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
traceback_file=io.StringIO()
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
self.traceback=traceback_file.getvalue()
traceback_file=None
return
if "experiment" in dataspace:
try:
exp_iterator=dataspace["experiment"]()
except Exception as e:
self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
traceback_file=io.StringIO()
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
self.traceback=traceback_file.getvalue()
traceback_file=None
return
while exp_iterator is not None and not self.quit_flag.isSet():
# get next experiment from script
try:
job=next(exp_iterator)
except StopExperiment as se:
break
except StopIteration as si:
break
except Exception as e:
self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
traceback_file=io.StringIO()
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
self.traceback=traceback_file.getvalue()
traceback_file=None
break
# send it
self.writer.send_next(job)
# write a note
if isinstance(job, Experiment):
if self.data is not None:
self.data["__recentexperiment"]=job.job_id+0
# track time when experiment is started. This is placed after executing the script
# and getting the iterator so that time for initializing devices etc. is not included
if "__experimentstarted" not in self.data:
self.data["__experimentstarted"] = time.time()
# track time of experiments in queue, the total time and for each experiment:
if "__totalexperimentlength" not in self.data:
self.data["__totalexperimentlength"] = 0.0
if "__experimentlengths" not in self.data:
self.data["__experimentlengths"] = {}
experimentlength = job.get_length()
self.data["__experimentlengths"][job.job_id+0] = experimentlength
self.data["__totalexperimentlength"] += experimentlength
# relax for a short time
if "__resultsinadvance" in self.data and self.data["__resultsinadvance"]+100<job.job_id:
self.quit_flag.wait(0.05)
if self.quit_flag.isSet():
dataspace=None
exp_iterator=None
break
self.writer.send_next(Quit(), quit=True)
# do not count quit job (is this a good idea?)
dataspace=None
self.exp_iterator=None
self.writer=None
+66
View File
@@ -0,0 +1,66 @@
import os
import os.path
import shutil
from damaris.experiments import Experiment
class ExperimentWriter:
"""
writes experiments in propper way to spool directory
"""
def __init__(self, spool, no=0, job_pattern="job.%09d", inform_last_job=None):
self.spool=spool
self.job_pattern=job_pattern
self.no=no
self.inform_last_job=inform_last_job
# test if spool exists
if not os.path.isdir(spool):
os.mkdir(spool)
def send_next(self, job, quit=False):
"""
"""
if quit and self.inform_last_job is not None:
self.inform_last_job.stop_no=self.no
self.inform_last_job=None
job.job_id=self.no
job_filename=os.path.join(self.spool,self.job_pattern%self.no)
f=open(job_filename+".tmp","w")
f.write(job.write_xml_string())
f.flush()
f.close() # explicit close under windows necessary (don't know why)
del f
# this implementation tries to satisfiy msvc filehandle caching
os.rename(job_filename+".tmp", job_filename)
#shutil.copyfile(job_filename+".tmp", job_filename)
#try:
# os.unlink(job_filename+".tmp")
#except OSError:
# print "could not delete temporary file %s.tmp"%job_filename
self.no+=1
def __del__(self):
if self.inform_last_job is not None:
self.inform_last_job.stop_no=self.no-1
self.inform_last_job=None
class ExperimentWriterWithCleanup(ExperimentWriter):
"""
writes experiments and cleans up in front of queue
"""
def __init__(self, spool, no=0, job_pattern="job.%09d", inform_last_job=None):
ExperimentWriter.__init__(self, spool, no, job_pattern, inform_last_job=inform_last_job)
self.delete_no_files(self.no)
def send_next(self, job, quit=False):
self.delete_no_files(self.no+1)
ExperimentWriter.send_next(self,job,quit)
def delete_no_files(self,no):
"""
delete everything with this job number
"""
filename=os.path.join(self.spool,(self.job_pattern%no))
if os.path.isfile(filename): os.unlink(filename)
if os.path.isfile(filename+".tmp"): os.unlink(filename+".tmp")
if os.path.isfile(filename+".result"): os.unlink(filename+".result")
+78
View File
@@ -0,0 +1,78 @@
import threading
import io
import sys
import os
import os.path
import traceback
from damaris.data import Resultable
class ResultHandling(threading.Thread):
"""
runs the result script in sandbox
"""
def __init__(self, script_data, result_iterator, data_pool):
threading.Thread.__init__(self,name="result handler")
self.script=script_data
self.results=result_iterator
self.data_space=data_pool
self.quit_flag=self.results.quit_flag
if self.data_space is not None:
self.data_space["__recentresult"]=-1
def run(self):
# execute it
dataspace={}
data_classes = __import__('damaris.data', dataspace, dataspace, ['*'])
for name in dir(data_classes):
if name[:2]=="__" and name[-2:]=="__": continue
dataspace[name]=data_classes.__dict__[name]
del data_classes
dataspace["results"]=self
dataspace["data"]=self.data_space
self.raised_exception=None
self.location = None
try:
exec(self.script, dataspace)
except Exception as e:
self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
traceback_file=io.StringIO()
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
self.traceback=traceback_file.getvalue()
traceback_file=None
return
if not "result" in dataspace:
dataspace=None
return
try:
dataspace["result"]()
except Exception as e:
self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
traceback_file=io.StringIO()
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
self.traceback=traceback_file.getvalue()
traceback_file=None
dataspace=None
def __iter__(self):
if self.quit_flag.isSet():
self.results=None
return
for i in self.results:
if hasattr(self.results, "in_advance"):
self.data_space["__resultsinadvance"]=self.results.in_advance
if self.quit_flag.isSet():
self.results=None
return
if isinstance(i, Resultable.Resultable):
if self.data_space is not None:
self.data_space["__recentresult"]=i.job_id+0
yield i
if self.quit_flag.isSet():
self.results=None
return
def stop(self):
self.quit_flag.set()
+578
View File
@@ -0,0 +1,578 @@
# -*- coding: iso-8859-1 -*-
#############################################################################
# #
# Name: Class ResultReader #
# #
#############################################################################
import os
import os.path
import glob
import time
import sys
import base64
import numpy
try:
import xml.etree.cElementTree
ELEMENT_TREE = True
except:
import xml.parsers.expat
ELEMENT_TREE = False
import threading
from datetime import datetime
from damaris.data import ADC_Result
from damaris.data import Error_Result
from damaris.data import TemperatureResult
from damaris.data import Config_Result
class ResultReader:
"""
starts at some point and returns result objects until none are there
"""
CONFIG_TYPE = 3
TEMP_TYPE = 2
ADCDATA_TYPE = 1
ERROR_TYPE = 0
def __init__(self, spool_dir=".", no=0, result_pattern="job.%09d.result", clear_jobs=False, clear_results=False):
self.spool_dir = spool_dir
self.start_no = no
self.no = self.start_no
self.result_pattern = result_pattern
self.clear_jobs=clear_jobs
self.clear_results=clear_results
self.quit_flag=threading.Event() # asychronous quit flag
def __iter__(self):
"""
get next job with iterator
"""
expected_filename=os.path.join(self.spool_dir,self.result_pattern%(self.no))
while os.access(expected_filename,os.R_OK):
yield self.get_result_object(expected_filename)
# purge result file
if self.clear_results:
if os.path.isfile(expected_filename):
os.remove(expected_filename)
if self.clear_jobs:
# job_name.result -> job_name
if os.path.isfile(expected_filename[:-7]):
os.remove(expected_filename[:-7])
self.no+=1
expected_filename=os.path.join(self.spool_dir, self.result_pattern % self.no)
return
def get_result_object(self, in_filename):
"""
get result object
"""
# class-intern result-object currently being processed
retries=0
result_file=None
while result_file is None:
try:
result_file = open(in_filename, "r")
except IOError as e:
if retries>10:
raise e
print(e, "retry", retries)
time.sleep(0.05)
retries+=1
# get date of last modification
stat_result = os.stat(in_filename)
mtime = stat_result.st_mtime
self.result_job_date = datetime.fromtimestamp(mtime)
if ELEMENT_TREE:
self.__parseFile = self.__parseFile_cETree
else:
self.__parseFile = self.__parseFile_expat
self.__parseFile (result_file)
result_file.close()
result_file = None
r=self.result
self.result = None
return r
def __parseFile_cETree(self, in_file):
self.result = None
self.in_description_section=False
self.result_description = { }
self.result_job_number = None
# Job Date is set in __read_file()
self.__filetype = None
for elem in xml.etree.cElementTree.ElementTree(file=in_file).iter():
if elem.tag == 'result':
self.result_job_number = int(elem.get("job"))
pass
elif elem.tag == 'description':
if elem.text!=None:
self.result_description = {}
self.in_description_section=True
self.in_description_data=()
for an_item in elem:
self.in_description_data = (an_item.get("key"), an_item.get("type"), an_item.text)
# make item contents to dictionary item:
k,t,v=self.in_description_data
self.in_description_data=()
if t == "None":
self.result_description[k]=None
if t == "Float":
self.result_description[k]=float(v)
elif t == "Int":
self.result_description[k]=int(v)
elif t == "Long":
self.result_description[k]=int(v)
elif t == "Complex":
self.result_description[k]=complex(v)
elif t == "Boolean":
self.result_description[k]=bool(v)
elif t == "String":
self.result_description[k]=v
else:
# Anything else will be handled as a string
# Probably "repr".
self.result_description[k]=v
elif elem.tag == 'adcdata':
self.__filetype = ResultReader.ADCDATA_TYPE
self.adc_result_trailing_chars = ""
if self.result is None:
self.result = ADC_Result()
# None: new guess for adc data encoding
# "a": ascii
# "b": base64
self.adc_data_encoding = None
self.result.set_sampling_rate(float(elem.get("rate")))
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
self.result.set_nChannels(int(elem.get("channels")))
self.result.set_description_dictionary(self.result_description.copy())
title = "ADC_Result: job-id=%d"%int(self.result_job_number)
if len(self.result_description) > 0:
for k,v in self.result_description.items():
title += ", %s=%s"%(k,v)
self.result.set_title(title)
self.result_description = None
self.adc_result_sample_counter = 0
self.adc_result_parts = [] # will contain arrays of sampled intervals, assumes same sample rate
else:
if float(elem.get("rate")) != self.result.get_sampling_rate():
print("sample rate different in ADC_Result, found %f, former value %f"%\
(float(elem.get("rate")),self.result.get_sampling_rate()))
new_samples = int(elem.get("samples"))
self.adc_result_sample_counter += new_samples
# extract adcdata (here base64 encoded)
self.adc_result_trailing_chars = "".join(elem.text.splitlines())
tmp_string = base64.standard_b64decode(self.adc_result_trailing_chars)
tmp = numpy.fromstring(tmp_string, dtype='int16')
self.adc_result_parts.append(tmp)
# we do not need this adcdata anymore, delete it
elem.clear()
elif elem.tag == 'error':
self.__filetype = ResultReader.ERROR_TYPE
self.result = Error_Result()
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
self.result.set_description_dictionary(self.result_description.copy())
self.result.set_error_message(elem.text)
elif elem.tag == 'temp':
self.__filetype = ResultReader.TEMP_TYPE
self.result = Error_Result()
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
elif elem.tag == 'conf':
self.__filetype = ResultReader.CONF_TYPE
self.result = Error_Result()
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
# xml file was traversed now prepare the data in one go
# prepare result data
if self.result is not None and \
self.__filetype == ResultReader.ADCDATA_TYPE and \
self.adc_result_sample_counter>0:
# fill the ADC_Result with collected data
# x data
self.result.x=numpy.arange(self.adc_result_sample_counter, dtype="float32")/\
self.result.get_sampling_rate()
self.result.y = []
nChannels = self.result.get_nChannels()
# initialise the y arrays
for i in range(nChannels):
self.result.y.append(numpy.empty(self.adc_result_sample_counter, dtype='int16'))
# remove from result stack
tmp_index = 0
while self.adc_result_parts:
tmp_part=self.adc_result_parts.pop(0)
tmp_size = int(tmp_part.size/nChannels)
for i in range(nChannels):
# split interleaved data
self.result.y[i][tmp_index:tmp_index+tmp_size] = tmp_part[i::nChannels]
self.result.y[i][tmp_index:tmp_index+tmp_size] = tmp_part[i::nChannels]
if self.result.index != []:
self.result.index.append((tmp_index, tmp_index+tmp_size-1))
else:
self.result.index = [(0,tmp_size-1)]
tmp_index += tmp_size
self.result.cont_data=True
tmp_part = None
def __parseFile_expat(self, in_file):
"Parses the given file, adding it to the result-queue"
self.result = None
self.in_description_section=False
self.result_description = { }
self.result_job_number = None
# Job Date is set in __read_file()
self.__filetype = None
# Expat XML-Parser & Binding handlers
self.xml_parser = xml.parsers.expat.ParserCreate()
self.xml_parser.StartElementHandler = self.__xmlStartTagFound
self.xml_parser.CharacterDataHandler = self.__xmlCharacterDataFound
self.xml_parser.EndElementHandler = self.__xmlEndTagFound
self.element_stack=[]
try:
# short version, but pyexpat buffers are awfully small
# self.xml_parser.ParseFile(in_file)
# read all, at least try
databuffer=in_file.read(-1)
# test wether really everything was read...
databuffer2=in_file.read(self.xml_parser.buffer_size)
if databuffer2=="":
# parse everything at once
self.xml_parser.Parse(databuffer,True)
else:
# do the first part ...
self.xml_parser.Parse(databuffer,False)
databuffer=databuffer2
# ... and again and again
while databuffer!="":
self.xml_parser.Parse(databuffer,False)
databuffer=in_file.read(-1)
self.xml_parser.Parse("",True)
except xml.parsers.expat.ExpatError as e:
print("result file %d: xml parser '%s' error at line %d, offset %d"%(self.no,
xml.parsers.expat.ErrorString(e.code),
e.lineno,
e.offset))
self.result = None
del databuffer
self.xml_parser.StartElementHandler=None
self.xml_parser.EndElementHandler=None
self.xml_parser.CharacterDataHandler=None
del self.xml_parser
# prepare result data
if self.result is not None and \
self.__filetype == ResultReader.ADCDATA_TYPE and \
self.adc_result_sample_counter>0:
# fill the ADC_Result with collected data
self.result.x=numpy.arange(self.adc_result_sample_counter, dtype="float32")/\
self.result.get_sampling_rate()
self.result.y=[]
self.result.index=[]
for i in range(2):
self.result.y.append(numpy.empty((self.adc_result_sample_counter,), dtype="int16"))
tmp_sample_counter=0
while self.adc_result_parts:
tmp_part=self.adc_result_parts.pop(0)
tmp_size=tmp_part.size/2
self.result.y[0][tmp_sample_counter:tmp_sample_counter+tmp_size]=tmp_part[::2]
self.result.y[1][tmp_sample_counter:tmp_sample_counter+tmp_size]=tmp_part[1::2]
self.result.index.append((tmp_sample_counter,tmp_sample_counter+tmp_size-1))
tmp_sample_counter+=tmp_size
self.result.cont_data=True
# Callback when a xml start tag is found
def __xmlStartTagFound(self, in_name, in_attribute):
# General Result-Tag
if in_name == "result":
self.result_job_number = int(in_attribute["job"])
# Job-Date is set in __read_file()
# Description
elif in_name == "description":
# old style description:
if len(in_attribute)!=0:
self.result_description = in_attribute.copy()
self.in_description_section=True
self.in_description_data=()
elif self.in_description_section and in_name == "item":
self.in_description_data=[in_attribute["key"], in_attribute["type"], ""]
# ADC_Results
elif in_name == "adcdata":
self.__filetype = ResultReader.ADCDATA_TYPE
self.adc_result_trailing_chars = ""
if self.result is None:
self.result = ADC_Result()
# None: new guess for adc data encoding
# "a": ascii
# "b": base64
self.adc_data_encoding = None
self.result.set_sampling_rate(float(in_attribute["rate"]))
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
self.result.set_description_dictionary(self.result_description.copy())
title="ADC-Result: job-id=%d"%int(self.result_job_number)
if len(self.result_description)>0:
for k,v in self.result_description.items():
title+=", %s=%s"%(k,v)
self.result.set_title(title)
self.result_description=None
self.adc_result_sample_counter = 0
self.adc_result_parts=[] # will contain arrays of sampled intervals, assumes same sample rate
else:
if float(in_attribute["rate"])!=self.result.get_sampling_rate():
print("sample rate different in ADC_Result, found %f, former value %f"%\
(float(in_attribute["rate"]),self.result.get_sampling_rate()))
new_samples=int(in_attribute["samples"])
self.adc_result_sample_counter += new_samples
# version depends on the inclusion of http://bugs.python.org/issue1137
if sys.hexversion>=0x020501f0:
# extend buffer to expected base64 size (2 channels, 2 byte)
required_buffer=int(new_samples*4/45+1)*62
if self.xml_parser.buffer_size < required_buffer:
try:
self.xml_parser.buffer_size=required_buffer
except AttributeError:
pass
# pass all chardata as one block
self.xml_parser.buffer_text = True
# do not change the contents
self.xml_parser.returns_unicode=False
# Error_Results
elif in_name == "error":
self.__filetype = ResultReader.ERROR_TYPE
self.result = Error_Result()
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
self.result.set_description_dictionary(self.result_description.copy())
# Temp_Results
elif in_name == "temp":
self.__filetype = ResultReader.TEMP_TYPE
self.result = Temp_Result()
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
# Config_Results
elif in_name == "conf":
self.__filetype = ResultReader.CONFIG_TYPE
self.result = Config_Result()
self.result.set_job_id(self.result_job_number)
self.result.set_job_date(self.result_job_date)
# maintain the stack
self.element_stack.append(in_name)
def __xmlCharacterDataFound(self, in_cdata):
if self.in_description_section and len(self.in_description_data):
self.in_description_data[2]+=in_cdata
# ADC_Result
elif self.__filetype == ResultReader.ADCDATA_TYPE and self.element_stack[-1]=="adcdata":
self.adc_result_trailing_chars+=in_cdata
# Error_Result
elif self.__filetype == ResultReader.ERROR_TYPE:
tmp_string = self.result.get_error_message()
if tmp_string is None: tmp_string = ""
tmp_string += in_cdata
self.result.set_error_message(tmp_string)
# Temp_Results
elif self.__filetype == ResultReader.TEMP_TYPE:
pass
# Config_Results
elif self.__filetype == ResultReader.CONFIG_TYPE:
pass
def __xmlEndTagFound(self, in_name):
# maintain the stack
self.element_stack.pop()
if in_name == "adcdata":
# ADC_Result
if self.__filetype == ResultReader.ADCDATA_TYPE:
# detect type of data encoding from first line
if self.adc_data_encoding is None:
self.adc_result_trailing_chars=self.adc_result_trailing_chars.strip()
first_line_end=self.adc_result_trailing_chars.find("\n")
first_line=""
if first_line_end!=-1:
first_line=self.adc_result_trailing_chars[:first_line_end]
else:
first_line=self.adc_result_trailing_chars
if len(first_line.lstrip("-0123456789 \t\n\r"))==0:
try:
list(map(int,list(filter(len,first_line.split()))))
except ValueError as e:
pass
else:
self.adc_data_encoding="a"
if self.adc_data_encoding is None and len(first_line)%4==0:
try:
base64.standard_b64decode(first_line)
except TypeError:
pass
else:
self.adc_data_encoding="b"
if self.adc_data_encoding is None:
print("unknown ADC data format \"%s\""%first_line)
tmp=None
if self.adc_data_encoding=="a":
values=list(map(int,self.adc_result_trailing_chars.split()))
tmp=numpy.array(values, dtype="int16")
elif self.adc_data_encoding=="b":
tmp_string=base64.standard_b64decode(self.adc_result_trailing_chars)
tmp=numpy.fromstring(tmp_string, dtype="int16")
del tmp_string
else:
print("unknown ADC data format")
self.adc_result_trailing_chars=""
self.adc_result_parts.append(tmp)
del tmp
return
elif in_name == "description":
self.in_description_section=False
elif self.in_description_section and in_name == "item":
# make item contents to dictionary item:
k,t,v=self.in_description_data
self.in_description_data=()
if t == "None":
self.result_description[k]=None
if t == "Float":
self.result_description[k]=float(v)
elif t == "Int":
self.result_description[k]=int(v)
elif t == "Long":
self.result_description[k]=int(v)
elif t == "Complex":
self.result_description[k]=complex(v)
elif t == "Boolean":
self.result_description[k]=bool(v)
elif t == "String":
self.result_description[k]=v
else:
# Anything else will be handled as a string
# Probably "repr".
self.result_description[k]=v
elif in_name == "result":
pass
# Error_Result
elif self.__filetype == ResultReader.ERROR_TYPE:
pass
# Temp_Result
elif self.__filetype == ResultReader.TEMP_TYPE:
pass
# Config_Result
elif self.__filetype == ResultReader.CONFIG_TYPE:
pass
class BlockingResultReader(ResultReader):
"""
to follow an active result stream
"""
def __init__(self, spool_dir=".", no=0, result_pattern="job.%09d.result", clear_jobs=False, clear_results=False):
ResultReader.__init__(self, spool_dir, no, result_pattern, clear_jobs=clear_jobs, clear_results=clear_results)
self.stop_no=None # end of job queue
self.poll_time=0.1 # sleep interval for polling results, <0 means no polling and stop
self.in_advance=0
def __iter__(self):
"""
get next job with iterator
block until result is available
"""
expected_filename=os.path.join(self.spool_dir,self.result_pattern%(self.no))
while (not self.quit_flag.isSet()) and (self.stop_no is None or self.stop_no>self.no):
if not os.access(expected_filename,os.R_OK):
# stop polling, if required
if self.poll_time<0: break
self.quit_flag.wait(self.poll_time)
continue
# find pending results
self.in_advance=max(self.no,self.in_advance)
in_advance_filename=os.path.join(self.spool_dir,self.result_pattern%(self.in_advance+1))
while os.access(in_advance_filename, os.R_OK) and (self.stop_no is None or self.stop_no>self.in_advance+1):
# do not more than 100 results in advance at one glance
if self.in_advance>self.no+100: break
self.in_advance+=1
in_advance_filename=os.path.join(self.spool_dir,self.result_pattern%(self.in_advance+1))
if self.quit_flag.isSet(): break
r=self.get_result_object(expected_filename)
if self.quit_flag.isSet(): break
if self.quit_flag.isSet(): break
yield r
if self.clear_results:
if os.path.isfile(expected_filename): os.remove(expected_filename)
if self.clear_jobs:
if os.path.isfile(expected_filename[:-7]): os.remove(expected_filename[:-7])
self.no+=1
expected_filename=os.path.join(self.spool_dir,self.result_pattern%(self.no))
return
def quit(self):
self.quit_flag.set()
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd">
<glade-project>
<name>damaris-gui</name>
<program_name>damaris</program_name>
<gnome_support>FALSE</gnome_support>
</glade-project>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+210
View File
@@ -0,0 +1,210 @@
<?xml version="1.0"?>
<!--
This syntax-file was generated by sourceview2codebuffer.xsl from
GtkSourceView's Python-syntax-file!
This transformation is not perfect so it may need some hand-word to fix
minor issues in this file.
You can get sourceview2codebuffer.xsl from http://pygtkcodebuffer.googlecode.com/.
-->
<syntax>
<string style="string" escape="\"><starts>([uUrR]|[uU][rR]|[rR][uU])?"""</starts><ends>"""</ends></string>
<string style="string" escape="\"><starts>([uUrR]|[uU][rR]|[rR][uU])?'''</starts><ends>'''</ends></string>
<string style="string" escape="\"><starts>([uUrR]|[uU][rR]|[rR][uU])?"</starts><ends>"</ends></string>
<string style="string" escape="\"><starts>([uUrR]|[uU][rR]|[rR][uU])?'</starts><ends>'</ends></string>
<keywordlist style="preprocessor">
<keyword>import</keyword>
<keyword>from</keyword>
<keyword>as</keyword>
<keyword>False</keyword>
<keyword>None</keyword>
<keyword>True</keyword>
<keyword>__name__</keyword>
<keyword>__debug__</keyword>
</keywordlist>
<keywordlist style="keyword">
<keyword>def</keyword>
<keyword>class</keyword>
<keyword>return</keyword>
</keywordlist>
<keywordlist style="keyword">
<keyword>and</keyword>
<keyword>assert</keyword>
<keyword>break</keyword>
<keyword>continue</keyword>
<keyword>del</keyword>
<keyword>elif</keyword>
<keyword>else</keyword>
<keyword>except</keyword>
<keyword>exec</keyword>
<keyword>finally</keyword>
<keyword>for</keyword>
<keyword>global</keyword>
<keyword>if</keyword>
<keyword>in</keyword>
<keyword>is</keyword>
<keyword>lambda</keyword>
<keyword>not</keyword>
<keyword>or</keyword>
<keyword>pass</keyword>
<keyword>print</keyword>
<keyword>raise</keyword>
<keyword>try</keyword>
<keyword>while</keyword>
<keyword>yield</keyword>
</keywordlist>
<keywordlist style="special">
<keyword>ArithmeticError</keyword>
<keyword>AssertionError</keyword>
<keyword>AttributeError</keyword>
<keyword>EnvironmentError</keyword>
<keyword>EOFError</keyword>
<keyword>Exception</keyword>
<keyword>FloatingPointError</keyword>
<keyword>ImportError</keyword>
<keyword>IndentationError</keyword>
<keyword>IndexError</keyword>
<keyword>IOError</keyword>
<keyword>KeyboardInterrupt</keyword>
<keyword>KeyError</keyword>
<keyword>LookupError</keyword>
<keyword>MemoryError</keyword>
<keyword>NameError</keyword>
<keyword>NotImplementedError</keyword>
<keyword>OSError</keyword>
<keyword>OverflowError</keyword>
<keyword>ReferenceError</keyword>
<keyword>RuntimeError</keyword>
<keyword>StandardError</keyword>
<keyword>StopIteration</keyword>
<keyword>SyntaxError</keyword>
<keyword>SystemError</keyword>
<keyword>SystemExit</keyword>
<keyword>TabError</keyword>
<keyword>TypeError</keyword>
<keyword>UnboundLocalError</keyword>
<keyword>UnicodeDecodeError</keyword>
<keyword>UnicodeEncodeError</keyword>
<keyword>UnicodeError</keyword>
<keyword>UnicodeTranslateError</keyword>
<keyword>ValueError</keyword>
<keyword>WindowsError</keyword>
<keyword>ZeroDivisionError</keyword>
<keyword>Warning</keyword>
<keyword>UserWarning</keyword>
<keyword>DeprecationWarning</keyword>
<keyword>PendingDeprecationWarning</keyword>
<keyword>SyntaxWarning</keyword>
<keyword>OverflowWarning</keyword>
<keyword>RuntimeWarning</keyword>
<keyword>FutureWarning</keyword>
<keyword>__import__</keyword>
<keyword>abs</keyword>
<keyword>apply</keyword>
<keyword>basestring</keyword>
<keyword>bool</keyword>
<keyword>buffer</keyword>
<keyword>callable</keyword>
<keyword>chr</keyword>
<keyword>classmethod</keyword>
<keyword>cmp</keyword>
<keyword>coerce</keyword>
<keyword>compile</keyword>
<keyword>complex</keyword>
<keyword>delattr</keyword>
<keyword>dict</keyword>
<keyword>dir</keyword>
<keyword>divmod</keyword>
<keyword>enumerate</keyword>
<keyword>eval</keyword>
<keyword>execfile</keyword>
<keyword>file</keyword>
<keyword>filter</keyword>
<keyword>float</keyword>
<keyword>getattr</keyword>
<keyword>globals</keyword>
<keyword>hasattr</keyword>
<keyword>hash</keyword>
<keyword>hex</keyword>
<keyword>id</keyword>
<keyword>input</keyword>
<keyword>int</keyword>
<keyword>intern</keyword>
<keyword>isinstance</keyword>
<keyword>issubclass</keyword>
<keyword>iter</keyword>
<keyword>len</keyword>
<keyword>list</keyword>
<keyword>locals</keyword>
<keyword>long</keyword>
<keyword>map</keyword>
<keyword>max</keyword>
<keyword>min</keyword>
<keyword>object</keyword>
<keyword>oct</keyword>
<keyword>open</keyword>
<keyword>ord</keyword>
<keyword>pow</keyword>
<keyword>property</keyword>
<keyword>range</keyword>
<keyword>raw_input</keyword>
<keyword>reduce</keyword>
<keyword>reload</keyword>
<keyword>repr</keyword>
<keyword>round</keyword>
<keyword>setattr</keyword>
<keyword>slice</keyword>
<keyword>staticmethod</keyword>
<keyword>str</keyword>
<keyword>sum</keyword>
<keyword>super</keyword>
<keyword>tuple</keyword>
<keyword>type</keyword>
<keyword>unichr</keyword>
<keyword>unicode</keyword>
<keyword>vars</keyword>
<keyword>xrange</keyword>
<keyword>zip</keyword>
</keywordlist>
<!-- Some Experiment keywords -->
<keywordlist style="special">
<keyword>set_pfg</keyword>
<keyword>set_pfg_wt</keyword>
<keyword>set_description</keyword>
<keyword>get_description</keyword>
<keyword>set_phase</keyword>
<keyword>set_frequency</keyword>
<keyword>ttl_pulse</keyword>
<keyword>rf_pulse</keyword>
<keyword>state_start</keyword>
<keyword>state_end</keyword>
<keyword>loop_start</keyword>
<keyword>loop_end</keyword>
<keyword>set_pts_local</keyword>
<keyword>wait</keyword>
<keyword>record</keyword>
</keywordlist>
<keywordlist style="datatype">
<keyword>Accumulation</keyword>
<keyword>Experiment</keyword>
<keyword>ADC_Result</keyword>
<keyword>MeasurementResult</keyword>
<keyword>AccumulatedValue</keyword>
</keywordlist>
<pattern style="comment">#.*$</pattern>
<pattern style="datatype">\bself\b</pattern>
<pattern style="number">\b([1-9][0-9]*|0)([Uu]([Ll]|LL|ll)?|([Ll]|LL|ll)[Uu]?)?\b</pattern>
<pattern style="number">\b([0-9]+[Ee][-]?[0-9]+|([0-9]*\.[0-9]+|[0-9]+\.)([Ee][-]?[0-9]+)?)[fFlL]?</pattern>
<pattern style="number">\b0[0-7]+([Uu]([Ll]|LL|ll)?|([Ll]|LL|ll)[Uu]?)?\b</pattern>
<pattern style="number">\b0[xX][0-9a-fA-F]+([Uu]([Ll]|LL|ll)?|([Ll]|LL|ll)[Uu]?)?\b</pattern>
</syntax>
+152
View File
@@ -0,0 +1,152 @@
#! /usr/bin/env python
import time
import sys
import os
import os.path
import tables
import damaris.data.DataPool as DataPool
import damaris.gui.ResultReader as ResultReader
import damaris.gui.ExperimentWriter as ExperimentWriter
import damaris.gui.BackendDriver as BackendDriver
import damaris.gui.ResultHandling as ResultHandling
import damaris.gui.ExperimentHandling as ExperimentHandling
def some_listener(event):
if event.subject=="__recentexperiment" or event.subject=="__recentresult":
r=event.origin.get("__recentresult",-1)+1
e=event.origin.get("__recentexperiment",-1)+1
if e!=0:
ratio=100.0*r/e
else:
ratio=100.0
print("\r%d/%d (%.0f%%)"%(r,e,ratio), end=' ')
class ScriptInterface:
def __init__(self, exp_script=None, res_script=None, backend_executable=None, spool_dir="spool"):
self.exp_script=exp_script
self.res_script=res_script
self.backend_executable=backend_executable
self.spool_dir=os.path.abspath(spool_dir)
self.exp_handling=self.res_handling=None
self.exp_writer=self.res_reader=self.back_driver=None
if self.backend_executable is not None:
self.back_driver=BackendDriver.BackendDriver(self.backend_executable, spool_dir)
if self.exp_script: self.exp_writer=self.back_driver.get_exp_writer()
if self.res_script: self.res_reader=self.back_driver.get_res_reader()
else:
self.back_driver=None
if self.exp_script: self.exp_writer=ExperimentWriter.ExperimentWriter(spool_dir)
if self.res_script: self.res_reader=ResultReader.ResultReader(spool_dir)
self.data=DataPool()
def runScripts(self):
# get script engines
if self.exp_script and self.exp_writer:
self.exp_handling=ExperimentHandling.ExperimentHandling(self.exp_script, self.exp_writer, self.data)
if self.res_script and self.res_reader:
self.res_handling=ResultHandling.ResultHandling(self.res_script, self.res_reader, self.data)
# start them
if self.exp_handling: self.exp_handling.start()
if self.back_driver is not None: self.back_driver.start()
if self.res_handling: self.res_handling.start()
def waitForScriptsEnding(self):
# time of last dump
dump_interval=600
next_dump_time=time.time()+dump_interval
# keyboard interrupts are handled in extra cleanup loop
try:
while [_f for _f in [self.exp_handling,self.res_handling,self.back_driver] if _f]:
time.sleep(0.1)
if time.time()>next_dump_time:
self.dump_data("pool/data_pool.h5")
next_dump_time+=dump_interval
if self.exp_handling is not None:
if not self.exp_handling.is_alive():
self.exp_handling.join()
if self.exp_handling.raised_exception:
print(": experiment script failed at line %d (function %s): %s"%(self.exp_handling.location[0],
self.exp_handling.location[1],
self.exp_handling.raised_exception))
else:
print(": experiment script finished")
self.exp_handling = None
if self.res_handling is not None:
if not self.res_handling.is_alive():
self.res_handling.join()
if self.res_handling.raised_exception:
print(": result script failed at line %d (function %s): %s"%(self.res_handling.location[0],
self.res_handling.location[1],
self.res_handling.raised_exception))
else:
print(": result script finished")
self.res_handling = None
if self.back_driver is not None:
if not self.back_driver.is_alive():
print(": backend finished")
self.back_driver=None
except KeyboardInterrupt:
still_running=[_f for _f in [self.exp_handling,self.res_handling,self.back_driver] if _f]
for r in still_running:
r.quit_flag.set()
for r in still_running:
r.join()
def dump_data(self, filename):
try:
# write data from pool
dump_file=tables.open_file(filename,mode="w",title="DAMARIS experiment data")
self.data.write_hdf5(dump_file, complib='zlib', complevel=6)
# write scripts
scriptgroup=dump_file.create_group("/","scripts","Used Scripts")
dump_file.create_array(scriptgroup,"experiment_script", self.exp_script)
dump_file.create_array(scriptgroup,"result_script", self.res_script)
dump_file.create_array(scriptgroup,"backend_executable", self.backend_executable)
dump_file.create_array(scriptgroup,"spool_directory", self.spool_dir)
dump_file.flush()
dump_file.close()
dump_file=None
# todo
except Exception as e:
print("dump failed", e)
if __name__=="__main__":
if len(sys.argv)==1:
print("%s: data_handling_script [spool directory]"%sys.argv[0])
sys.exit(1)
if len(sys.argv)==3:
spool_dir=os.getcwd()
else:
spool_dir=sys.argv[3]
expscriptfile=open(sys.argv[1])
expscript=expscriptfile.read()
resscriptfile=open(sys.argv[2])
resscript=resscriptfile.read()
si=ScriptInterface(expscript, resscript,"/usr/lib/damaris/backends/Mobilecore", spool_dir)
si.data.register_listener(some_listener)
si.runScripts()
si.waitForScriptsEnding()
si.dump_data("data_pool.h5")
si=None
View File
+120
View File
@@ -0,0 +1,120 @@
import serial
import re
import operator
from functools import reduce
DEBUG=False
reply_pattern = re.compile(r"\x02..(.*)\x03.", re.DOTALL)
# example answer '\x02PV279.8\x03/'
# [EOT] = \x04
# [STX] = \x02
# [ENQ] = \x05
# [ETX] = \x03
# [ACK] = \x06
# BCC = checksum
standard_device='0011'
EOT = '\x04'
STX = '\x02'
ENQ = '\x05'
ETX = '\x03'
ACK = '\x06'
NAK = '\x15'
"""
Paramter read example:
Master: [EOT]0011PV[ENQ]
Instrument: [STX]PV16.4[ETX]{BCC}
Writing data:
Master: [EOT] {GID}{GID}{UID}{UID}[STX]{CHAN}(c1)(c2)<DATA>[ETX](BCC)
"""
def checksum(message):
bcc = (reduce(operator.xor, list(map(ord,message))))
return chr(bcc)
class Eurotherm(object):
def __init__(self, serial_device, baudrate = 19200):
self.device = standard_device
# timeout: 110 ms to get all answers.
self.s = serial.Serial(serial_device,
baudrate = baudrate,
bytesize=7,
parity='E',
stopbits=1,
timeout=0.11)
self._expect_len = 50
def send_read_param(self, param):
self.s.write(EOT + self.device + param + ENQ)
def read_param(self, param):
self.s.flushInput()
self.send_read_param(param)
answer = self.s.read(self._expect_len)
m = reply_pattern.search(answer)
if m is None:
# Reading _expect_len bytes was not enough...
answer += self.s.read(200)
m = reply_pattern.search(answer)
if m is not None:
self._expect_len = len(answer)
return m.group(1)
else:
print("received:", repr(answer))
return None
def write_param(self, mnemonic, data):
if len(mnemonic) > 2:
raise ValueError
bcc = checksum(mnemonic + data + ETX)
mes = EOT+self.device+STX+mnemonic+data+ETX+bcc
if DEBUG:
for i in mes:
print(i,hex(ord(i)))
self.s.flushInput()
self.s.write(mes)
answer = self.s.read(1)
# print "received:", repr(answer)
if answer == "":
# raise IOError("No answer from device")
return None
return answer[-1] == ACK
def get_current_temperature(self):
temp = self.read_param('PV')
if temp is None:
temp = "0"
return temp
def set_temperature(self, temperature):
return self.write_param('SL', str(temperature))
def get_setpoint_temperature(self):
return self.read_param('SL')
if __name__ == '__main__':
import time
delta=5
date = time.strftime('%Y-%m-%d')
f = open('templog_%s'%date,'w')
f.write('# Start time: %s\n#delta t : %.1f s\n'%(time.asctime(), delta))
et = Eurotherm("/dev/ttyUSB0")
while True:
for i in range(120):
time.sleep(delta)
#t = time.strftime()
T = et.get_current_temperature()
l = '%f %s\n'%(time.time(),T)
print(time.asctime(), T)
f.write(l)
f.flush()
f.write('# MARK -- %s --\n'%(time.asctime()))
+246
View File
@@ -0,0 +1,246 @@
#!/usr/bin/env python2
# -*- encoding: utf-8 -*-
import optparse
import os, sys
import serial
import numpy as N
import struct
import binascii
import time
DEBUG = False
crc_test = "\x0d\x01\x00\x62\x00\x33"
crc_expected = 0xddd
def crc(message):
"""
(from "Modbus_over_serial_line_V1_02.pdf" at http://www.modbus.org)
6.2.2 CRC Generation
====================
The Cyclical Redundancy Checking (CRC) field is two bytes, containing a 16bit binary value. The CRC value is calculated by the
transmitting device, which appends the CRC to the message. The device that receives recalculates a CRC during receipt of the
message, and compares the calculated value to the actual value it received in the CRC field. If the two values are not equal, an error
results.
The CRC is started by first preloading a 16bit register to all 1s. Then a process begins of applying successive 8bit bytes of the
message to the current contents of the register. Only the eight bits of data in each character are used for generating the CRC. Start
and stop bits and the parity bit, do not apply to the CRC.
During generation of the CRC, each 8bit character is exclusive ORed with the register contents. Then the result is shifted in the
direction of the least significant bit (LSB), with a zero filled into the most significant bit (MSB) position. The LSB is extracted and
examined. If the LSB was a 1, the register is then exclusive ORed with a preset, fixed value. If the LSB was a 0, no exclusive OR takes
place.
This process is repeated until eight shifts have been performed. After the last (eighth) shift, the next 8bit character is exclusive ORed
with the registers current value, and the process repeats for eight more shifts as described above. The final content of the register,
after all the characters of the message have been applied, is the CRC value.
A procedure for generating a CRC is:
1. Load a 16bit register with FFFF hex (all 1s). Call this the CRC register.
2. Exclusive OR the first 8bit byte of the message with the loworder byte of the 16bit CRC register, putting the result in the
CRC register.
3. Shift the CRC register one bit to the right (toward the LSB), zerofilling the MSB. Extract and examine the LSB.
4. (If the LSB was 0): Repeat Step 3 (another shift).
(If the LSB was 1): Exclusive OR the CRC register with the polynomial value 0xA001 (1010 0000 0000 0001).
5. Repeat Steps 3 and 4 until 8 shifts have been performed. When this is done, a complete 8bit byte will have been
processed.
6. Repeat Steps 2 through 5 for the next 8bit byte of the message. Continue doing this until all bytes have been processed.
7. The final content of the CRC register is the CRC value.
8. When the CRC is placed into the message, its upper and lower bytes must be swapped as described below.
Placing the CRC into the Message
When the 16bit CRC (two 8bit bytes) is transmitted in the message, the low-order byte will be transmitted first, followed by the high-
order byte.
"""
crc = 0xffff # step 1
for byte in message:
if type(byte) == type(str):
crc ^= ord(byte) # step 2: xor with lower byte of crc
else:
crc ^= byte # step 2: xor with lower byte of crc
for i in range(8): # step 5: repeat 2-4 until 8 shifts were performed
if (crc & 0x1) == 0: # step 4: check LSB
mask = 0x0
else:
mask = 0xa001
crc >>= 1 # step 3: shift one bit to the right
crc ^= mask
if DEBUG: print("Message:", [i for i in message], " CRC:", hex(crc))
return chr(crc & 0xff) + chr(
(crc >> 8) & 0xff) # return lower byte, upper byte (shift to the right, return last byte) i
class Flow:
def __init__(self, device="/dev/ttyUSB0"):
self.s = serial.Serial(port=device, timeout=0.02)
self.s.readline()
self.s.timeout = 0.2
self.address = bytearray([0x3])
self.rcommands = {
"flow": [bytearray([0x3, 0x0, 0x0, 0x0, 0x2]), ">f"],
"temperature": [bytearray([0x3, 0x0, 0x2, 0x0, 0x2]), ">f"],
"setflow": [bytearray([0x3, 0x0, 0x6, 0x0, 0x2]), ">f"],
"total": [bytearray([0x3, 0x0, 0x8, 0x0, 0x2]), ">f"],
"alarm": [bytearray([0x3, 0x0, 0xc, 0x0, 0x1]), ">H"],
"hwerror": [bytearray([0x3, 0x0, 0xd, 0x0, 0x1]), ">H"],
"unit_flow": [bytearray([0x3, 0x0, 0x16, 0x0, 0x4]), ">8s"],
"medium": [bytearray([0x3, 0x00, 0x1a, 0x0, 0x4]), ">8s"],
"device": [bytearray([0x3, 0x00, 0x23, 0x0, 0x4]), ">8s"],
"unit_total": [bytearray([0x3, 0x40, 0x48, 0x0, 0x4]), ">8s"],
}
self.wcommands = {
"flow": [bytearray([0x06, 0x0, 0x6]), ">f"]
}
def send_data(self, data_and_type):
"""
send data to the device at self.address.
"""
data, type = data_and_type
if DEBUG: print("send", data_and_type, "data:", binascii.hexlify(data), "type:", type, self.address)
msg = self.address + data
msg_crc = msg + crc(msg)
if DEBUG: print("send:", repr(str(msg_crc)))
nbytes_sent = self.s.write(str(msg_crc))
if DEBUG: print("send:", self.s)
if DEBUG: print("Sent bytes:", nbytes_sent)
time.sleep(0.1) # this is necessary, otherwise no data can be received
return msg_crc
def recv_data(self):
"""
Reading Data
============
Function: 0x3
Format of the resulting string
Addr Func NoBytes Byte1...N CRCHi CRCLo
"""
message = bytearray(self.s.read(3))
if DEBUG: print("recv_data: header:", binascii.hexlify(message))
if message == "":
return
addr, func, nbytes = message
if func == 0x03:
data = self.s.read(nbytes)
if DEBUG: print("recv_data: data: %i %s" % (nbytes, binascii.hexlify(data)))
message += data
crc_data = self.s.read(2)
if DEBUG: print("recv_data: crc", repr(str(crc_data)))
if crc(message) != crc_data:
print("mismatch", crc(message), crc_data)
return data
def get_var(self, named_type="flow"):
"""
get variable from device, variables are definde in self.rcommands as list [bytearray[hiAd, loAd, numHi, numLo], type]
"""
cmd,fmt = self.rcommands[named_type]
if DEBUG: print("get_var", cmd,fmt)
self.send_data([cmd,fmt])
d = self.recv_data()
if d:
d = struct.unpack(fmt, d)[0]
return d
# msg = self.recv_data()
# print str(msg)
# float struct.unpack('>f', "4byte string")
# ushort struct.unpack('>H', "2byte string")
def current_flow(self):
# start register 0x0000
# 2 bytes
cmd = self.address + bytearray([0x3, 0x0, 0x0, 0x0, 0x2])
return cmd + crc(cmd)
def current_temperature(self):
# start register 0x0002
#
#
cmd = self.address + bytearray([0x3, 0x0, 0x2, 0x0, 0x2])
return cmd + crc(cmd)
def set_flow(self, flow):
"""
write multiple registers: 0x10
startHi
startLo
numregHi
numregLo
numbytes
"""
_flow = struct.pack(">f", flow)
# cmd = address + bytearray([0x10,0x0,0x6, 0x0, 0x2, 0x2]) + _flow
cmd1 = self.address + bytearray([0x06, 0x0, 0x6]) + _flow
self.s.write(cmd1 + crc(cmd1))
time.sleep(0.01)
# print self.s.readline()
# self.recv_data()
def cmd(self, data):
cmds = self.address + data
for i in cmds:
print(hex(ord(i)), end=' ')
return cmds + crc(cmds)
if __name__ == "__main__":
"""
This is the command line program to control the red-y flow control series.
Usage: ./flow_control.py [FLOW]
Several parameters are printed on every execution
The default serial device is /dev/ttyUSB0. If you want to change that create a
file called "~/.flow" with the first line containing an alternate path, i.e. /dev/ttyUSB1
If you want to enable debug output edit the script and set DEBUG=True.
Sample output:
user@pfg # flow
Device: GSCC9SA
Medium: N2B
Current Flow: 0.00
Current Set Flow: 0.00
Flow Unit: ln/min
Total Gas: 0.00
Total Unit: lnF
Current Temperature: 22.38 C
Alarm: 0
HW Error: 0
"""
conf = os.path.expanduser('~/.flow')
if os.path.exists(conf):
dev = open(conf).readlines()[0].strip()
redy = Flow(device=dev)
print("Device: %6s" % (redy.get_var("device")))
print("Medium: %6s" % (redy.get_var("medium")))
print("Current Flow: %6.2f" % (redy.get_var("flow")))
print("Current Set Flow: %6.2f" % (redy.get_var("setflow")))
print("Flow Unit: %6s" % (redy.get_var("unit_flow")))
print("Total Gas: %6.2f" % (redy.get_var("total")))
print("Total Unit: %6s" % (redy.get_var("unit_total")))
print("Current Temperature: %6.2f C" % (redy.get_var("temperature")))
print("Alarm: %8i" % (redy.get_var("alarm")))
print("HW Error: %8i" % (redy.get_var("hwerror")))
if len(sys.argv) > 1:
try:
float(sys.argv[1])
except:
raise SyntaxError("usage: %s <flow> # to set flow or empty to get current flow\nfirst line in ~/.flow defines serial tty, default: /dev/ttyUSB0" % (sys.argv[0]))
print("Set flow: %6.2f"%(float(sys.argv[1])))
redy.set_flow(float(sys.argv[1]))
+125
View File
@@ -0,0 +1,125 @@
__author__ = 'Markus Rosenstihl <markus.rosenstihl@physik.tu-darmstadt.de>'
import re
import serial
import time
import logging
import os
logfile = os.path.expanduser("~/.goniometer.log")
logging.basicConfig(filename=logfile,level=logging.INFO)
logger = logging.getLogger()
logger.name = "goniometer"
class goniometer:
"""
This class is meant to control the goniometer probe head from Marco Braun (AXYs)
"""
def __init__(self, dev="/dev/ttyUSB0",
baudrate=38400,
timeout=1,
return_to_origin=True,
loglevel=2,
logangle=False):
"""
Upon creating an instance the goniometer will be initialised and the current angle defined as being 0.
The goniometer log can be found in `~/.goniometer.log`.
:param return_to_origin: Set to true if you want the stepper to return to the beginning when
the class instance is deleted (for example with "del" ). Default: True
:type return_to_origin: bool
:param loglevel: This sets the log level 0=DEBUG,1=INFO,2=WARNING. Default: 2
:type loglevel: int
:param logangle: Should a history of the angles be kept? The log can be accessed in the `_angle_history`
variable. Default: False
:type logangle: bool
"""
self.serial = serial.Serial(port=dev, baudrate=baudrate, timeout=timeout)
logger.setLevel([logging.DEBUG,logging.INFO,logging.WARNING][loglevel])
self.serial.readlines() # empty serial buffer
self.current_angle = 0.0
self._angle_history = []
self.initialise()
self.return_to_origin = return_to_origin
self.logangel = logangle
def __del__(self):
if self.return_to_origin:
logging.info("Return to origin")
self._send("g1")
self._wait()
self.serial.close()
def _send(self, string_to_send):
formatted_string = "%s>\r\n"%string_to_send
logging.debug("send: %s"%(repr(formatted_string)))
self.serial.write(formatted_string)
def _recv(self):
retstr = self.serial.readline().strip().replace("\x00","")
search_result = re.search(r"<(\d+)>", retstr)
if search_result != None:
angle = float(search_result.group(1))/36.0
self.current_angle = angle
if self.logangle:
self._angle_history.append(angle)
logging.debug("%.2f"%angle)
return angle
else:
return None
def _wait(self):
logging.debug("Wait until rotation finnished (current: %.2f)"%(self.current_angle))
time.sleep(4)
while True:
retstr = self._recv()
if not retstr:
logging.debug("Rotation finnished (current: %.2f)"%(self.current_angle))
break
def initialise(self):
logging.info("Initialize goniometer")
self._send("R000")
self.current_angle = 0.0
def stop(self):
"""
Stops the current movement, not possible while doing :func:`step` and :func:`angle`
"""
logging.info("Stop goniometer")
self._send("S456")
def step(self, sample_rotation_degree=1.0):
"""
How many degrees should we step further. Negative values are not allowed, the stepper would return to 0.
A ValueError exception is raised in this case.
:param sample_rotation_degree: How many degrees to rotate. Default: 1
:type sample_rotation_degree: float
"""
if sample_rotation_degree <= 0:
raise ValueError("This does not what you expect, would move to the start position whatever the value")
logging.info("Step goniometer: %.2f"%sample_rotation_degree)
direction = "g" if (sample_rotation_degree < 0) else "G"
sample_rotation_degree = -1*sample_rotation_degree if sample_rotation_degree < 0 else sample_rotation_degree
steps = int(sample_rotation_degree%360)
self._send("%s%i"%(direction, steps ))
self._wait()
def angle(self, angle_degree):
"""
To what angle, in degrees, should we rotate. Mod 360 is calculated for `angle_degree`.
If angle is smaller than current angle an exception will be raised.
(From the underlying :func:`step` function)
TODO: rotate either back or over to the wanted position.
:param angle_degree: position in degree
:type angle_degree: float
"""
angle_degree %= 360
delta_degree = angle_degree - self.current_angle
logging.info("Rotate tp %.2f (from %.2f, i.e. delta=%.2f)"%(angle_degree, self.current_angle, delta_degree))
self.step(sample_rotation_degree=delta_degree)
+127
View File
@@ -0,0 +1,127 @@
import numpy as N
import sys
if sys.version_info > (2,6,0):
import numbers
else:
pass
if sys.version_info > (2,6,0):
def lin_range(start,stop,step):
if isinstance(step, numbers.Integral):
return N.linspace(start,stop,step)
else:
return N.arange(start,stop,step)
else:
def lin_range(start,stop,step):
return N.arange(start,stop,step)
def log_range(start, stop, stepno):
if (start<=0 or stop<=0 or stepno<1):
raise ValueError("start, stop must be positive and stepno must be >=1")
return N.logspace(N.log10(start),N.log10(stop), num=stepno)
def staggered_range(some_range, size=3):
m=0
if isinstance(some_range, N.ndarray):
is_numpy = True
some_range = list(some_range)
else:
is_numpy = False
new_list=[]
for k in range(len(some_range)):
for i in range(size):
try:
index = (m*size)
new_list.append(some_range.pop(index))
except IndexError:
break
m+=1
if is_numpy:
new_list = N.asarray(new_list+some_range)
else:
new_list+=some_range
return new_list
def combine_ranges(*ranges):
new_list = []
for r in ranges:
new_list.extend(r)
return new_list
combined_ranges=combine_ranges
def interleaved_range(some_list, left_out):
"""
in first run, do every n-th, then do n-1-th of the remaining values and so on...
"""
m=0
new_list = []
for j in range(left_out):
for i in range(len(some_list)):
if (i*left_out+m) < len(some_list):
new_list.append(some_list[i*left_out+m])
else:
m+=1
break
if isinstance(some_list, N.ndarray):
new_list = N.array(new_list)
return new_list
# These are the generators
def lin_range_iter(start,stop, step):
this_one=float(start)+0.0
if step>0:
while (this_one<=float(stop)):
yield this_one
this_one+=float(step)
else:
while (this_one>=float(stop)):
yield this_one
this_one+=float(step)
def log_range_iter(start, stop, stepno):
if (start<=0 or stop<=0 or stepno<1):
raise ValueError("start, stop must be positive and stepno must be >=1")
if int(stepno)==1:
factor=1.0
else:
factor=(stop/start)**(1.0/int(stepno-1))
for i in range(int(stepno)):
yield start*(factor**i)
def staggered_range_iter(some_range, size = 1):
"""
size=1: do one, drop one, ....
size=n: do 1 ... n, drop n+1 ... 2*n
in a second run the dropped values were done
"""
left_out=[]
try:
while True:
for i in range(size):
yield next(some_range)
for i in range(size):
left_out.append(next(some_range))
except StopIteration:
pass
# now do the droped ones
for i in left_out:
yield i
def combined_ranges_iter(*ranges):
"""
iterate over one range after the other
"""
for r in ranges:
for i in r:
yield i
combine_ranges_iter=combined_ranges_iter
+30
View File
@@ -0,0 +1,30 @@
import math
__all__ = ['rotate_signal']
def rotate_signal(timesignal, angle):
"Rotate <timesignal> by <angle> degrees"
# implicit change to float arrays!
if timesignal.get_number_of_channels()!=2:
raise Exception("rotation defined only for 2 channels")
# simple case 0, 90, 180, 270 degree
reduced_angle=divmod(angle, 90)
if abs(reduced_angle[1])<1e-6:
reduced_angle=reduced_angle[0]%4
if reduced_angle==0:
return
elif reduced_angle==1:
timesignal.y[1]*=-1
timesignal.y=[timesignal.y[1],timesignal.y[0]]
elif reduced_angle==2:
timesignal.y[0]*=-1
timesignal.y[1]*=-1
elif reduced_angle==3:
timesignal.y[0]*=-1
timesignal.y=[timesignal.y[1],timesignal.y[0]]
else:
sin_angle=math.sin(angle/180.0*math.pi)
cos_angle=math.cos(angle/180.0*math.pi)
timesignal.y=[cos_angle*timesignal.y[0]-sin_angle*timesignal.y[1],
sin_angle*timesignal.y[0]+cos_angle*timesignal.y[1]]