diff --git a/src/experiments/Experiment.py b/src/experiments/Experiment.py
index 5ef264c..aaa42c3 100644
--- a/src/experiments/Experiment.py
+++ b/src/experiments/Experiment.py
@@ -90,6 +90,15 @@ class Experiment:
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 -------------------------------------------------------------------------------------
@@ -118,6 +127,8 @@ class Experiment:
the_value=1<' % the_value))
+
+ self.total_time[-1] += length
## Same as ttl_pulse, but no *channel* keyword
def ttls(self, length = None, value = None):
@@ -132,8 +143,10 @@ class Experiment:
s_content = '' % 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):
"""
@@ -157,6 +170,7 @@ class Experiment:
This must be closed with state_end()
"""
self.state_list.append('\n' % repr(time))
+ self.total_time[-1] += time
## End of *state_start*
def state_end(self):
@@ -184,6 +198,8 @@ class Experiment:
self.state_list.append(StateSimple(time,s_content))
else:
self.state_list.append(StateSimple(time))
+
+ self.total_time[-1] += time
## Records data with given number of samples, sampling-frequency frequency and sensitivity
# @param samples Number of samples to record
@@ -247,6 +263,7 @@ class Experiment:
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
@@ -264,6 +281,9 @@ class Experiment:
# (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):
@@ -272,6 +292,10 @@ class Experiment:
"""
# (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
## Set the frequency and phase of the frequency source.
## This state needs 2us.
@@ -291,6 +315,8 @@ class Experiment:
if ttls != 0:
s_content += '' % ttls
self.state_list.append(StateSimple(2e-6, s_content))
+
+ self.total_time[-1] += 2e-6
## Creates a, possibly shaped, pulsed gradient.
# @param dac_value DAC value to set
@@ -331,10 +357,13 @@ class Experiment:
if form == 'rec': # shape==None --> rectangular gradients
s_content = '' % (trigger, dac_value)
self.state_list.append(StateSimple(length, s_content))
+ self.total_time[-1] += length
if not is_seq:
s_content = ''
self.state_list.append(StateSimple(42*9e-8, s_content))
+
+ self.total_time[-1] += 42*9e-8
elif form == 'sin2':
# sin**2 shape
@@ -342,9 +371,12 @@ class Experiment:
dac = int (dac_value*numpy.sin(numpy.pi/length*t)**2)
s_content = '' % (trigger, dac)
self.state_list.append(StateSimple(resolution, s_content))
+
# set it back to zero
s_content = '' % (trigger)
self.state_list.append(StateSimple(resolution, s_content))
+
+ self.total_time[-1] += resolution*(len(t_steps)+1)
elif form == 'sin':
# sin shape
@@ -355,6 +387,8 @@ class Experiment:
# set it back to zero
s_content = '' % (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
@@ -385,10 +419,15 @@ class Experiment:
s_content = '' \
% (dac_id, dac_value, ttls)
self.state_list.append(StateSimple(length, s_content))
+
+ self.total_time[-1] += length
+
if not is_seq:
s_content = '' \
% (dac_id, ttls)
self.state_list.append(StateSimple(42*9e-8, s_content))
+
+ self.total_time[-1] += 42*9e-8
## sets the phase of the frequency source.
## This state needs 0.5us, though the phase switching time is dependent on the frequency source
@@ -408,6 +447,8 @@ class Experiment:
if ttls!=0:
s_content += '' % ttls
self.state_list.append(StateSimple(0.5e-6, s_content))
+
+ self.total_time[-1] += 0.5e-6
## sets a description which is carried via the back end result
## file to the result script in the front end. In the result script
@@ -434,6 +475,8 @@ class Experiment:
"""
self.state_list.append(StateSimple(1e-6, ''))
self.state_list.append(StateSimple(1e-6, ''))
+
+ self.total_time[-1] += 2e-6
# / Commands -----------------------------------------------------------------------------------
@@ -508,7 +551,17 @@ class Experiment:
:returns: XML quit-job
"""
return '\n'
-
+
+ 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):
@@ -529,8 +582,7 @@ def self_test():
e.ttl_pulse(1e-6/3, None, 7) # val = 7
if True:
e.loop_start(30)
- e.set_pfg(dac_value=1024, is_seq = True)
- e.set_pfg_wt(dac_value=2048)
+ 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()
@@ -550,6 +602,8 @@ def self_test():
raise AssertionError("An exception should happen")
e.set_pts_local()
print e.write_xml_string()
+
+ print e.get_length()
if __name__ == '__main__':
self_test()
diff --git a/src/gui/DamarisGUI.py b/src/gui/DamarisGUI.py
index e9e118b..a2c3415 100644
--- a/src/gui/DamarisGUI.py
+++ b/src/gui/DamarisGUI.py
@@ -53,9 +53,11 @@ matplotlib.rcParams[ "axes.formatter.limits" ] = "-3,3"
if matplotlib.rcParams[ "backend" ] == "GTK":
from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
+
max_points_to_display = 0 # no limit
elif matplotlib.rcParams[ "backend" ] == "GTKCairo":
from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas
+
max_points_to_display = 1 << 14 # cairo cannot render longer paths than 18???
else:
# default
@@ -181,7 +183,7 @@ class DamarisGUI:
# my notebook
self.main_notebook = self.xml_gui.get_widget( "main_notebook" )
- self.log = LogWindow( self.xml_gui )
+ self.log = LogWindow( self.xml_gui , self)
self.sw = ScriptWidgets( self.xml_gui )
@@ -195,6 +197,9 @@ class DamarisGUI:
# lock file to prevent other DAMARIS to start immediatly
self.lockfile = LockFile() # = os.path.expanduser("~/.damaris.lock")
self.id = None
+
+ #to stop queued experiments
+ self.stop_experiment_flag = threading.Event()
exp_script = u""
if exp_script_filename is not None and exp_script_filename != "":
@@ -277,11 +282,14 @@ class DamarisGUI:
self.toolbar_run_button.emit("clicked")
gtk.main( )
gtk.gdk.threads_leave( )
+
self.si = None
self.sw = None
self.config = None
self.xml_gui = None
+ # event handling: the real acitons in gui programming
+
# first global events
def quit_event( self, widget, data=None ):
@@ -449,16 +457,32 @@ class DamarisGUI:
self.log.textbuffer.create_mark( "lastdumped", self.log.textbuffer.get_end_iter( ), left_gravity=True )
loop_run=0
+ #user shorter interval because this blocks user events from the GUI
interval = 0.1
+ self.stop_experiment_flag.clear()
+
self.id = self.lockfile.add_experiment()
while not self.lockfile.am_i_next():
- time.sleep(interval)
+ self.stop_experiment_flag.wait(interval)
while gtk.events_pending():
gtk.main_iteration(False)
if loop_run > 1:
self.experiment_script_statusbar_label.set_text("Waiting for other experiment to finish")
loop_run = 0
loop_run += interval
+
+ if self.stop_experiment_flag.isSet():
+ #Experiment has been stopped, clean up and leave
+ self.experiment_script_statusbar_label.set_text("Experiment stopped")
+ self.state = DamarisGUI.Edit_State
+ self.sw.enable_editing( )
+ self.toolbar_run_button.set_sensitive( True )
+ self.toolbar_stop_button.set_sensitive( False )
+ self.toolbar_pause_button.set_sensitive( False )
+ self.toolbar_pause_button.set_active( False )
+ #delete experiment from queue so that next experiment may start
+ self.lockfile.del_experiment(self.id)
+ return
# start experiment
try:
@@ -504,6 +528,8 @@ class DamarisGUI:
self.toolbar_stop_button.set_sensitive( False )
self.toolbar_pause_button.set_sensitive( False )
self.toolbar_pause_button.set_active( False )
+ #delete Experiment from queue
+ self.lockfile.del_experiment(self.id)
return
# switch to grapics
@@ -541,6 +567,33 @@ class DamarisGUI:
r = self.si.data.get( "__recentresult", -1 ) + 1
b = self.si.data.get( "__resultsinadvance", -1 ) + 1
e = self.si.data.get( "__recentexperiment", -1 ) + 1
+
+ experimentstarttime = self.si.data.get( "__experimentstarted", 0 )
+ expectedexperimentruntime = self.si.data.get( "__totalexperimentlength", 0 )
+ experimentsfinishedtime = 0.0
+
+ for i in range(0, b):
+ experimentsfinishedtime += self.si.data.get("__experimentlengths", {}).get(i, 0.0)
+
+ experimentruntime = time.time() - experimentstarttime
+
+ experimenttimetext = ""
+
+ if experimentstarttime > 0 and expectedexperimentruntime > 0:
+ runtimemin, runtimesec = divmod(math.floor(experimentruntime), 60)
+ runtimehours, runtimemin = divmod(runtimemin, 60)
+ experimenttimetext += " ({:02d}:{:02d}:{:02d} / ".format(int(runtimehours), int(runtimemin), int(runtimesec))
+ expectedmin, expectedsec = divmod(math.ceil(expectedexperimentruntime), 60)
+ expectedhours, expectedmin = divmod(expectedmin, 60)
+ experimenttimetext += "{:02d}:{:02d}:{:02d})".format(int(expectedhours), int(expectedmin), int(expectedsec))
+
+ backendtimetext = ""
+
+ if experimentsfinishedtime > 0:
+ finexperimmin, finexperimsec = divmod(round(experimentsfinishedtime), 60)
+ finexperimhours, finexperimmin = divmod(finexperimmin, 60)
+ backendtimetext = " ({:02d}:{:02d}:{:02d})".format(int(finexperimhours), int(finexperimmin), int(finexperimsec))
+
e_text = None
r_text = None
b_text = None
@@ -560,6 +613,7 @@ class DamarisGUI:
else:
#print self.si.back_driver.get_messages()
e_text = "Experiment Script Running (%d)" % e
+ e_text += experimenttimetext
if self.si.res_handling is not None:
if not self.si.res_handling.isAlive( ):
@@ -588,6 +642,7 @@ class DamarisGUI:
b_text = "Backend Running"
if b != 0:
b_text += " (%d)" % b
+ b_text += backendtimetext
if self.dump_thread is not None:
if self.dump_thread.isAlive( ):
@@ -656,7 +711,7 @@ class DamarisGUI:
# keep data to display but throw away everything else
self.si = None
- # delete locak file so that other experiment can start
+ # delete lock file so that other experiment can start
self.lockfile.del_experiment(self.id)
return False
@@ -904,6 +959,7 @@ class DamarisGUI:
def stop_experiment( self, widget, data=None ):
if self.state in [ DamarisGUI.Run_State, DamarisGUI.Pause_State ]:
+ self.stop_experiment_flag.set()
if self.si is None:
return
still_running = filter( None, [ self.si.exp_handling, self.si.res_handling, self.si.back_driver ] )
@@ -990,7 +1046,8 @@ class DamarisGUI:
# check generic debian location
self.doc_urls[ "Python DAMARIS" ] = "file:///usr/share/doc/python-damaris/html/index.html"
else:
- self.doc_urls[ "Python DAMARIS" ] = "https://element.fkp.physik.tu-darmstadt.de/damaris_cms/index.php?id=a-not-so-short-tutorial-on-damaris"
+
+ self.doc_urls[ "Python DAMARIS" ] = "https://element.fkp.physik.tu-darmstadt.de/damaris_cms/index.php?id=documentation"
self.doc_browser = None
@@ -1060,10 +1117,12 @@ class LogWindow:
writes messages to the log window
"""
- def __init__( self, xml_gui ):
+ def __init__( self, xml_gui, damaris_gui ):
self.xml_gui = xml_gui
+ self.damaris_gui = damaris_gui
self.textview = self.xml_gui.get_widget( "messages_textview" )
+ self.textview.connect( "key-press-event", self.textview_keypress)
self.textbuffer = self.textview.get_buffer( )
self.logstream = log
self.logstream.gui_log = self
@@ -1086,6 +1145,14 @@ class LogWindow:
self.textbuffer.insert_at_cursor( date_tag + unicode( message ) )
self.textview.scroll_to_mark( self.textbuffer.get_insert( ), 0.1 )
gtk.gdk.threads_leave( )
+
+ def textview_keypress( self, widget, event, data=None ):
+ if event.state & gtk.gdk.CONTROL_MASK != 0:
+ if event.keyval == gtk.gdk.keyval_from_name("f"):
+ self.damaris_gui.sw.search(None, None)
+ return True
+
+ return False
def __del__( self ):
self.logstream.gui_log = None
@@ -1125,13 +1192,22 @@ class ScriptWidgets:
self.data_handling_textbuffer.set_language(langpython)
self.data_handling_textbuffer.set_highlight_syntax(True)
+ fontdesc = pango.FontDescription("monospace")
self.experiment_script_textview.set_buffer( self.experiment_script_textbuffer )
self.experiment_script_textview.set_show_line_numbers(True)
self.experiment_script_textview.set_auto_indent(True)
+ self.experiment_script_textview.set_insert_spaces_instead_of_tabs(True)
+ self.experiment_script_textview.set_tab_width(4)
+ self.experiment_script_textview.set_smart_home_end(True)
+ self.experiment_script_textview.modify_font(fontdesc)
self.data_handling_textview.set_buffer( self.data_handling_textbuffer )
self.data_handling_textview.set_show_line_numbers(True)
self.data_handling_textview.set_auto_indent(True)
+ self.data_handling_textview.set_insert_spaces_instead_of_tabs(True)
+ self.data_handling_textview.set_tab_width(4)
+ self.data_handling_textview.set_smart_home_end(True)
+ self.data_handling_textview.modify_font(fontdesc)
keywords = """for if else elif in print try finally except global lambda not or pass def
class import from as return yield while continue break assert None True False AccumulatedValue
@@ -1455,10 +1531,62 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda
elif event.keyval == gtk.gdk.keyval_from_name("f"):
self.search(None, None)
return True
- return 0
+ return False
+
+ #Handle Backspace (delete more than one space on line beginning)
+ if event.keyval == 0xFF08:
+ textbuffer = widget.get_buffer( )
+
+ if textbuffer.get_has_selection():
+ return False
+
+ cursor_mark = textbuffer.get_insert( )
+ cursor_iter = textbuffer.get_iter_at_mark( cursor_mark )
+
+ linestart_iter = cursor_iter.copy( )
+ linestart_iter.set_line_offset( 0 )
+
+ linebegin = textbuffer.get_text( linestart_iter, cursor_iter ).expandtabs(4)
+
+ if linebegin.isspace() and len(linebegin) > 0:
+ linebegin = u' ' * int((len( linebegin ) - 1) / 4) * 4
+ textbuffer.delete( linestart_iter, cursor_iter )
+ textbuffer.insert( linestart_iter, linebegin )
+ return True
+
+ elif event.keyval == 0xFF0D:
+ textbuffer = widget.get_buffer( )
+
+ if textbuffer.get_has_selection():
+ return False
+
+ cursor_mark = textbuffer.get_insert( )
+ cursor_iter = textbuffer.get_iter_at_mark( cursor_mark )
+
+ lastchar_iter = cursor_iter.copy()
+ lastchar = lastchar_iter.backward_char( )
+
+ if not lastchar_iter.get_char( ) == u":":
+ return False
+
+ linestart_iter = cursor_iter.copy( )
+ linestart_iter.set_line_offset( 0 )
+ spaceend_iter = linestart_iter.copy( )
+ while (not spaceend_iter.ends_line( ) and not spaceend_iter.is_end( ) and spaceend_iter.get_char( ).isspace( )):
+ spaceend_iter.forward_char( )
+ linebegin = textbuffer.get_text( linestart_iter, spaceend_iter ).expandtabs(4)
+ indent_length = int((int(len(linebegin)/4)+1)*4)
+
+ intext = u"\n" + (u" " * indent_length)
+
+ textbuffer.insert(cursor_iter, intext)
+ widget.scroll_to_mark( cursor_mark, 0.0, 0 )
+
+ return True
+
#self.textviews_moved(widget)
- return 0
+ return False
def load_file_as_unicode( self, script_filename ):
script_file = file( script_filename, "rU" )
@@ -3209,7 +3337,7 @@ class ScriptInterface:
self.exp_writer = self.res_reader = self.back_driver = None
if self.backend_executable is not None and self.backend_executable != "":
self.back_driver = BackendDriver.BackendDriver( self.backend_executable, spool_dir, clear_jobs,
- clear_results)
+ clear_results )
if self.exp_script:
self.exp_writer = self.back_driver.get_exp_writer( )
if self.res_script:
@@ -3229,6 +3357,7 @@ class ScriptInterface:
self.data = DataPool( )
def runScripts( self ):
+
try:
# get script engines
self.exp_handling = self.res_handling = None
diff --git a/src/gui/ExperimentHandling.py b/src/gui/ExperimentHandling.py
index 5f40d44..0ee5100 100644
--- a/src/gui/ExperimentHandling.py
+++ b/src/gui/ExperimentHandling.py
@@ -66,6 +66,7 @@ class ExperimentHandling(threading.Thread):
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:
@@ -86,6 +87,21 @@ class ExperimentHandling(threading.Thread):
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