move to a more standard python packaging structure
This commit is contained in:
@@ -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"]
|
||||
@@ -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()
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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__!")
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,3 @@
|
||||
from .Experiment import Experiment
|
||||
from damaris.tools.ranges import *
|
||||
#__all__=["Experiment"]
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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>
|
||||
@@ -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
|
||||
@@ -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()))
|
||||
Executable
+246
@@ -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 16–bit 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 16–bit register to all 1’s. Then a process begins of applying successive 8–bit 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 8–bit 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 8–bit character is exclusive ORed
|
||||
with the register’s 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 16–bit register with FFFF hex (all 1’s). Call this the CRC register.
|
||||
2. Exclusive OR the first 8–bit byte of the message with the low–order byte of the 16–bit CRC register, putting the result in the
|
||||
CRC register.
|
||||
3. Shift the CRC register one bit to the right (toward the LSB), zero–filling 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 8–bit byte will have been
|
||||
processed.
|
||||
6. Repeat Steps 2 through 5 for the next 8–bit 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 16–bit CRC (two 8–bit 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]))
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
Reference in New Issue
Block a user