-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MxMap error when running the same scan twice in a thread or process #1
Comments
Here's a not so minimal example that recreates the problem: #! /usr/bin/env python
# coding: utf-8
#
# Project: BioCAT staff beamline control software (CATCON)
# https://github.com/biocatiit/beamline-control-staff
#
#
# Principal author: Jesse Hopkins
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function, unicode_literals
from builtins import object, range, map
from io import open
import queue
import tempfile
import os
import math
import threading
import time
import multiprocessing
import wx
import matplotlib
matplotlib.rcParams['backend'] = 'WxAgg'
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
import matplotlib.gridspec as gridspec
def get_mxdir():
"""Gets the top level install directory for MX."""
try:
mxdir = os.environ["MXDIR"]
except:
mxdir = "/opt/mx" # This is the default location.
return mxdir
def get_mpdir():
"""Construct the name of the Mp modules directory."""
mxdir = get_mxdir()
mp_modules_dir = os.path.join(mxdir, "lib", "mp")
mp_modules_dir = os.path.normpath(mp_modules_dir)
return mp_modules_dir
def set_mppath():
"""Puts the mp directory in the system path, if it isn't already."""
path = os.environ['PATH']
mp_dir = get_mpdir()
if mp_dir not in path:
os.environ["PATH"] = mp_dir+os.pathsep+os.environ["PATH"]
set_mppath()
import Mp as mp
def file_follow(the_file, stop_event):
"""
This function follows a file that is continuously being written to and
provides a generator that gives each new line written into the file.
Modified from: http://www.dabeaz.com/generators/follow.py
:param file the_file: The file object to read lines from.
:param threading.Event stop_event: A stop event that will end the generator,
allowing any loops iterating on the generator to exit.
"""
while True:
if stop_event.is_set():
break
line = the_file.readline()
if not line:
time.sleep(0.001)
continue
yield line
class ScanProcess(multiprocessing.Process):
"""
This is a separate Process (as opposed to Thread) that runs the ``Mp``
scan. It has to be a Process because even in a new Thread the scan
eats all processing resources and essentially locks the GUI while it's
running.
"""
def __init__(self, command_queue, return_queue, abort_event):
"""
Initializes the Process.
:param multiprocessing.Manager.Queue command_queue: This queue is used
to pass commands to the scan process.
:param multiprocessing.Manager.Queue return_queue: This queue is used
to return values from the scan process.
:param multiprocessing.Manager.Event abort_event: This event is set when
a scan needs to be aborted.
"""
multiprocessing.Process.__init__(self)
self.daemon = True
self.command_queue = command_queue
self.return_queue = return_queue
self._abort_event = abort_event
self._stop_event = multiprocessing.Event()
mp.set_user_interrupt_function(self._stop_scan)
self._commands = {'start_mxdb' : self._start_mxdb,
'set_scan_params' : self._set_scan_params,
'scan' : self._scan,
}
def run(self):
"""
Runs the process. It waits for commands to show up in the command_queue,
and then runs them. It is aborted if the abort_event is set. It is stopped
when the stop_event is set, and that allows the process to end gracefully.
"""
while True:
try:
cmd, args, kwargs = self.command_queue.get_nowait()
except queue.Empty:
cmd = None
if self._abort_event.is_set():
self._abort()
cmd = None
if self._stop_event.is_set():
self._abort()
break
if cmd is not None:
try:
self._commands[cmd](*args, **kwargs)
except Exception as e:
print(e)
if self._stop_event.is_set():
self._stop_event.clear()
else:
self._abort()
def _start_mxdb(self, db_path):
"""
Starts the MX database
:param str db_path: The path to the MX database.
"""
self.db_path = db_path
self.mx_database = mp.setup_database(self.db_path)
self.mx_database.set_plot_enable(2)
def _set_scan_params(self, device, start, stop, step, scalers,
dwell_time, timer, detector=None, file_name=None, dir_path=None):
"""
Sets the parameters for the scan.
:param str device: The MX record name.
:param float start: The absolute start position of the scan.
:param float stop: The absolute stop position of the scan.
:param float step: The step size of the scan.
:param list scalers: A list of the scalers for the scan.
:param float dwell_time: The count time at each point in the scan.
:param str timer: The name of the timer to be used for the scan.
:param str detector: Currently not used. The name of the detector
to be used for the scan.
:param str file_name: The scan name (and output name) for the scan.
Currently not used.
:param str dir_path: The directory path where the scan file will be
saved. Currently not used.
"""
self.out_path = dir_path
self.out_name = file_name
self.device = device
self.start = start
self.stop = stop
self.step = step
self.scalers = scalers
self.dwell_time = dwell_time
self.timer = timer
self.detector = detector
def _scan(self):
"""
Constructs and MX scan record and then carries out the scan. It also
communicates with the :mod:`ScanPanel` to send the filename for live
plotting of the scan.
"""
all_names = [r.name for r in self.mx_database.get_all_records()]
if self.out_name is not None:
scan_name = self.out_name
else:
scan_name = self.device
i=1
while scan_name in all_names:
if i == 1:
scan_name = "{}_{}".format(scan_name, str(i).zfill(2))
else:
scan_name = "{}_{}".format(scan_name[:-2], str(i).zfill(2))
i=i+1
description = ('{} scan linear_scan motor_scan "" "" '.format(scan_name))
num_scans = 1
num_motors = 1
num_independent_variables = num_motors
description = description + ("{} {} {} {} ".format(num_scans,
num_independent_variables, num_motors, str(self.device)))
scalers_detector = list(self.scalers)
if self.detector is not None:
scalers_detector.append(self.detector['name'])
description = description + ("{} ".format(len(scalers_detector)))
for j in range(len(scalers_detector)):
description = description + ("{} ".format(scalers_detector[j]))
scan_flags = 0x0
settling_time = 0.0
measurement_type = "preset_time"
measurement_time = self.dwell_time
description = description + (
'%x %f %s "%f %s" ' % (scan_flags, settling_time, measurement_type, measurement_time, self.timer))
if self.out_path is None:
standard_paths = wx.StandardPaths.Get()
tmpdir = standard_paths.GetTempDir()
fname = tempfile.NamedTemporaryFile(dir=tmpdir).name
fname=os.path.normpath('./{}'.format(os.path.split(fname)[-1]))
else:
fname = os.path.join(self.out_path, self.out_name)
datafile_description = "sff"
datafile_name = fname
plot_description = "none"
plot_arguments = "$f[0]"
description = description + (
"%s %s %s %s " % (datafile_description, datafile_name, plot_description, plot_arguments))
description = description + ("{} {} ".format(self.start, self.step))
num_measurements = int(abs(math.floor((self.stop - self.start)/self.step)))+1
description = description + ("{} ".format(num_measurements))
self.mx_database.create_record_from_description(description)
scan = self.mx_database.get_record(scan_name)
scan.finish_record_initialization()
self.return_queue.put_nowait([datafile_name])
scan.perform_scan()
self.return_queue.put_nowait(['stop_live_plotting'])
def _abort(self):
"""Clears the ``command_queue`` and aborts all current actions."""
while True:
try:
self.command_queue.get_nowait()
except queue.Empty:
break
self._abort_event.clear()
def stop_thread(self):
"""Stops the thread cleanly."""
self._stop_event.set()
def _stop_scan(self):
"""
This function is used Mp to abort the scan.
:returns: Returns 1 if the abort event is set, and that causes Mp to
abort the running scan. Returns 0 otherwise, which doesn't abort
anything.
:rtype: int
"""
return int(self._abort_event.is_set())
class ScanThread(threading.Thread):
"""
This is a separate Thread that runs the ``Mp``
scan.
"""
def __init__(self, command_queue, return_queue, abort_event):
"""
Initializes the thread.
"""
threading.Thread.__init__(self)
self.daemon = True
self.command_queue = command_queue
self.return_queue = return_queue
self._abort_event = abort_event
self._stop_event = threading.Event()
mp.set_user_interrupt_function(self._stop_scan)
self._commands = {'start_mxdb' : self._start_mxdb,
'set_scan_params' : self._set_scan_params,
'scan' : self._scan,
}
def run(self):
"""
Runs the thread. It waits for commands to show up in the command_queue,
and then runs them. It is aborted if the abort_event is set. It is stopped
when the stop_event is set, and that allows the process to end gracefully.
"""
while True:
try:
cmd, args, kwargs = self.command_queue.get_nowait()
except queue.Empty:
cmd = None
if self._abort_event.is_set():
self._abort()
cmd = None
if self._stop_event.is_set():
self._abort()
break
if cmd is not None:
try:
self._commands[cmd](*args, **kwargs)
except Exception as e:
print(e)
if self._stop_event.is_set():
self._stop_event.clear()
else:
self._abort()
def _start_mxdb(self, db_path):
"""
Starts the MX database
:param str db_path: The path to the MX database.
"""
self.db_path = db_path
self.mx_database = mp.setup_database(self.db_path)
self.mx_database.set_plot_enable(2)
def _set_scan_params(self, device, start, stop, step, scalers,
dwell_time, timer, detector=None, file_name=None, dir_path=None):
"""
Sets the parameters for the scan.
:param str device: The MX record name.
:param float start: The absolute start position of the scan.
:param float stop: The absolute stop position of the scan.
:param float step: The step size of the scan.
:param list scalers: A list of the scalers for the scan.
:param float dwell_time: The count time at each point in the scan.
:param str timer: The name of the timer to be used for the scan.
:param str detector: Currently not used. The name of the detector
to be used for the scan.
:param str file_name: The scan name (and output name) for the scan.
Currently not used.
:param str dir_path: The directory path where the scan file will be
saved. Currently not used.
"""
self.out_path = dir_path
self.out_name = file_name
self.device = device
self.start = start
self.stop = stop
self.step = step
self.scalers = scalers
self.dwell_time = dwell_time
self.timer = timer
self.detector = detector
def _scan(self):
"""
Constructs and MX scan record and then carries out the scan. It also
communicates with the :mod:`ScanPanel` to send the filename for live
plotting of the scan.
"""
all_names = [r.name for r in self.mx_database.get_all_records()]
if self.out_name is not None:
scan_name = self.out_name
else:
scan_name = self.device
i=1
while scan_name in all_names:
if i == 1:
scan_name = "{}_{}".format(scan_name, str(i).zfill(2))
else:
scan_name = "{}_{}".format(scan_name[:-2], str(i).zfill(2))
i=i+1
description = ('{} scan linear_scan motor_scan "" "" '.format(scan_name))
num_scans = 1
num_motors = 1
num_independent_variables = num_motors
description = description + ("{} {} {} {} ".format(num_scans,
num_independent_variables, num_motors, str(self.device)))
scalers_detector = list(self.scalers)
if self.detector is not None:
scalers_detector.append(self.detector['name'])
description = description + ("{} ".format(len(scalers_detector)))
for j in range(len(scalers_detector)):
description = description + ("{} ".format(scalers_detector[j]))
scan_flags = 0x0
settling_time = 0.0
measurement_type = "preset_time"
measurement_time = self.dwell_time
description = description + (
'%x %f %s "%f %s" ' % (scan_flags, settling_time, measurement_type, measurement_time, self.timer))
if self.out_path is None:
standard_paths = wx.StandardPaths.Get()
tmpdir = standard_paths.GetTempDir()
fname = tempfile.NamedTemporaryFile(dir=tmpdir).name
fname=os.path.normpath('./{}'.format(os.path.split(fname)[-1]))
else:
fname = os.path.join(self.out_path, self.out_name)
datafile_description = "sff"
datafile_name = fname
plot_description = "none"
plot_arguments = "$f[0]"
description = description + (
"%s %s %s %s " % (datafile_description, datafile_name, plot_description, plot_arguments))
description = description + ("{} {} ".format(self.start, self.step))
num_measurements = int(abs(math.floor((self.stop - self.start)/self.step)))+1
description = description + ("{} ".format(num_measurements))
self.mx_database.create_record_from_description(description)
scan = self.mx_database.get_record(scan_name)
scan.finish_record_initialization()
self.return_queue.put_nowait([datafile_name])
scan.perform_scan()
self.return_queue.put_nowait(['stop_live_plotting'])
def _abort(self):
"""Clears the ``command_queue`` and aborts all current actions."""
while True:
try:
self.command_queue.get_nowait()
except queue.Empty:
break
self._abort_event.clear()
def stop_thread(self):
"""Stops the thread cleanly."""
self._stop_event.set()
def _stop_scan(self):
"""
This function is used Mp to abort the scan.
:returns: Returns 1 if the abort event is set, and that causes Mp to
abort the running scan. Returns 0 otherwise, which doesn't abort
anything.
:rtype: int
"""
return int(self._abort_event.is_set())
class ScanPanel(wx.Panel):
"""
This creates the scan panel with both scan controls and the live plot.
"""
def __init__(self, *args, **kwargs):
"""
Initializes the scan panel. Accepts the usual wx.Panel arguments
"""
wx.Panel.__init__(self, *args, **kwargs)
self.scalers = ['Io']
self.timer = 'timer1'
self.device_name = 'mtr1'
self.use_thread = False
if self.use_thread:
self.cmd_q = queue.Queue()
self.return_q = queue.Queue()
self.abort_event = threading.Event()
self.scan_proc = ScanThread(self.cmd_q, self.return_q, self.abort_event)
else:
self.manager = multiprocessing.Manager()
self.cmd_q = self.manager.Queue()
self.return_q = self.manager.Queue()
self.abort_event = self.manager.Event()
self.scan_proc = ScanProcess(self.cmd_q, self.return_q, self.abort_event)
self.scan_proc.start()
self.scan_timer = wx.Timer()
self.scan_timer.Bind(wx.EVT_TIMER, self._on_scantimer)
self.live_plt_evt = threading.Event()
self.Bind(wx.EVT_CLOSE, self._on_closewindow)
self.plt_line = None
self.plt_y = None
self.plt_x = None
self._create_layout()
self._start_scan_mxdb()
def _create_layout(self):
dname = wx.StaticText(self, label=self.device_name)
info_grid = wx.FlexGridSizer(rows=2, cols=2, vgap=5, hgap=5)
info_grid.Add(wx.StaticText(self, label='Device name:'))
info_grid.Add(dname)
info_sizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Info'),
wx.VERTICAL)
info_sizer.Add(info_grid, wx.EXPAND)
self.start = wx.TextCtrl(self, value='', size=(80, -1))
self.stop = wx.TextCtrl(self, value='', size=(80, -1))
self.step = wx.TextCtrl(self, value='', size=(80, -1))
self.count_time = wx.TextCtrl(self, value='0.1')
type_sizer =wx.BoxSizer(wx.HORIZONTAL)
type_sizer.Add(wx.StaticText(self, label='Scan type:'))
type_sizer.Add(wx.StaticText(self,label='Absolute'), border=5, flag=wx.LEFT)
mv_grid = wx.FlexGridSizer(rows=2, cols=3, vgap=5, hgap=5)
mv_grid.Add(wx.StaticText(self, label='Start'))
mv_grid.Add(wx.StaticText(self, label='Stop'))
mv_grid.Add(wx.StaticText(self, label='Step'))
mv_grid.Add(self.start)
mv_grid.Add(self.stop)
mv_grid.Add(self.step)
count_grid = wx.FlexGridSizer(rows=4, cols=2, vgap=5, hgap=5)
count_grid.Add(wx.StaticText(self, label='Count time (s):'))
count_grid.Add(self.count_time)
count_grid.AddGrowableCol(1)
self.start_btn = wx.Button(self, label='Start')
self.start_btn.Bind(wx.EVT_BUTTON, self._on_start)
self.stop_btn = wx.Button(self, label='Stop')
self.stop_btn.Bind(wx.EVT_BUTTON, self._on_stop)
self.stop_btn.Disable()
ctrl_btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
ctrl_btn_sizer.Add(self.start_btn)
ctrl_btn_sizer.Add(self.stop_btn, border=5, flag=wx.LEFT)
ctrl_sizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Scan Controls'),
wx.VERTICAL)
ctrl_sizer.Add(type_sizer)
ctrl_sizer.Add(mv_grid, border=5, flag=wx.EXPAND|wx.TOP)
ctrl_sizer.Add(count_grid, border=5, flag=wx.EXPAND|wx.TOP)
ctrl_sizer.Add(ctrl_btn_sizer, border=5, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.TOP)
scan_sizer = wx.BoxSizer(wx.VERTICAL)
scan_sizer.Add(info_sizer, flag=wx.EXPAND)
scan_sizer.Add(ctrl_sizer, flag=wx.EXPAND)
self.fig = matplotlib.figure.Figure()
self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
self.canvas.SetBackgroundColour('white')
self.toolbar = NavigationToolbar2WxAgg(self.canvas)
self.toolbar.Realize()
self.plt_gs = gridspec.GridSpec(1, 1)
self.plot = self.fig.add_subplot(self.plt_gs[0], title='Scan')
self.plot.set_ylabel('Scaler counts')
self.plot.set_xlabel('Position')
self.cid = self.canvas.mpl_connect('draw_event', self._ax_redraw)
plot_sizer = wx.BoxSizer(wx.VERTICAL)
plot_sizer.Add(self.canvas, 1, flag=wx.EXPAND)
plot_sizer.Add(self.toolbar, 0, flag=wx.EXPAND)
top_sizer = wx.BoxSizer(wx.HORIZONTAL)
top_sizer.Add(scan_sizer)
top_sizer.Add(plot_sizer, 1, flag=wx.EXPAND)
self.SetSizer(top_sizer)
def _on_start(self, evt):
"""
Called when the start scan button is pressed. It gets the scan
parameters and then puts the scan in the ``cmd_q``.
"""
self.start_btn.Disable()
self.stop_btn.Enable()
scan_params = self._get_params()
if scan_params is not None:
if scan_params['start'] < scan_params['stop']:
self.plot.set_xlim(scan_params['start'], scan_params['stop'])
else:
self.plot.set_xlim(scan_params['stop'], scan_params['start'])
self.scan_timer.Start(10)
self.cmd_q.put_nowait(['set_scan_params', [], scan_params])
self.cmd_q.put_nowait(['scan', [], {}])
def _on_stop(self, evt):
"""
Called when the stop button is pressed. Aborts the scan and live
plotting
"""
self.abort_event.set()
time.sleep(0.5) #Wait for the process to abort before trying to reload the db
self.return_q.put_nowait(['stop_live_plotting'])
def _get_params(self):
"""
Gets the scan parameters from the GUI and returns them.
:returns: A dictionary of the scan parameters.
:rtype: dict
"""
try:
start = float(self.start.GetValue())
stop = float(self.stop.GetValue())
if start < stop:
step = abs(float(self.step.GetValue()))
else:
step = -abs(float(self.step.GetValue()))
scan_params = {'device' : self.device_name,
'start' : start,
'stop' : stop,
'step' : step,
'scalers' : self.scalers,
'dwell_time' : float(self.count_time.GetValue()),
'timer' : self.timer,
'detector' : 'None'
}
except ValueError:
msg = 'All of start, stop, step, and count time must be numbers.'
wx.MessageBox(msg, "Failed to start scan", wx.OK)
return None
if scan_params['detector'] == 'None':
scan_params['detector'] = None
return scan_params
def _start_scan_mxdb(self):
"""Loads the mx database in the scan process"""
mxdir = get_mxdir()
database_filename = os.path.join(mxdir, "etc", "mxmotor.dat")
database_filename = os.path.normpath(database_filename)
self.cmd_q.put_nowait(['start_mxdb', [database_filename], {}])
def _on_scantimer(self, evt):
"""
Called while the scan is running. It starts the live plotting at the
start of the scan, and stops the live plotting at the end, and enables/disables
the start/stop buttons as appropriate.
"""
try:
scan_return = self.return_q.get_nowait()[0]
except queue.Empty:
scan_return = None
if scan_return is not None and scan_return != 'stop_live_plotting':
print('starting live plotting')
self.live_plt_evt.clear()
self.live_thread = threading.Thread(target=self.live_plot, args=(scan_return,))
self.live_thread.daemon = True
self.live_thread.start()
elif scan_return == 'stop_live_plotting':
self.scan_timer.Stop()
self.live_plt_evt.set()
#This is a hack
# self.scan_proc.stop_thread()
# if self.use_thread:
# self.scan_proc = ScanThread(self.cmd_q, self.return_q, self.abort_event)
# else:
# self.scan_proc = ScanProcess(self.cmd_q, self.return_q, self.abort_event)
# self.scan_proc.start()
# self._start_scan_mxdb()
self.start_btn.Enable()
self.stop_btn.Disable()
def _ax_redraw(self, widget=None):
"""Redraw plots on window resize event."""
self.background = self.canvas.copy_from_bbox(self.plot.bbox)
self.update_plot()
def _safe_draw(self):
"""A safe draw call that doesn 't endlessly recurse."""
self.canvas.mpl_disconnect(self.cid)
self.canvas.draw()
self.cid = self.canvas.mpl_connect('draw_event', self._ax_redraw)
def update_plot(self):
"""
Updates the plot.
"""
get_plt_bkg = False
if self.plt_line is None:
if (self.plt_x is not None and self.plt_y is not None and
len(self.plt_x) == len(self.plt_y)) and len(self.plt_x) > 0:
self.plt_line, = self.plot.plot(self.plt_x, self.plt_y, 'b-o', animated=True)
get_plt_bkg = True
if get_plt_bkg:
self._safe_draw()
self.background = self.canvas.copy_from_bbox(self.plot.bbox)
if self.plt_line is not None:
self.plt_line.set_xdata(self.plt_x)
self.plt_line.set_ydata(self.plt_y)
redraw = False
if self.plt_line is not None:
oldx = self.plot.get_xlim()
oldy = self.plot.get_ylim()
self.plot.relim()
self.plot.autoscale_view()
newx = self.plot.get_xlim()
newy = self.plot.get_ylim()
if newx != oldx or newy != oldy:
redraw = True
if redraw:
self.canvas.mpl_disconnect(self.cid)
self.canvas.draw()
self.cid = self.canvas.mpl_connect('draw_event', self._ax_redraw)
if self.plt_line is not None:
self.canvas.restore_region(self.background)
self.plot.draw_artist(self.plt_line)
self.canvas.blit(self.plot.bbox)
def live_plot(self, filename):
"""
This does the live plotting. It is intended to be run in its own
thread. It first clears all of the plot related variables and clears
the plot. It then enters a loop where it reads from the scan file
and plots the points as they come in, until the scan ends.
:param str filename: The filename of the scan file to live plot.
"""
print('live plot thread started')
if self.plt_line is not None:
wx.CallAfter(self.plt_line.remove)
self.plt_line = None
wx.Callafter(self.plt_pts.remove)
self.plt_pts = None
self.plt_x = []
self.plt_y = []
self.scan_header = ''
wx.CallAfter(self.update_plot) #Is this threadsafe?
wx.Yield()
if not os.path.exists(filename):
time.sleep(0.1)
with open(filename) as thefile:
print('opening scan output datafile')
data = file_follow(thefile, self.live_plt_evt)
for val in data:
print('got new scan value: %s' %(val))
if self.live_plt_evt.is_set():
break
if val.startswith('#'):
self.scan_header = self.scan_header + val
else:
x, y = val.strip().split()
self.plt_x.append(float(x))
self.plt_y.append(float(y))
wx.CallAfter(self.update_plot) #Is this threadsafe?
wx.Yield()
os.remove(filename)
def _on_closewindow(self, event):
"""
Called when the scan window is closed.
"""
self.scan_timer.Stop()
self.scan_proc.stop_thread()
while self.scan_proc.is_alive():
time.sleep(.01)
self.Destroy()
class ScanFrame(wx.Frame):
"""
A lightweight scan frame that holds the :mod:`ScanPanel`.
"""
def __init__(self, *args, **kwargs):
"""
Initializes the scan frame. Takes all the usual wx.Frame arguments.
"""
wx.Frame.__init__(self, *args, **kwargs)
self._create_layout()
self.Fit()
def _create_layout(self):
"""
Creates the layout, by calling mod:`ScanPanel`.
"""
scan_panel = ScanPanel(parent=self)
top_sizer = wx.BoxSizer(wx.HORIZONTAL)
top_sizer.Add(scan_panel, 1, wx.EXPAND)
self.SetSizer(top_sizer)
if __name__ == '__main__':
app = wx.App()
frame = ScanFrame(parent=None, title='Test Scan Control')
frame.Show()
app.MainLoop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Exception in thread Thread-1:
Traceback (most recent call last):
File "/home/biocat/miniconda2/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "/home/biocat/miniconda2/lib/python2.7/threading.py", line 754, in run
self.__target(*self.__args, **self.__kwargs)
File "mxmap/utils/Scanner.py", line 76, in mx_main_loop
self.performScan()
File "mxmap/utils/Scanner.py", line 90, in performScan
all_records = [r.name for r in self.mx_database.get_all_records()]
File "/opt/mx/lib/mp/Mp.py", line 570, in get_all_records
while ( current_record.name != list_head_name ):
AttributeError: 'list' object has no attribute 'name'
The text was updated successfully, but these errors were encountered: