예제 #1
0
class MADResultsManager(TreeManager):
    class Data(Enum):
        NAME, LABEL, ENERGY, EDGE, WAVELENGTH, FPP, FP, DIRECTORY = list(
            range(8))

    Types = [str, str, float, str, float, float, float, str]
    Columns = ColumnSpec(
        (Data.LABEL, 'Label', ColumnType.TEXT, '{}', True),
        (Data.ENERGY, 'Energy', ColumnType.NUMBER, '{:0.3f}', True),
        (Data.WAVELENGTH, "\u03BB", ColumnType.NUMBER, '{:0.4f}', True),
        (Data.FP, "f'", ColumnType.NUMBER, '{:0.1f}', True),
        (Data.FPP, 'f"', ColumnType.NUMBER, '{:0.1f}', True),
    )
    parent = Data.NAME
    run_info = Property(type=object)
    run_name = Property(type=str, default='')
    directory = Property(type=str, default='')

    def selection_changed(self, model, itr):
        if itr:
            self.props.directory = model[itr][self.Data.DIRECTORY.value]
            self.props.run_name = model[itr][self.Data.NAME.value]
            self.props.run_info = self.get_items(itr)

        else:
            self.props.directory = ''
            self.props.run_name = ''
            self.props.run_info = None

    def make_parent(self, row):
        parent_row = super(MADResultsManager, self).make_parent(row)
        parent_row[self.Data.ENERGY.value] = row[self.Data.EDGE.value]
        return parent_row
예제 #2
0
class Message(Object):

    user = Property(type=str, default='')
    message = Property(type=str, default='')
    date = Property(type=str, default='')
    avatar = Property(type=int, default=0)

    def __init__(self, user, avatar, message, date):
        super().__init__()
        self.props.user = user
        self.props.avatar = avatar
        self.props.message = message
        self.props.date = date

    def update(self, user, avatar, message, date):
        self.props.user = user
        self.props.avatar = avatar
        self.props.message = message
        self.props.date = date

    def get_info(self):
        return {
            'user': self.user,
            'avatar': self.avatar,
            'message': self.message,
            'date': self.date,
        }
예제 #3
0
class Diagnostic(Object):
    """
    Base class for diagnostics.
    """
    class State(Enum):
        GOOD, WARN, BAD, UNKNOWN, DISABLED = list(range(5))

    state = Property(type=object)
    message = Property(type=str, default='')

    def __init__(self, descr):
        super(Diagnostic, self).__init__()
        self.description = descr
        self.props.state = self.State.UNKNOWN
        Registry.subscribe(IDiagnostic, self)

    def __repr__(self):
        return "<{}:'{}', status:{}>".format(self.__class__.__name__,
                                             self.description,
                                             self.state[0].name)

    def update_status(self, status, msg):
        if status != self.state or msg != self.message:
            self.props.state = status
            self.props.message = msg
            if status != self.State.GOOD:
                logger.warning("{}: {}".format(self.description, msg))
예제 #4
0
class XRFResultsManager(TreeManager):
    class Data(Enum):
        SELECTED, SYMBOL, NAME, PERCENT, DIRECTORY = list(range(5))

    Types = [bool, str, str, float, str]
    Columns = ColumnSpec(
        (Data.SYMBOL, 'Symbol', ColumnType.TEXT, '{}', True),
        (Data.NAME, 'Element', ColumnType.TEXT, '{}', True),
        (Data.PERCENT, 'Amount', ColumnType.NUMBER, '{:0.1f} %', True),
        (Data.SELECTED, '', ColumnType.TOGGLE, '{}', False),
    )
    flat = True
    directory = Property(type=str, default='')

    def format_cell(self, column, renderer, model, itr, spec):
        super(XRFResultsManager, self).format_cell(column, renderer, model,
                                                   itr, spec)
        index = model.get_path(itr)[0]
        renderer.set_property("foreground", colors.Category.GOOG20[index % 20])

    def selection_changed(self, model, itr):
        if itr:
            self.props.directory = model[itr][self.Data.DIRECTORY.value]
        else:
            self.props.directory = ''
예제 #5
0
class GroupItem(Object):
    selected = Property(type=bool, default=False)
    name = Property(type=str, default="")
    changed = Property(type=object)

    def __init__(self, name, sample_model, items=()):
        super().__init__()
        self.props.name = name
        self.sample_model = sample_model
        self.items = {path: False for path in items}
        self.uuid = str(uuid.uuid4())
        self.notify_id = self.connect('notify::selected', self.on_selected)
        self.propagate = True

    def update_item(self, key, value):
        """propagate sample selection to whole group preventing cyclic propagation"""
        if key in self.items and self.items[key] != value:
            self.items[key] = value
            if self.propagate:
                selected = all(self.items.values())
                if self.props.selected != selected:
                    self.handler_block(
                        self.notify_id
                    )  # do not propagate if group was set/unset from item
                    self.props.selected = selected
                    self.handler_unblock(self.notify_id)

    def on_selected(self, obj, param):
        """Propagate group selection to individual samples preventing cyclic propagation"""
        changed = set()
        for sample in self.sample_model:
            can_change = (
                sample[SampleStore.Data.GROUP] == self.props.name
                and sample[SampleStore.Data.SELECTED] != self.props.selected)
            if can_change:
                self.items[sample[SampleStore.Data.UUID]] = self.props.selected
                valid_ports = [Port.GOOD, Port.UNKNOWN, Port.MOUNTED]
                if sample[SampleStore.Data.PORT] and sample[
                        SampleStore.Data.STATE] in valid_ports:
                    sample[SampleStore.Data.SELECTED] = self.props.selected
                    sample[
                        SampleStore.Data.PROGRESS] = SampleStore.Progress.NONE
                    changed.add(sample[SampleStore.Data.DATA]['id'])
        self.props.changed = changed

    def __str__(self):
        return '<Group: {}|{}>'.format(self.props.name, self.props.selected)
예제 #6
0
class BaseStorageRing(Device):
    """
    Base class for storage ring devices

    Signals:
        - **ready**: arg_types=(bool,), beam available state

    Properties:
        - **current**: float, stored current
        - **mode**:  int, operational mode
        - **state**: int, storage ring state
        - **message**: str, storage ring message
    """
    class Signals:
        ready = Signal("ready", arg_types=(bool, ))

    # Properties
    current = Property(type=float, default=0.0)
    mode = Property(type=int, default=0)
    state = Property(type=int, default=0)
    message = Property(type=str, default='')

    def __init__(self):
        super().__init__()

    def check_ready(self, *args, **kwargs):
        return True

    def beam_available(self):
        """
        Check beam availability
        """
        return self.get_state('ready')

    def wait_for_beam(self, timeout=60):
        """
        Wait for beam to become available

        :param timeout: timeout period
        """
        while not self.get_state('ready') and timeout > 0:
            time.sleep(0.05)
            timeout -= 0.05
        logger.warn('Timed out waiting for beam!')
예제 #7
0
파일: cryojet.py 프로젝트: michel4j/mxdc
class CryostatBase(Device):
    """
    Base class for all cryostat devices.  A cryostat maintains low temperatures at the sample position.

    Signals:
        - temp (float,): Sample temperature
        - level (float,): Cryogen level
        - sample (float,): Cryogen flow-rate
        - shield (float,): Shield flow-rate
    """
    class Positions(Enum):
        IN, OUT = range(2)

    class Signals:
        temp = Signal('temp', arg_types=(float, ))
        level = Signal('level', arg_types=(float, ))
        sample = Signal('sample', arg_types=(float, ))
        shield = Signal('shield', arg_types=(float, ))
        pos = Signal('position', arg_types=(object, ))

    # Properties
    temperature = Property(type=float, default=0.0)
    shield = Property(type=float, default=0.0)
    sample = Property(type=float, default=0.0)
    level = Property(type=float, default=0.0)

    def configure(self, temp=None, sample=None, shield=None, position=None):
        """
        Configure the Cryostat.

        :param temp: Set the target sample temperature
        :param sample: Set the sample flow rate
        :param shield: Set the shield flow rate
        :param position: If the cryostat set the position. Should be one of Positions.IN, Positions.OUT
        """

    def stop(self):
        """
        Stop the cryostat
        """

    def start(self):
        """
예제 #8
0
파일: settings.py 프로젝트: michel4j/mxdc
class Setting(Object):
    name = Property(type=str, default='')
    key = Property(type=str, default='')
    icon = Property(type=str, default='')
    info = Property(type=object)
    value = Property(type=str, default='')

    def __init__(self, key, icon, validator, kind='string'):
        super().__init__()
        self.props.icon = icon
        self.props.key = key
        self.kind = kind
        self.props.name = key.replace('-', ' ').title()
        self.props.info = settings.get_setting_properties(key)
        self.validator = validator

        self.props.value = settings.get_string(key)
        settings.Settings.bind(key, self, 'value', 0)

    def validate(self, value):
        return self.validator(value)
예제 #9
0
class XASResultsManager(TreeManager):
    class Data(Enum):
        NAME, EDGE, SCAN, TIME, X_PEAK, Y_PEAK, DIRECTORY = list(range(7))

    Types = [str, str, int, str, float, float, str]
    Columns = ColumnSpec(
        (Data.SCAN, 'Scan', ColumnType.TEXT, '{}', True),
        (Data.TIME, 'TIME', ColumnType.TEXT, '{}', True),
        (Data.X_PEAK, 'X-Peak', ColumnType.NUMBER, '{:0.3f}', True),
        (Data.Y_PEAK, 'Y-Peak', ColumnType.NUMBER, '{:0.1f}', True),
    )
    parent = Data.NAME
    directory = Property(type=str, default='')

    def make_parent(self, row):
        parent_row = super(XASResultsManager, self).make_parent(row)
        parent_row[self.Data.TIME.value] = row[self.Data.EDGE.value]
        return parent_row

    def selection_changed(self, model, itr):
        if itr:
            self.props.directory = model[itr][self.Data.DIRECTORY.value]
        else:
            self.props.directory = ''
예제 #10
0
파일: rastering.py 프로젝트: michel4j/mxdc
class RasterController(Object):
    class StateType:
        READY, ACTIVE, PAUSED = list(range(3))

    Fields = (
        FieldSpec('exposure', 'entry', '{:0.3g}', Validator.Float(0.001, 720, 0.5)),
        FieldSpec('resolution', 'entry','{:0.2g}', Validator.Float(0.5, 50, 2.0)),
        FieldSpec('width', 'entry', '{:0.0f}', Validator.Float(5., 500., 200.)),
        FieldSpec('height', 'entry', '{:0.0f}', Validator.Float(5., 500., 200.)),
        FieldSpec('frames', 'entry', '{:d}', Validator.Int(1, 100, 10)),
        FieldSpec('lines', 'entry', '{:d}', Validator.Int(1, 100, 10)),
        FieldSpec('angle', 'entry', '{:0.3g}', Validator.Float(-1e6, 1e6, 0.0)),
        FieldSpec('aperture', 'entry', '{:0.0f}', Validator.Float(-1e6, 1e6, 50.)),
    )

    state = Property(type=int, default=StateType.READY)

    def __init__(self, view, widget):
        super().__init__()
        self.view = view
        self.widget = widget

        # housekeeping
        self.start_time = 0
        self.pause_dialog = None
        self.results = {}

        self.form = RasterForm(
            widget, fields=self.Fields, prefix='raster', persist=True, disabled=('lines', 'frames')
        )

        self.beamline = Registry.get_utility(IBeamline)
        self.microscope = Registry.get_utility(IMicroscope)
        self.sample_store = Registry.get_utility(ISampleStore)
        self.collector = RasterCollector()
        self.manager = RasterResultsManager(self.view, colormap=self.microscope.props.grid_cmap)

        # signals
        self.microscope.connect('notify::grid-params', self.on_grid_changed)
        self.connect('notify::state', self.on_state_changed)
        self.beamline.aperture.connect('changed', self.on_aperture)
        self.beamline.goniometer.omega.connect('changed', self.on_angle)
        self.collector.connect('done', self.on_done)
        self.collector.connect('paused', self.on_pause)
        self.collector.connect('stopped', self.on_stopped)
        self.collector.connect('progress', self.on_progress)
        self.collector.connect('started', self.on_started)
        self.collector.connect('result', self.on_results)
        self.setup()

    def setup(self):
        self.view.props.activate_on_single_click = False
        self.widget.raster_start_btn.connect('clicked', self.start_raster)
        self.widget.raster_stop_btn.connect('clicked', self.stop_raster)
        self.view.connect('row-activated', self.on_result_activated)

    def start_raster(self, *args, **kwargs):
        if self.props.state == self.StateType.ACTIVE:
            self.widget.raster_progress_lbl.set_text("Pausing raster ...")
            self.collector.pause()
        elif self.props.state == self.StateType.PAUSED:
            self.widget.raster_progress_lbl.set_text("Resuming raster ...")
            self.collector.resume()
        elif self.props.state == self.StateType.READY:
            self.widget.raster_progress_lbl.set_text("Starting raster ...")
            params = {
                'name': datetime.now().strftime('%y%m%d-%H%M'),
                'uuid': str(uuid.uuid4()),
                'activity': 'raster',
                'energy': self.beamline.energy.get_position(),
                'delta': RASTER_DELTA,
                'attenuation': self.beamline.attenuator.get(),
            }
            params.update(self.microscope.grid_params)
            params.update(self.form.get_values())
            params.update({
                'distance': resol_to_dist(params['resolution'], self.beamline.detector.mm_size, params['energy']),
                'origin': self.beamline.goniometer.stage.get_xyz(),
            })

            params = datatools.update_for_sample(params, self.sample_store.get_current())
            self.collector.configure(params)
            self.collector.start()

    def stop_raster(self, *args, **kwargs):
        self.widget.raster_progress_lbl.set_text("Stopping raster ...")
        self.collector.stop()

    def on_aperture(self, aperture, value):
        self.form.set_value('aperture', value, propagate=True)

    def on_angle(self, omega, value):
        self.form.set_value('angle', value)

    def on_state_changed(self, obj, param):
        if self.props.state == self.StateType.ACTIVE:
            self.widget.raster_start_icon.set_from_icon_name(
                "media-playback-pause-symbolic", Gtk.IconSize.LARGE_TOOLBAR
            )
            self.widget.raster_stop_btn.set_sensitive(True)
            self.widget.raster_start_btn.set_sensitive(True)
            self.widget.raster_config_box.set_sensitive(False)
            self.view.set_sensitive(False)
        elif self.props.state == self.StateType.PAUSED:
            self.widget.raster_progress_lbl.set_text("Rastering paused!")
            self.widget.raster_start_icon.set_from_icon_name(
                "media-playback-start-symbolic", Gtk.IconSize.LARGE_TOOLBAR
            )
            self.widget.raster_stop_btn.set_sensitive(True)
            self.widget.raster_start_btn.set_sensitive(True)
            self.widget.raster_config_box.set_sensitive(False)
            self.view.set_sensitive(True)
        else:
            self.widget.raster_start_icon.set_from_icon_name(
                "media-playback-start-symbolic", Gtk.IconSize.LARGE_TOOLBAR
            )
            self.widget.raster_config_box.set_sensitive(True)
            self.widget.raster_start_btn.set_sensitive(True)
            self.widget.raster_stop_btn.set_sensitive(False)
            self.view.set_sensitive(True)

    def on_started(self, collector, config):
        self.start_time = time.time()
        self.props.state = self.StateType.ACTIVE
        logger.info("Rastering Started.")

        self.microscope.props.grid_xyz = config['grid']
        self.results[config['uuid']] = {
            'config': copy.deepcopy(config),
            'scores': {}
        }

        directory = config['directory']
        home_dir = misc.get_project_home()
        current_dir = directory.replace(home_dir, '~')
        self.widget.dsets_dir_fbk.set_text(current_dir)

    def on_done(self, obj, data):
        self.props.state = self.StateType.READY
        self.widget.raster_progress_lbl.set_text("Rastering Completed.")
        self.widget.raster_eta.set_text('--:--')
        self.widget.raster_pbar.set_fraction(1.0)

    def on_pause(self, obj, paused, reason):
        if paused:
            self.props.state = self.StateType.PAUSED
            if reason:
                # Build the dialog message
                self.pause_dialog = dialogs.make_dialog(
                    Gtk.MessageType.WARNING, 'Rastering Paused', reason,
                    buttons=(('OK', Gtk.ResponseType.OK),)
                )
                self.pause_dialog.run()
                self.pause_dialog.destroy()
                self.pause_dialog = None
        else:
            self.props.state = self.StateType.ACTIVE
            if self.pause_dialog:
                self.pause_dialog.destroy()
                self.pause_dialog = None

    def on_error(self, obj, reason):
        error_dialog = dialogs.make_dialog(
            Gtk.MessageType.WARNING, 'Rastering Error!', reason,
            buttons=(('OK', Gtk.ResponseType.OK),)
        )
        error_dialog.run()
        error_dialog.destroy()

    def on_stopped(self, obj, data):
        self.props.state = self.StateType.READY
        self.widget.raster_progress_lbl.set_text("Rastering Stopped.")
        self.widget.raster_eta.set_text('--:--')

    def on_progress(self, obj, fraction, message):
        used_time = time.time() - self.start_time
        remaining_time = (1 - fraction) * used_time / fraction
        eta_time = remaining_time
        self.widget.raster_eta.set_text('{:0>2.0f}:{:0>2.0f} ETA'.format(*divmod(eta_time, 60)))
        self.widget.raster_pbar.set_fraction(fraction)
        self.widget.raster_progress_lbl.set_text(message)

    def on_results(self, collector, frame, results):
        index = frame - 1   # frames are 1-based counting
        config = collector.config['params']
        try:
            score = misc.frame_score(results)
        except (KeyError, ValueError, AttributeError):
            score = 0.0
        self.microscope.add_grid_score(frame, score)
        x, y, z = self.microscope.props.grid_xyz[index]  # frames
        parent, child = self.manager.add_item({
            'name': config['name'],
            'angle': config['angle'],
            'cell': frame,
            'x_pos': x,
            'y_pos': y,
            'z_pos': z,
            'score': score,
            'color': score,
            'filename': results['filename'],
            'uuid': config['uuid'],
        })
        self.results[config['uuid']]['scores'][frame] = score
        if parent:
            self.view.expand_row(parent, False)
        self.view.scroll_to_cell(child, None, True, 0.5, 0.5)
        self.microscope.update_grid()

    def on_result_activated(self, view, path, column=None):
        itr = self.manager.model.get_iter(path)
        item = self.manager.get_item(itr)

        if self.manager.model.iter_has_child(itr):
            self.beamline.goniometer.omega.move_to(item['angle'], wait=True)
            grid = self.results[item['uuid']]
            self.microscope.load_grid(
                grid['grid'],
                {'origin': grid['config']['origin'], 'angle': grid['config']['angle']},
                grid['scores']
            )
            self.widget.dsets_dir_fbk.set_text(grid['config']['directory'])
        else:
            image_viewer = Registry.get_utility(IImageViewer)
            self.beamline.goniometer.stage.move_xyz(item['x_pos'], item['y_pos'], item['z_pos'])
            image_viewer.open_frame(item['filename'])

    def on_grid_changed(self, obj, param):
        params = self.microscope.grid_params
        if params is not None and 'width' in params and 'height' in params:
            self.form.set_values({'width': params['width'], 'height': params['height']}, propagate=True)
예제 #11
0
class SampleStore(Object):
    class Data(object):
        (SELECTED, NAME, GROUP, CONTAINER, PORT, LOCATION, BARCODE, MISMATCHED,
         PRIORITY, COMMENTS, STATE, CONTAINER_TYPE, PROGRESS, UUID, SORT_NAME,
         DATA) = list(range(16))
        TYPES = (bool, str, str, str, str, str, str, bool, int, str, int, str,
                 int, str, object, object)

    class Progress(object):
        NONE, PENDING, ACTIVE, DONE = list(range(4))

    Column = OrderedDict([
        (Data.SELECTED, ''),
        (Data.STATE, ''),
        (Data.NAME, 'Name'),
        (Data.GROUP, 'Group'),
        (Data.PORT, 'Port'),
        (Data.LOCATION, 'Container'),
        (Data.PRIORITY, 'Priority'),
    ])

    class Signals:
        updated = Signal("updated", arg_types=())

    # properties
    cache = Property(type=object)
    current_sample = Property(type=object)
    next_sample = Property(type=object)
    ports = Property(type=object)
    containers = Property(type=object)

    def __init__(self, view, widget):
        super().__init__()
        self.model = Gtk.ListStore(*self.Data.TYPES)
        self.search_model = self.model.filter_new()
        self.search_model.set_visible_func(self.search_data)
        self.sort_model = Gtk.TreeModelSort(model=self.search_model)
        self.group_model = Gio.ListStore(item_type=GroupItem)
        self.group_registry = {}
        self.prefetch_pending = False
        self.initializing = True

        # initialize properties
        self.props.next_sample = {}
        self.props.current_sample = {}
        self.props.ports = set()
        self.props.containers = set()

        cache = load_cache('samples')
        self.props.cache = set() if not cache else set(cache)

        self.filter_text = ''

        self.view = view
        self.widget = widget
        self.view.set_model(self.sort_model)

        self.beamline = Registry.get_utility(IBeamline)

        self.setup()
        self.model.connect('row-changed', self.on_sample_row_changed)
        self.widget.samples_selectall_btn.connect('clicked',
                                                  self.on_select_all)
        self.widget.samples_selectnone_btn.connect('clicked',
                                                   self.on_unselect_all)
        self.view.connect('key-press-event', self.on_key_press)
        self.widget.samples_reload_btn.connect('clicked',
                                               lambda x: self.import_mxlive())
        self.beamline.automounter.connect('sample', self.on_sample_mounted)
        self.beamline.automounter.connect('next-port', self.on_prefetched)
        self.beamline.automounter.connect('ports', self.update_states)
        self.widget.samples_mount_btn.connect('clicked',
                                              lambda x: self.mount_action())
        self.widget.samples_dismount_btn.connect(
            'clicked', lambda x: self.dismount_action())
        self.widget.samples_search_entry.connect('search-changed',
                                                 self.on_search)
        self.widget.mxdc_main.connect('realize', self.import_mxlive)
        self.connect('notify::cache', self.on_cache)
        self.connect('notify::current-sample', self.on_cur_changed)
        self.connect('notify::next-sample', self.on_next_changed)

        Registry.add_utility(ISampleStore, self)

    def get_current(self):
        return self.current_sample

    def setup(self):
        # Selected Column
        for data, title in list(self.Column.items()):
            if data == self.Data.SELECTED:
                renderer = Gtk.CellRendererToggle(activatable=True)
                renderer.connect('toggled', self.on_row_toggled,
                                 self.sort_model)
                column = Gtk.TreeViewColumn(title=title,
                                            cell_renderer=renderer,
                                            active=data)
                column.set_fixed_width(30)
            elif data == self.Data.STATE:
                renderer = Gtk.CellRendererText(text="\u25c9")
                column = Gtk.TreeViewColumn(title=title,
                                            cell_renderer=renderer)
                column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
                column.set_fixed_width(20)
                column.set_cell_data_func(renderer, self.format_state)
            else:
                renderer = Gtk.CellRendererText()
                column = Gtk.TreeViewColumn(title=title,
                                            cell_renderer=renderer,
                                            text=data)
                column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
                column.set_expand(True)
                if data in [
                        self.Data.NAME, self.Data.PORT, self.Data.GROUP,
                        self.Data.CONTAINER
                ]:
                    self.sort_model.set_sort_func(data, human_name_sort, data)

                column.set_sort_column_id(data)
                column.set_cell_data_func(renderer, self.format_progress)
                if data in [self.Data.NAME, self.Data.GROUP]:
                    column.set_resizable(True)

            column.set_clickable(True)
            column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
            self.view.append_column(column)

        self.selection = self.view.get_selection()
        self.selection.set_mode(Gtk.SelectionMode.MULTIPLE)
        self.view.props.activate_on_single_click = False
        self.view.connect('row-activated', self.on_row_activated)
        self.view.set_enable_search(True)
        self.view.set_search_entry(self.widget.samples_search_entry)
        self.view.set_search_column(self.Data.NAME)
        self.view.set_tooltip_column(self.Data.COMMENTS)
        self.view.set_search_column(self.Data.NAME)
        self.sort_model.set_sort_column_id(self.Data.PRIORITY,
                                           Gtk.SortType.ASCENDING)
        self.sort_model.connect('sort-column-changed',
                                lambda x: self.roll_next_sample())
        self.roll_next_sample()

        self.widget.auto_groups_box.bind_model(self.group_model,
                                               self.create_group_selector)

        self.sample_dewar = DewarController(self.widget, self)
        self.sample_dewar.connect('selected', self.on_dewar_selected)

    def load_data(self, data):
        self.clear()
        groups = defaultdict(list)
        for item in data:
            key = self.add_item(item)
            groups[item['group']].append(key)
        for name, samples in list(groups.items()):
            group_item = GroupItem(name, self.model, items=samples)
            group_item.connect('notify::changed', self.on_group_changed)
            self.group_model.append(group_item)
            self.group_registry[name] = group_item
        self.emit('updated')
        self.props.containers = self.containers

    def add_item(self, item):
        item['uuid'] = str(uuid.uuid4())
        if not (item.get('port')
                and self.beamline.automounter.is_valid(item.get('port'))):
            item['port'] = ''
            state = Port.UNKNOWN
        else:
            ports = self.beamline.automounter.get_state('ports')
            state = ports.get(item['port'], Port.UNKNOWN)
            state = state if state in [Port.BAD, Port.MOUNTED, Port.EMPTY
                                       ] else Port.GOOD
        self.model.append([
            item['id'] in self.cache,
            item.get('name', 'unknown'),
            item.get('group', ''),
            item.get('container', ''),
            item.get('port', ''),
            item.get('container'),
            item.get('barcode', ''),
            False,  # not mismatched
            item.get('priority', 0),
            item.get('comments', ''),
            state,
            item.get('container_type', ''),
            self.Progress.NONE,
            item['uuid'],
            re.split(r'(\d+)', item.get('name', 'unknown')),
            item
        ])

        if item.get('port'):
            self.props.ports.add(item['port'])
            container_location = item['port'].rsplit(item['location'], 1)[0]
            self.props.containers.add(container_location)
        return item['uuid']

    def create_group_selector(self, item):
        btn = Gtk.CheckButton(item.props.name)
        btn.set_active(item.selected)
        btn.connect('toggled', self.on_group_btn_toggled, item)
        item.connect('notify::selected', self.on_group_item_toggled, btn)
        return btn

    @async_call
    def schedule_prefetch(self):
        port = self.next_sample.get('port')
        cur = self.current_sample.get('port')
        if port and not self.prefetch_pending and port != cur:
            self.prefetch_pending = True
            self.beamline.automounter.prefetch(port, wait=True)
            self.prefetch_pending = False

    def on_group_btn_toggled(self, btn, item):
        if item.props.selected != btn.get_active():
            item.props.selected = btn.get_active()

    def on_group_item_toggled(self, item, param, btn):
        if item.props.selected != btn.get_active():
            btn.set_active(item.props.selected)

    def on_group_changed(self, item, *args, **kwargs):
        if item.selected:
            cache = self.cache | item.changed
        else:
            cache = self.cache - item.changed
        self.props.cache = cache

    def on_search(self, obj):
        self.filter_text = obj.get_text()
        self.search_model.refilter()

    def on_cache(self, *args, **kwargs):
        save_cache(list(self.props.cache), 'samples')

    def on_next_changed(self, *args, **kwargs):
        self.widget.samples_next_sample.set_text(
            self.next_sample.get('name', '...'))
        port = self.next_sample.get('port', '...') or '<manual>'
        self.widget.samples_next_port.set_text(port)
        self.widget.samples_mount_btn.set_sensitive(bool(self.next_sample))

        # Only prefetch if there is a sample currently mounted
        if port not in ['...', '<manual>', None
                        ] and self.beamline.automounter.is_mounted():
            self.schedule_prefetch()

    def on_prefetched(self, obj, port):
        name_style = self.widget.samples_next_sample.get_style_context()
        port_style = self.widget.samples_next_port.get_style_context()
        if port:
            name_style.add_class('prefetched')
            port_style.add_class('prefetched')
            row = self.find_by_port(port)
            if row:
                self.props.next_sample = row[self.Data.DATA]
            else:
                self.props.next_sample = {'port': port}
        else:
            name_style.remove_class('prefetched')
            port_style.remove_class('prefetched')

    def on_cur_changed(self, *args, **kwargs):
        self.widget.samples_cur_sample.set_text(
            self.current_sample.get('name', '...'))
        port = self.current_sample.get('port', '...') or '<manual>'
        self.widget.samples_cur_port.set_text(port)
        self.widget.samples_dismount_btn.set_sensitive(
            bool(self.current_sample))
        self.emit('updated')

    def search_data(self, model, itr, dat):
        """Test if the row is visible"""
        row = model[itr]
        search_text = " ".join([
            str(row[col]) for col in [
                self.Data.NAME, self.Data.GROUP, self.Data.CONTAINER,
                self.Data.PORT, self.Data.COMMENTS, self.Data.BARCODE
            ]
        ])
        return (not self.filter_text) or (self.filter_text in search_text)

    def import_mxlive(self, *args, **kwargs):
        data = self.beamline.lims.get_samples(self.beamline.name)
        if not 'error' in data:
            self.widget.notifier.notify(
                '{} Samples Imported from MxLIVE'.format(len(data)))
            self.load_data(data)
        else:
            self.widget.notifier.notify(data['error'])

    def format_state(self, column, cell, model, itr, data):
        value = model[itr][self.Data.STATE]
        loaded = model[itr][self.Data.PORT]
        mismatched = model[itr][self.Data.MISMATCHED]

        if not loaded:
            cell.set_property("text", "")
        elif mismatched:
            col = Gdk.RGBA(red=0.5, green=0.5, blue=0.0, alpha=1.0)
            cell.set_property("foreground-rgba", col)
            cell.set_property("text", "\u2b24")
        elif value in [Port.EMPTY]:
            col = Gdk.RGBA(red=0.0, green=0.0, blue=0.0, alpha=0.5)
            cell.set_property("foreground-rgba", col)
            cell.set_property("text", "\u2b24")
        elif value in [Port.UNKNOWN]:
            col = Gdk.RGBA(red=0.0, green=0.0, blue=0.0, alpha=1.0)
            cell.set_property("foreground-rgba", col)
            cell.set_property("text", "\u25ef")
        else:
            col = Gdk.RGBA(**PortColors[value])
            cell.set_property("foreground-rgba", col)
            cell.set_property("text", "\u2b24")

    def format_progress(self, column, cell, model, itr, data):
        value = model.get_value(itr, self.Data.PROGRESS)
        if value == self.Progress.DONE:
            cell.set_property("style", Pango.Style.ITALIC)
        else:
            cell.set_property("style", Pango.Style.NORMAL)

    def roll_next_sample(self):
        items = self.get_selected()
        if items:
            self.props.next_sample = items[0]
        else:
            self.props.next_sample = {}

    def get_next(self):
        itr = self.sort_model.get_iter_first()
        while itr and not self.sort_model.get_value(itr, self.Data.SELECTED):
            itr = self.sort_model.iter_next(itr)
        if itr:
            return Gtk.TreeRowReference.new(self.sort_model,
                                            self.sort_model.get_path(itr))

    def find_by_port(self, port):
        for row in self.model:
            if row[self.Data.PORT] == port:
                return row

    def find_by_id(self, sample_id):
        for row in self.model:
            if row[self.Data.DATA].get('id') == sample_id:
                return row

    def get_name(self, port):
        row = self.find_by_port(port)
        if row:
            return row[self.Data.NAME]
        else:
            return '...'

    def get_selected(self):
        return [
            row[self.Data.DATA] for row in self.sort_model
            if row[self.Data.SELECTED]
            and row[self.Data.STATE] not in [Port.BAD, Port.EMPTY]
        ]

    def select_all(self, option=True):
        # toggle all selected rows otherwise toggle the whole list
        changed = set()
        for row in self.model:
            if row[self.Data.SELECTED] != option:
                row[self.Data.SELECTED] = option
                changed.add(row[self.Data.DATA]['id'])

        cache = self.props.cache
        if changed:
            if option:
                cache |= changed
            else:
                cache -= changed
            self.props.cache = cache

    def clear(self):
        self.props.ports = set()
        self.props.containers = set()
        self.model.clear()
        self.group_model.remove_all()
        self.group_registry = {}
        self.emit('updated')

    def toggle_row(self, path):
        path = self.sort_model.convert_path_to_child_path(path)
        row = self.search_model[path]
        if row[self.Data.STATE] not in [Port.BAD, Port.EMPTY]:
            selected = not row[self.Data.SELECTED]
            row[self.Data.SELECTED] = selected
            cache = self.cache
            if selected:
                row[self.Data.PROGRESS] = self.Progress.NONE
                cache.add(row[self.Data.DATA]['id'])
            else:
                cache.remove(row[self.Data.DATA]['id'])
            self.props.cache = cache

    def update_states(self, obj, ports):
        for row in self.model:
            port = row[self.Data.PORT]
            if port:
                state = ports.get(port, Port.UNKNOWN)
                state = state if state in [Port.BAD, Port.MOUNTED, Port.EMPTY
                                           ] else Port.GOOD
                row[self.Data.STATE] = state

    def on_sample_row_changed(self, model, path, itr):
        if self.group_registry:
            val = model[path][self.Data.SELECTED]
            group = model[path][self.Data.GROUP]
            key = model[path][self.Data.UUID]
            self.group_registry[group].update_item(key, val)

    def on_dewar_selected(self, obj, port):
        row = self.find_by_port(port)
        if row:
            self.props.next_sample = row[self.Data.DATA]
        else:
            self.props.next_sample = {'port': port}

    def on_select_all(self, obj, *args):
        try:
            self.select_all(True)
        except ValueError:
            pass

    def on_unselect_all(self, obj, *args):
        try:
            self.select_all(False)
        except ValueError:
            pass

    def on_sample_mounted(self, obj, sample):
        if sample:
            port = sample.get('port', '')
            row = self.find_by_port(port)
            if row:
                self.props.current_sample = row[self.Data.DATA]
                row[self.Data.SELECTED] = False
                self.widget.samples_dismount_btn.set_sensitive(True)
                if self.props.current_sample['barcode'] != sample.get(
                        'barcode'):
                    row[self.Data.MISMATCHED] = True
                else:
                    row[self.Data.MISMATCHED] = False
            elif self.beamline.is_admin():
                self.props.current_sample = {
                    'port': port,
                }
                self.widget.samples_dismount_btn.set_sensitive(True)
            else:
                self.props.current_sample = {
                    'port': port,
                }
                self.widget.samples_dismount_btn.set_sensitive(False)
        else:
            self.props.current_sample = {}

        self.widget.spinner.stop()

        if not self.initializing:
            self.roll_next_sample()
        self.initializing = False

    def on_key_press(self, obj, event):
        return self.widget.samples_search_entry.handle_event(event)

    def on_row_activated(self, cell, path, column):
        path = self.sort_model.convert_path_to_child_path(path)
        row = self.search_model[path]
        self.props.next_sample = row[self.Data.DATA]

    def on_row_toggled(self, cell, path, model):
        path = Gtk.TreePath.new_from_string(path)
        self.toggle_row(path)

    def mount_action(self):
        if not self.next_sample.get('port'):
            if self.current_sample:
                self.dismount_action()
                self.widget.notifier.notify(
                    'Switching from Automounter to Manual. Try again after '
                    'current sample is done dismounting!')
            else:
                self.widget.notifier.notify(
                    'Manual Mode: Please mount it manually before proceeding')
                self.props.current_sample = self.next_sample
                self.props.next_sample = {}
        elif self.next_sample and self.beamline.automounter.is_mountable(
                self.next_sample['port']):
            if self.current_sample and not self.current_sample.get('port'):
                self.widget.notifier.notify(
                    'Switching from Manual to Autmounter. Try again after '
                    'current sample is has been dismounted manually!')
            else:
                self.widget.spinner.start()
                auto.auto_mount(self.beamline, self.next_sample['port'])

    def dismount_action(self):
        if not self.current_sample.get('port'):
            self.widget.notifier.notify(
                'Sample was mounted manually. Please dismount it manually')
            item = self.find_by_id(self.current_sample.get('id'))
            item[self.Data.SELECTED] = False
            self.props.current_sample = {}
            self.roll_next_sample()
        elif self.current_sample and self.beamline.automounter.is_mounted(
                self.current_sample['port']):
            self.widget.spinner.start()
            auto.auto_dismount(self.beamline)
예제 #12
0
파일: cryojet.py 프로젝트: michel4j/mxdc
class CryoJetBase(Device):
    """
    Cryogenic Nozzle Jet Device

    """

    temperature = Property(type=float, default=0.0)
    shield = Property(type=float, default=0.0)
    sample = Property(type=float, default=0.0)
    level = Property(type=float, default=0.0)

    def __init__(self, *args, **kwargs):
        super().__init__()
        self.name = 'Cryojet'
        self._previous_flow = 7.0
        self.setup(*args, **kwargs)

    def setup(self, *args, **kwargs):
        pass

    def stop_flow(self):
        """
        Stop the flow of the cold nitrogen stream. The current setting for
        flow rate is saved.
        """
        self._previous_flow = self.sample_fbk.get()
        self.sample_sp.put(0.0)

    def resume_flow(self):
        """
        Restores the flow rate to the previously saved setting.
        """
        self.sample_sp.put(self._previous_flow)

    def on_temp(self, obj, val):
        if val < 110:
            self.set_state(health=(0, 'temp', ''))
        elif val < 115:
            self.set_state(health=(2, 'temp', 'Temp. high!'))
        else:
            self.set_state(health=(4, 'temp', 'Temp. too high!'))
        self.set_property('temperature', val)

    def on_sample(self, obj, val):
        if val > 5:
            self.set_state(health=(0, 'sample', ''))
        elif val > 4:
            self.set_state(health=(2, 'sample', 'Sample Flow Low!'))
        else:
            self.set_state(health=(4, 'sample', 'Sample Flow Too Low!'))
        self.set_property('sample', val)

    def on_shield(self, obj, val):
        if val > 5:
            self.set_state(health=(0, 'shield', ''))
        elif val > 4:
            self.set_state(health=(2, 'shield', 'Shield Flow Low!'))
        else:
            self.set_state(health=(4, 'shield', 'Shield Flow Too Low!'))
        self.set_property('shield', val)

    def on_level(self, obj, val):
        if val < 15:
            self.set_state(health=(4, 'cryo', 'Cryogen too low!'))
        elif val < 20:
            self.set_state(health=(2, 'cryo', 'Cryogen low!'))
        else:
            self.set_state(health=(0, 'cryo', ''))

        self.set_property('level', val)

    def on_nozzle(self, obj, val):
        if val:
            self.set_state(health=(1, 'nozzle', 'Retracted!'))
        else:
            self.set_state(health=(0, 'nozzle', 'Restored'))
예제 #13
0
파일: samples.py 프로젝트: michel4j/mxdc
class HutchSamplesController(Object):
    ports = Property(type=object)
    containers = Property(type=object)

    def __init__(self, widget):
        super().__init__()
        self.widget = widget
        self.props.ports = {}
        self.props.containers = {}

        self.beamline = Registry.get_utility(IBeamline)
        self.microscope = microscope.Microscope(self.widget)
        self.cryo_tool = cryo.CryoController(self.widget)
        self.sample_dewar = automounter.DewarController(self.widget, self)
        self.sample_dewar.connect('selected', self.on_dewar_selected)
        self.beamline.automounter.connect('sample', self.on_sample_mounted)

        self.setup()

    def setup(self):
        # create and pack devices into settings frame
        self.image_viewer = imageviewer.ImageViewer()
        self.widget.datasets_viewer_box.add(self.image_viewer)
        if self.beamline.is_admin():
            self.beamline.detector.connect('new-image', self.on_new_image)

    def on_new_image(self, obj, dataset):
        self.image_viewer.show_frame(dataset)

    def on_dewar_selected(self, obj, port):
        logger.info('Sample Selected: {}'.format(port))
        row = self.find_by_port(port)
        if row:
            self.next_sample = row[self.Data.DATA]
        elif port:
            self.next_sample = {'port': port}
        else:
            self.next_sample = None

        if self.next_sample:
            self.widget.samples_mount_btn.set_sensitive(True)
            self.widget.samples_next_port.set_text(port)
        else:
            self.widget.samples_mount_btn.set_sensitive(False)
            self.widget.samples_next_port.set_text("...")
        self.widget.samples_next_sample.set_text('...')

    def get_name(self, port):
        return '...'

    def find_by_port(self, port):
        return None

    def find_by_id(self, port):
        return None

    def on_sample_mounted(self, obj, sample):
        self.microscope.clear_objects()
        if sample and self.beamline.is_admin():
            self.widget.samples_cur_sample.set_text('...')
            port = sample.get('port')
            if port:
                self.widget.samples_cur_port.set_text(port)
                self.widget.samples_dismount_btn.set_sensitive(True)
예제 #14
0
파일: cryo.py 프로젝트: michel4j/mxdc
class CryoController(Object):
    anneal_active = Property(type=bool, default=False)
    anneal_time = Property(type=float, default=0.0)

    def __init__(self, widget):
        super().__init__()
        self.widget = widget
        self.beamline = Registry.get_utility(IBeamline)
        self.cryojet = self.beamline.cryojet
        self.stopped = True
        self.labels = {}
        self.limits = {}
        self.formats = {}
        self.setup()
        self._animation = GdkPixbuf.PixbufAnimation.new_from_resource("/org/mxdc/data/active_stop.gif")

    def setup(self):
        self.labels = {
            'temperature': self.widget.cryo_temp_fbk,
            'level': self.widget.cryo_level_fbk,
            'sample': self.widget.cryo_sample_fbk,
            'shield': self.widget.cryo_shield_fbk
        }
        self.formats = {
            'temperature': '{:0.0f}',
            'level': '{:0.0f}',
            'sample': '{:0.1f}',
            'shield': '{:0.1f}',
        }
        self.limits = {
            'temperature': (105, 110),
            'level': (25, 15),
            'sample': (5, 4),
            'shield': (5, 4),
        }
        self.cryojet.connect('notify', self.on_parameter_changed)
        self.nozzle_monitor = common.BoolMonitor(
            self.cryojet.nozzle, self.widget.cryo_nozzle_fbk,
            {True: 'OUT', False: 'IN'},
        )
        self.connect('notify::anneal-time', self.on_anneal_time)
        self.connect('notify::anneal-active', self.on_anneal_state)
        self.widget.anneal_btn.connect('clicked', self.on_anneal_action)

    def on_anneal_action(self, btn):
        if self.anneal_active:
            self.stop_annealing()
        else:
            val = self.widget.anneal_entry.get_text()
            try:
                t = float(val)
            except ValueError:
                pass
            else:
                self.props.anneal_time = t
                self.start_annealing()

    def on_parameter_changed(self, obj, param):
        if param.name in self.labels:
            val = obj.get_property(param.name)
            txt = self.formats[param.name].format(val)
            col = common.value_class(val, *self.limits[param.name])
            self.labels[param.name].set_text(txt)
            style = self.labels[param.name].get_style_context()
            for name in ['dev-error', 'dev-warning']:
                if col == name:
                    style.add_class(name)
                else:
                    style.remove_class(name)

    def on_anneal_state(self, *args, **kwargs):
        if self.props.anneal_active:
            self.widget.anneal_icon.set_from_animation(self._animation)
            self.widget.anneal_entry.set_sensitive(False)
        else:
            self.widget.anneal_icon.set_from_icon_name("media-playback-start-symbolic", Gtk.IconSize.BUTTON)
            self.widget.anneal_entry.set_sensitive(True)

    def on_anneal_time(self, *args, **kwargs):
        self.widget.anneal_entry.set_text('{:0.0f}'.format(self.props.anneal_time))

    def start_annealing(self):
        if self.props.anneal_time > 0:
            message = (
                "This procedure will stop the cold cryogen "
                "stream, \nwarming up your sample for {:0.0f} "
                "seconds. Are you sure?"
            ).format(self.props.anneal_time)
            response = dialogs.warning('Annealing may damage your sample!', message,
                                       buttons=(('Cancel', Gtk.ButtonsType.CANCEL), ('Proceed', Gtk.ButtonsType.OK)))
            if response == Gtk.ButtonsType.OK:
                self.stopped = False
                self.props.anneal_active = True
                duration = 100
                GLib.timeout_add(duration, self.monitor_annealing, duration)
                self.cryojet.stop_flow()

    def stop_annealing(self):
        self.cryojet.resume_flow()
        self.props.anneal_active = False
        self.stopped = True

    def monitor_annealing(self, dur):
        self.props.anneal_time = max(self.props.anneal_time - dur / 1000., 0)
        if self.anneal_time <= 0.0 or self.stopped:
            self.stop_annealing()
            return False
        return True
예제 #15
0
파일: datasets.py 프로젝트: michel4j/mxdc
class AutomationController(Object):
    class StateType:
        STOPPED, PAUSED, ACTIVE, PENDING = list(range(4))

    state = Property(type=int, default=0)

    def __init__(self, widget):
        super().__init__()
        self.widget = widget
        self.beamline = Registry.get_utility(IBeamline)
        self.image_viewer = Registry.get_utility(IImageViewer)
        self.run_dialog = datawidget.DataDialog()
        self.widget.auto_edit_acq_btn.set_popover(self.run_dialog.popover)
        self.automation_queue = SampleQueue(self.widget.auto_queue)
        self.automator = Automator()

        self.config = datawidget.RunItem()
        self.config_display = ConfigDisplay(self.config, self.widget, 'auto')

        self.start_time = 0
        self.pause_info = None
        self.automator.connect('done', self.on_done)
        self.automator.connect('paused', self.on_pause)
        self.automator.connect('stopped', self.on_stopped)
        self.automator.connect('progress', self.on_progress)
        self.automator.connect('sample-done', self.on_sample_done)
        self.automator.connect('sample-started', self.on_sample_started)
        self.automator.connect('started', self.on_started)
        self.automator.connect('error', self.on_error)

        self.connect('notify::state', self.on_state_changed)

        # default
        params = self.run_dialog.get_default(strategy_type=datawidget.StrategyType.SINGLE)
        params.update({
            'resolution': converter.dist_to_resol(
                250, self.beamline.detector.mm_size, self.beamline.energy.get_position()
            ),
            'energy': self.beamline.energy.get_position(),
        })
        self.run_dialog.configure(params)
        self.config.props.info = self.run_dialog.get_parameters()

        # btn, type, options method
        self.tasks = {
            'mount': self.widget.mount_task_btn,
            'center': self.widget.center_task_btn,
            'pause1': self.widget.pause1_task_btn,
            'acquire': self.widget.acquire_task_btn,
            'analyse': self.widget.analyse_task_btn,
            'pause2': self.widget.pause2_task_btn,
        }
        self.task_templates = [
            (self.widget.mount_task_btn, Automator.Task.MOUNT),
            (self.widget.center_task_btn, Automator.Task.CENTER),
            (self.widget.pause1_task_btn, Automator.Task.PAUSE),
            (self.widget.acquire_task_btn, Automator.Task.ACQUIRE),
            (self.widget.analyse_task_btn, Automator.Task.ANALYSE),
            (self.widget.pause2_task_btn, Automator.Task.PAUSE)
        ]
        self.options = {
            'capillary': self.widget.center_cap_option,
            'loop': self.widget.center_loop_option,
            'screen': self.widget.analyse_screen_option,
            'process': self.widget.analyse_process_option,
            'anomalous': self.widget.analyse_anom_option,
            'powder': self.widget.analyse_powder_option,
            'analyse': self.widget.analyse_task_btn
        }
        self.setup()

    def import_from_cache(self):
        config = load_cache('auto')
        if config:
            self.config.info = config['info']
            for name, btn in list(self.tasks.items()):
                if name in config:
                    btn.set_active(config[name])
            for name, option in list(self.options.items()):
                if name in config:
                    option.set_active(config[name])

    def get_options(self, task_type):
        if task_type == Automator.Task.CENTER:
            for name in ['loop', 'crystal', 'raster', 'capillary']:
                if name in self.options and self.options[name].get_active():
                    return {'method': name}
        elif task_type == Automator.Task.ACQUIRE:
            options = {}
            if self.options['analyse'].get_active():
                for name in ['screen', 'process', 'powder']:
                    if self.options[name].get_active():
                        options = {'analysis': name, 'anomalous': self.options['anomalous'].get_active()}
                        break
            options.update(self.config.props.info)
            options['energy'] = self.beamline.energy.get_position()  # use current beamline energy
            return options
        return {}

    def get_task_list(self):
        return [
            {'type': kind, 'options': self.get_options(kind)}
            for btn, kind in self.task_templates if btn.get_active()
        ]

    def get_sample_list(self):
        return self.automation_queue.get_samples()

    def on_state_changed(self, obj, param):
        if self.props.state == self.StateType.ACTIVE:
            self.widget.auto_collect_icon.set_from_icon_name(
                "media-playback-pause-symbolic", Gtk.IconSize.SMALL_TOOLBAR
            )
            self.widget.auto_stop_btn.set_sensitive(True)
            self.widget.auto_collect_btn.set_sensitive(True)
            self.widget.auto_sequence_box.set_sensitive(False)
            self.widget.auto_groups_btn.set_sensitive(False)
        elif self.props.state == self.StateType.PAUSED:
            self.widget.auto_progress_lbl.set_text("Automation paused!")
            self.widget.auto_collect_icon.set_from_icon_name(
                "media-playback-start-symbolic", Gtk.IconSize.SMALL_TOOLBAR
            )
            self.widget.auto_stop_btn.set_sensitive(True)
            self.widget.auto_collect_btn.set_sensitive(True)
            self.widget.auto_sequence_box.set_sensitive(False)
            self.widget.auto_groups_btn.set_sensitive(False)
        elif self.props.state == self.StateType.PENDING:
            self.widget.auto_collect_icon.set_from_icon_name(
                "media-playback-start-symbolic", Gtk.IconSize.SMALL_TOOLBAR
            )
            self.widget.auto_sequence_box.set_sensitive(False)
            self.widget.auto_collect_btn.set_sensitive(False)
            self.widget.auto_stop_btn.set_sensitive(False)
            self.widget.auto_groups_btn.set_sensitive(False)
        else:
            self.widget.auto_collect_icon.set_from_icon_name(
                "media-playback-start-symbolic", Gtk.IconSize.SMALL_TOOLBAR
            )
            self.widget.auto_stop_btn.set_sensitive(False)
            self.widget.auto_collect_btn.set_sensitive(True)
            self.widget.auto_sequence_box.set_sensitive(True)
            self.widget.auto_groups_btn.set_sensitive(True)

    def setup(self):
        self.import_from_cache()
        self.widget.auto_edit_acq_btn.connect('clicked', self.on_edit_acquisition)
        self.run_dialog.data_save_btn.connect('clicked', self.on_save_acquisition)

        for btn in list(self.tasks.values()):
            btn.connect('toggled', self.on_save_acquisition)

        self.widget.auto_collect_btn.connect('clicked', self.on_start_automation)
        self.widget.auto_stop_btn.connect('clicked', self.on_stop_automation)
        self.widget.auto_clean_btn.connect('clicked', self.on_clean_automation)
        self.widget.auto_groups_btn.set_popover(self.widget.auto_groups_pop)
        self.widget.center_task_btn.bind_property('active', self.widget.center_options_box, 'sensitive')
        self.widget.analyse_task_btn.bind_property('active', self.widget.analyse_options_box, 'sensitive')
        self.widget.acquire_task_btn.bind_property('active', self.widget.acquire_options_box, 'sensitive')

    def on_edit_acquisition(self, obj):
        info = self.config.info
        info['energy'] = self.beamline.energy.get_position()
        self.run_dialog.configure(info)

    def on_save_acquisition(self, obj):
        self.config.props.info = self.run_dialog.get_parameters()
        cache = {
            'info': self.config.info,
        }
        for name, btn in list(self.tasks.items()):
            cache[name] = btn.get_active()
        for name, option in list(self.options.items()):
            cache[name] = option.get_active()
        save_cache(cache, 'auto')

    def on_progress(self, obj, fraction, message):
        used_time = time.time() - self.start_time
        remaining_time = 0 if not fraction else (1 - fraction) * used_time / fraction
        eta_time = remaining_time
        self.widget.auto_eta.set_text('{:0>2.0f}:{:0>2.0f} ETA'.format(*divmod(eta_time, 60)))
        self.widget.auto_pbar.set_fraction(fraction)
        self.widget.auto_progress_lbl.set_text(message)

    def on_sample_done(self, obj, uuid):
        self.automation_queue.mark_progress(uuid, SampleStore.Progress.DONE)

    def on_sample_started(self, obj, uuid):
        self.automation_queue.mark_progress(uuid, SampleStore.Progress.ACTIVE)

    def on_done(self, obj, data):
        self.props.state = self.StateType.STOPPED
        self.widget.auto_progress_lbl.set_text("Automation Completed.")
        self.widget.auto_eta.set_text('--:--')
        self.widget.auto_pbar.set_fraction(1.0)

    def on_stopped(self, obj, data):
        self.props.state = self.StateType.STOPPED
        self.widget.auto_progress_lbl.set_text("Automation Stopped.")
        self.widget.auto_eta.set_text('--:--')

    def on_pause(self, obj, paused, reason):
        if paused:
            self.props.state = self.StateType.PAUSED
            if reason:
                # Build the dialog message
                self.pause_info = dialogs.make_dialog(
                    Gtk.MessageType.WARNING, 'Automation Paused', reason,
                    buttons=(('OK', Gtk.ResponseType.OK),)
                )
                self.pause_info.run()
                if self.pause_info:
                    self.pause_info.destroy()
                    self.pause_info = None
        else:
            self.props.state = self.StateType.ACTIVE
            if self.pause_info:
                self.pause_info.destroy()
                self.pause_info = None

    def on_error(self, obj, reason):
        # Build the dialog message
        error_dialog = dialogs.make_dialog(
            Gtk.MessageType.WARNING, 'Automation Error!', reason,
            buttons=(('OK', Gtk.ResponseType.OK),)
        )
        error_dialog.run()
        error_dialog.destroy()

    def on_started(self, obj, data):
        self.start_time = time.time()
        self.props.state = self.StateType.ACTIVE
        logger.info("Automation Started.")

    def on_stop_automation(self, obj):
        self.automator.stop()

    def on_clean_automation(self, obj):
        self.automation_queue.clean()

    def on_start_automation(self, obj):
        if self.props.state == self.StateType.ACTIVE:
            self.widget.auto_progress_lbl.set_text("Pausing automation ...")
            self.automator.pause()
        elif self.props.state == self.StateType.PAUSED:
            self.widget.auto_progress_lbl.set_text("Resuming automation ...")
            self.automator.resume()
        elif self.props.state == self.StateType.STOPPED:
            tasks = self.get_task_list()
            samples = self.get_sample_list()
            if not samples:
                msg1 = 'Queue is empty!'
                msg2 = 'Please add samples and try again.'
                dialogs.warning(msg1, msg2)
            else:
                self.widget.auto_progress_lbl.set_text("Starting automation ...")
                self.props.state = self.StateType.PENDING
                self.automator.configure(samples, tasks)
                self.widget.auto_pbar.set_fraction(0)
                self.automator.start()
                self.image_viewer.set_collect_mode(True)
예제 #16
0
class ScanController(Object):
    class StateType:
        READY, ACTIVE, PAUSED = list(range(3))

    state = Property(type=int, default=StateType.READY)
    config = Property(type=object)
    desc = 'MAD Scan'
    result_class = None
    ConfigSpec = None
    prefix = 'mad'
    Fields = ()
    disabled = ()

    def __init__(self, scanner, plotter, widget, edge_selector):
        super().__init__()
        self.widget = widget
        self.plotter = plotter
        self.scanner = scanner
        self.form = FormManager(self.widget,
                                fields=self.Fields,
                                prefix=self.prefix,
                                persist=True,
                                disabled=self.disabled)
        self.edge_selector = edge_selector
        self.sample_store = Registry.get_utility(ISampleStore)

        self.pause_dialog = None
        self.start_time = 0
        self.scan = None
        self.scan_links = []

        self.results = self.result_class(self.results_view)
        self.setup()

    def setup(self):
        self.scanner.connect('started', self.on_started)
        self.scanner.connect('new-point', self.on_new_point)
        self.scanner.connect('progress', self.on_progress)
        self.scanner.connect('paused', self.on_paused)
        self.scanner.connect('stopped', self.on_stopped)
        self.scanner.connect('error', self.on_error)
        self.scanner.connect('done', self.on_done)

        self.start_btn.connect('clicked', self.start)
        self.stop_btn.connect('clicked', self.stop)

        self.edge_btn.set_popover(self.widget.scans_ptable_pop)
        self.edge_btn.connect('toggled', self.prepare_ptable, self.edge_entry)
        self.edge_entry.connect('changed', self.hide_ptable)
        self.edge_entry.connect('changed', self.on_edge_changed)
        self.connect('notify::state', self.on_state_changed)

    def update_directory(self, directory):
        home = misc.get_project_home()
        dir_text = directory.replace(home, '~')
        self.widget.scans_dir_fbk.set_text(dir_text)
        self.widget.scans_dir_fbk.set_tooltip_text(directory)

    def prepare_ptable(self, btn, entry):
        if btn.get_active():
            self.edge_selector.set_entry(entry)

    def hide_ptable(self, *args, **kwargs):
        self.widget.scans_ptable_pop.popdown()

    def start(self, *args, **kwargs):
        if self.props.state == self.StateType.ACTIVE:
            self.progress_lbl.set_text("Pausing {} ...".format(self.desc))
            self.scanner.pause()
        elif self.props.state == self.StateType.PAUSED:
            self.progress_lbl.set_text("Resuming {} ...".format(self.desc))
            self.scanner.resume()
        elif self.props.state == self.StateType.READY:
            self.progress_lbl.set_text("Starting {} ...".format(self.desc))
            params = self.form.get_values()
            params['uuid'] = str(uuid.uuid4())
            params['name'] = datetime.now().strftime('%y%m%d-%H%M')
            params['activity'] = '{}-scan'.format(self.prefix)
            params = datatools.update_for_sample(
                params, self.sample_store.get_current())
            self.props.config = params
            self.scanner.configure(**self.props.config)
            self.scanner.start()

    def stop(self, *args, **kwargs):
        self.progress_lbl.set_text("Stopping {} ...".format(self.desc))
        self.scanner.stop()

    def on_edge_changed(self, entry):
        edge = entry.get_text()
        if edge:
            absorption, emission = self.edge_selector.get_edge_specs(edge)
            self.absorption_entry.set_text('{:0.4f}'.format(absorption))
            self.emission_entry.set_text('{:0.4f}'.format(emission))

    def on_state_changed(self, *args, **kwargs):
        if self.props.state == self.StateType.ACTIVE:
            self.start_icon.set_from_icon_name("media-playback-pause-symbolic",
                                               Gtk.IconSize.BUTTON)
            self.stop_btn.set_sensitive(True)
            self.start_btn.set_sensitive(True)
            self.config_box.set_sensitive(False)

        elif self.props.state == self.StateType.PAUSED:
            self.progress_lbl.set_text("{} paused!".format(self.desc))
            self.start_icon.set_from_icon_name("media-playback-start-symbolic",
                                               Gtk.IconSize.BUTTON)
            self.stop_btn.set_sensitive(True)
            self.start_btn.set_sensitive(True)
            self.config_box.set_sensitive(False)
        else:
            self.start_icon.set_from_icon_name("media-playback-start-symbolic",
                                               Gtk.IconSize.BUTTON)
            self.config_box.set_sensitive(True)
            self.start_btn.set_sensitive(True)
            self.stop_btn.set_sensitive(False)

    def on_new_point(self, scanner, data):
        self.plotter.add_point(data)

    def on_progress(self, scanner, fraction, message):
        if fraction > 0.0:
            used_time = time.time() - self.start_time
            remaining_time = (1 - fraction) * used_time / fraction
            eta_time = remaining_time
            self.eta.set_text(
                '{:0>2.0f}:{:0>2.0f} ETA'.format(*divmod(eta_time, 60)))
        self.pbar.set_fraction(fraction)
        self.progress_lbl.set_text(message)

    def on_paused(self, scanner, paused, reason):
        if paused:
            self.props.state = self.StateType.PAUSED
            if reason:
                # Build the dialog message
                self.pause_dialog = dialogs.make_dialog(
                    Gtk.MessageType.WARNING,
                    '{} Paused'.format(self.desc),
                    reason,
                    buttons=(('OK', Gtk.ResponseType.OK), ))
                self.pause_dialog.run()
                self.pause_dialog.destroy()
                self.pause_dialog = None
        else:
            self.props.state = self.StateType.ACTIVE
            if self.pause_dialog:
                self.pause_dialog.destroy()
                self.pause_dialog = None

    def on_started(self, scan, specs):
        """
        Clear Scan and setup based on contents of data dictionary.
        """
        self.plotter.clear(specs)
        self.start_time = time.time()

        self.props.state = self.StateType.ACTIVE
        logger.info("{} Started.".format(self.desc))
        self.update_directory(scan.config.directory)

        x_name = specs['data_type']['names'][0]
        x_unit = specs['units'].get(x_name, '').strip()
        self.plotter.set_labels(
            title=specs['scan_type'],
            x_label='{}{}'.format(x_name,
                                  ' ({})'.format(x_unit) if x_unit else ''),
        )

    def on_stopped(self, scanner, data):
        self.props.state = self.StateType.READY
        self.progress_lbl.set_text("{} Stopped.".format(self.desc))
        self.eta.set_text('--:--')

    def on_error(self, scanner, message):
        error_dialog = dialogs.make_dialog(Gtk.MessageType.WARNING,
                                           '{} Error!'.format(self.desc),
                                           message,
                                           buttons=(('OK',
                                                     Gtk.ResponseType.OK), ))
        error_dialog.run()
        error_dialog.destroy()

    def on_done(self, scan, data):
        self.props.state = self.StateType.READY
        self.progress_lbl.set_text("{} Completed.".format(self.desc))
        self.eta.set_text('--:--')
        self.pbar.set_fraction(1.0)

    def __getattr__(self, item):
        try:
            return getattr(self.widget, '{}_{}'.format(self.prefix, item))
        except AttributeError:
            raise AttributeError('{} does not have attribute: {}'.format(
                self, item))
예제 #17
0
파일: analysis.py 프로젝트: michel4j/mxdc
class ReportManager(TreeManager):
    class Data(Enum):
        NAME, GROUP, ACTIVITY, TYPE, SCORE, TITLE, STATE, UUID, SAMPLE, DIRECTORY, DATA, REPORT, ERROR = list(
            range(13))

    Types = [
        str, str, str, str, float, str, int, str, object, str, object, object,
        object
    ]

    class State:
        PENDING, ACTIVE, SUCCESS, FAILED = list(range(4))

    Columns = ColumnSpec(
        (Data.NAME, 'Name', ColumnType.TEXT, '{}', True),
        (Data.TITLE, "Title", ColumnType.TEXT, '{}', True),
        (Data.ACTIVITY, "Type", ColumnType.TEXT, '{}', False),
        (Data.SCORE, 'Score', ColumnType.NUMBER, '{:0.2f}', False),
        (Data.STATE, "", ColumnType.ICON, '{}', False),
    )
    Icons = {
        State.PENDING: ('content-loading-symbolic', colors.Category.CAT20[14]),
        State.ACTIVE:
        ('emblem-synchronizing-symbolic', colors.Category.CAT20[2]),
        State.SUCCESS: ('object-select-symbolic', colors.Category.CAT20[4]),
        State.FAILED: ('computer-fail-symbolic', colors.Category.CAT20[6]),
    }
    #
    # tooltips = Data.TITLE
    parent = Data.NAME
    flat = False
    select_multiple = True
    single_click = True

    directory = Property(type=str, default='')
    sample = Property(type=object)
    strategy = Property(type=object)

    def update_item(self, item_id, report=None, error=None, title='????'):
        itr = self.model.get_iter_first()
        row = None
        while itr and not row:
            if self.model[itr][self.Data.UUID.value] == item_id:
                row = self.model[itr]
                break
            elif self.model.iter_has_child(itr):
                child_itr = self.model.iter_children(itr)
                while child_itr:
                    if self.model[child_itr][self.Data.UUID.value] == item_id:
                        row = self.model[child_itr]
                        break
                    child_itr = self.model.iter_next(child_itr)
            itr = self.model.iter_next(itr)
        if row:
            if report:
                row[self.Data.REPORT.value] = report
                row[self.Data.SCORE.value] = report.get('score', 0.0)
                row[self.Data.STATE.value] = self.State.SUCCESS
            elif error:
                row[self.Data.ERROR.value] = error
                row[self.Data.STATE.value] = self.State.FAILED
            row[self.Data.TITLE.value] = title

    def row_activated(self, view, path, column):
        model = view.get_model()
        itr = model.get_iter(path)
        item = self.row_to_dict(model[itr])
        report = item['report'] or {}
        self.props.strategy = report.get('strategy')
        self.props.directory = item['directory']
        self.props.sample = item['sample']

    def format_cell(self, column, renderer, model, itr, spec):
        super(ReportManager, self).format_cell(column, renderer, model, itr,
                                               spec)
        if not model.iter_has_child(itr):
            row = model[itr]
            state = row[self.Data.STATE.value]
            if state == self.State.PENDING:
                renderer.set_property(
                    "foreground-rgba",
                    Gdk.RGBA(red=0.0, green=0.0, blue=0.0, alpha=0.7))
            elif state == self.State.FAILED:
                renderer.set_property(
                    "foreground-rgba",
                    Gdk.RGBA(red=0.35, green=0.0, blue=0.0, alpha=1.0))
            elif state == self.State.SUCCESS:
                renderer.set_property(
                    "foreground-rgba",
                    Gdk.RGBA(red=0.0, green=0.35, blue=0.0, alpha=1.0))
            else:
                renderer.set_property(
                    "foreground-rgba",
                    Gdk.RGBA(red=0.35, green=0.35, blue=0.0, alpha=1.0))
        else:
            renderer.set_property("foreground-rgba", None)
예제 #18
0
파일: manager.py 프로젝트: michel4j/mxdc
class BaseManager(Device):
    """
    Base Mode Manager. A device to manage beamline modes

    Signals:
        - **mode**: beamline mode

    Properties:
        - **mode**: beamline mode
    """
    class ModeType(Enum):
        MOUNT, CENTER, COLLECT, ALIGN, BUSY, UNKNOWN = list(range(6))

    class Signals:
        mode = Signal("mode", arg_types=(object, ))

    # Properties
    mode = Property(type=object)

    def __init__(self, name='Beamline Modes'):
        super().__init__()
        self.name = name
        self.mode = self.ModeType.UNKNOWN

    def wait(self, *modes, start=True, stop=True, timeout=30):
        """
        Wait for the one of specified modes.

        :param modes: a list of Mode ENUMS or strings to wait for
        :param start: (bool), Wait for the manager to become busy.
        :param stop: (bool), Wait for the manager to become idle.
        :param timeout: maximum time in seconds to wait before failing.
        :return: (bool), False if wait timed-out
        """

        mode_set = {
            m if isinstance(m, self.ModeType) else self.ModeType[m]
            for m in modes
        }
        if self.mode in mode_set:
            logger.debug('Already in requested mode')
            return True

        poll = 0.05
        time_left = 2
        if start:
            logger.debug('Waiting for mode manager to start')
            while not self.is_busy() and time_left > 0:
                time.sleep(poll)
                time_left -= poll
            if time_left <= 0:
                logger.warn('Timed out waiting for mode manager to start')

        if stop:
            time_left = timeout

            if mode_set:
                logger.debug('Waiting for {}: {}'.format(self.name, mode_set))
                while time_left > 0 and (not self.mode
                                         in mode_set) and self.is_busy():
                    time_left -= poll
                    time.sleep(poll)
            else:
                logger.debug('Waiting for {} to stop moving'.format(self.name))
                while time_left > 0 and self.is_busy():
                    time_left -= poll
                    time.sleep(poll)

        if time_left <= 0:
            logger.warning('Timed out waiting for {}'.format(self.name))
            return False

        return True

    def mount(self, wait=False):
        """
        Switch to Mount mode

        :param wait: wait for switch to complete
        """
        raise NotImplementedError('Sub-classes must implement "mount"')

    def center(self, wait=False):
        """
        Switch to Center mode

        :param wait: wait for switch to complete
        """
        raise NotImplementedError('Sub-classes must implement "center"')

    def collect(self, wait=False):
        """
        Switch to Collect mode

        :param wait: wait for switch to complete
        """
        raise NotImplementedError('Sub-classes must implement "collect"')

    def align(self, wait=False):
        """
        Switch to Align mode

        :param wait: wait for switch to complete
        """
        raise NotImplementedError('Sub-classes must implement "align"')

    def get_mode(self):
        """
        Return the current mode
        """
        return self.props.mode
예제 #19
0
class DewarController(Object):
    class Signals:
        selected = Signal("selected", arg_types=(str, ))

    layout = Property(type=object)
    ports = Property(type=object)
    containers = Property(type=object)

    def __init__(self, widget, store):
        super().__init__()
        self.widget = widget
        self.store = store
        self.beamline = Registry.get_utility(IBeamline)
        self.setup()
        self.failure_dialog = None
        self.messages = [(None, "")]

        self.props.layout = {}
        self.props.ports = {}
        self.props.containers = []

        self.beamline.automounter.connect('status', self.on_state_changed)
        self.beamline.automounter.connect('ports', self.on_layout_changed)
        self.beamline.automounter.connect('layout', self.on_layout_changed)
        self.beamline.automounter.connect('containers', self.on_layout_changed)
        self.beamline.automounter.connect('sample', self.on_layout_changed)
        self.beamline.automounter.connect('failure', self.on_failure_changed)

        self.store.connect('notify::ports', self.on_layout_changed)
        self.store.connect('notify::containers', self.on_layout_changed)

        self.beamline.automounter.connect('active', self.on_state_changed)
        self.beamline.automounter.connect('busy', self.on_state_changed)
        self.beamline.automounter.connect('health', self.on_state_changed)
        self.beamline.automounter.connect('message', self.on_messages)

    def setup(self):
        self.widget.sample_dewar_area.connect('draw', self.draw_dewar)
        self.widget.sample_dewar_area.set_events(
            Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK
            | Gdk.EventMask.BUTTON_PRESS_MASK
            | Gdk.EventMask.VISIBILITY_NOTIFY_MASK)
        self.widget.sample_dewar_area.connect('query-tooltip',
                                              self.on_query_tooltip)
        self.widget.sample_dewar_area.connect('button-press-event',
                                              self.on_press_event)

    def get_port_state(self, port):
        robot_ports = self.beamline.automounter.get_state('ports')
        user_ports = self.store.ports
        state = robot_ports.get(port, Port.UNKNOWN)
        if port in user_ports:
            if state not in [Port.BAD, Port.EMPTY, Port.MOUNTED]:
                state = Port.GOOD
        return state

    def on_layout_changed(self, obj, *args):
        self.props.layout = self.beamline.automounter.get_state('layout')
        robot_ports = self.beamline.automounter.get_state('ports')
        robot_containers = self.beamline.automounter.get_state('containers')
        user_ports = self.store.ports
        user_containers = self.store.containers
        self.props.ports = {
            port: self.get_port_state(port)
            for port in list(robot_ports.keys())
            if (port in user_ports or self.beamline.is_admin())
        }
        self.props.containers = {
            container
            for container in robot_containers
            if container in user_containers or self.beamline.is_admin()
        }

        self.widget.sample_dewar_area.queue_draw()

    def draw_dewar(self, widget, cr):
        # background
        alloc = widget.get_allocation()
        cr.save()
        cr.scale(alloc.width, alloc.height)

        if self.layout:
            for loc, container in list(self.layout.items()):
                container.draw(cr, self.ports, self.containers)
        else:
            xscale, yscale = cr.device_to_user_distance(1, 1)
            cr.set_font_size(14 * xscale)
            cr.set_line_width(1 * xscale)
            text = 'Layout Not available!'
            cr.set_source_rgb(0.5, 0.35, 0)
            xb, yb, w, h = cr.text_extents(text)[:4]
            cr.move_to(0.5 - w / 2.0, 0.5 - h / 2.0 - yb)
            cr.show_text(text)
            cr.stroke()

        cr.restore()

    def find_port(self, x, y):
        for loc, container in list(self.layout.items()):
            port = container.get_port(x, y)
            if port:
                return loc, port
        return None, None

    def on_query_tooltip(self, widget, x, y, keyboard, tooltip):
        if keyboard:
            return False
        alloc = widget.get_allocation()
        xp = x / alloc.width
        yp = y / alloc.height
        loc, port = self.find_port(xp, yp)
        if loc and port and self.allow_port(loc, port):
            label = self.store.get_name(port)
            widget.get_window().set_cursor(Gdk.Cursor.new(
                Gdk.CursorType.HAND2))
            tooltip.set_markup('<small><b>{}</b>\n{}</small>'.format(
                port, label))
            return True
        else:
            widget.get_window().set_cursor(None)
            return False

    def allow_port(self, container, port):
        if self.beamline.is_admin() or ((container and port) and
                                        (port in self.ports)):
            return self.get_port_state(port) not in [Port.EMPTY, Port.BAD]
        return False

    def on_press_event(self, widget, event):
        alloc = widget.get_allocation()
        x = event.x / alloc.width
        y = event.y / alloc.height
        loc, port = self.find_port(x, y)
        if self.allow_port(loc, port):
            self.emit('selected', port)

    def on_state_changed(self, obj, *args):
        status = self.beamline.automounter.get_state('status')

        if status.name in [
                'IDLE',
        ]:
            self.widget.automounter_command_box.set_sensitive(True)

        self.widget.automounter_status_fbk.set_text(status.name)

        failure = self.beamline.automounter.get_state('failure')
        if status.name == 'IDLE' and failure and self.failure_dialog:
            self.failure_dialog.destroy()
            self.failure_dialog = None

    def failure_callback(self, dialog, response, context):
        if self.failure_dialog:
            self.failure_dialog.destroy()
            self.failure_dialog = None
        if response == Gtk.ButtonsType.OK:
            self.beamline.automounter.recover(context)
            message = ("Recovery is in progress. This operation may take\n"
                       "a few minutes. The automounter will be usable again\n"
                       "when it reaches the IDLE state")
            dialogs.info('Automounter Recovery', message, modal=False)

    def on_failure_changed(self, obj, failure):
        failure_context = failure
        if failure_context:
            failure_type, message = failure_context
            self.failure_dialog = dialogs.make_dialog(
                Gtk.MessageType.QUESTION,
                'Automounter Failed: {}'.format(
                    failure_type.replace('-', ' ').title()),
                message,
                buttons=(('Cancel', Gtk.ButtonsType.CANCEL),
                         ('Recover', Gtk.ButtonsType.OK)),
                modal=False)
            self.failure_dialog.connect('response', self.failure_callback,
                                        failure_context)
            self.failure_dialog.show_all()
        else:
            if self.failure_dialog:
                self.failure_dialog.destroy()
                self.failure_dialog = None

    def on_messages(self, obj, message):
        if message:
            prev_time, prev_msg = self.messages[-1]
            if message == prev_msg:
                return
            self.messages.append((datetime.now(), message))

            if len(self.messages) > 3:
                self.messages = self.messages[-3:]

            text = '\n'.join([
                "<small><tt>{} - </tt>{}</small>".format(
                    dt.now().strftime('%H:%M:%S'), msg.strip())
                for dt, msg in self.messages if dt
            ])

            self.widget.automounter_message_fbk.set_markup(text)
예제 #20
0
파일: microscope.py 프로젝트: michel4j/mxdc
class Microscope(Object):
    class GridState:
        PENDING, COMPLETE = list(range(2))

    class ToolState(object):
        DEFAULT, CENTERING, GRID, MEASUREMENT = list(range(4))

    grid = Property(type=object)
    grid_xyz = Property(type=object)
    grid_state = Property(type=int, default=GridState.PENDING)
    grid_params = Property(type=object)
    grid_scores = Property(type=object)
    grid_cmap = Property(type=object)
    grid_bbox = Property(type=object)
    points = Property(type=object)

    tool = Property(type=int, default=ToolState.DEFAULT)
    mode = Property(type=object)

    def __init__(self, widget):
        super().__init__()
        self.timeout_id = None
        self.max_fps = 20
        self.fps_update = 0
        self.video_ready = False
        self.queue_overlay()
        self.overlay_surface = None
        self.overlay_ctx = None

        self.props.grid = None
        self.props.grid_xyz = None
        self.props.points = []
        self.props.grid_bbox = []
        self.props.grid_scores = {}
        self.props.grid_params = {}
        self.props.grid_state = self.GridState.PENDING
        self.props.grid_cmap = colors.ColorMapper(min_val=0, max_val=100)
        self.props.tool = self.ToolState.DEFAULT
        self.prev_tool = self.tool

        self.tool_cursors = {
            self.ToolState.DEFAULT:
            None,
            self.ToolState.CENTERING:
            Gdk.Cursor.new_from_name(Gdk.Display.get_default(), 'pointer'),
            self.ToolState.GRID:
            Gdk.Cursor.new_from_name(Gdk.Display.get_default(), 'cell'),
            self.ToolState.MEASUREMENT:
            Gdk.Cursor.new_from_name(Gdk.Display.get_default(), 'crosshair'),
        }

        self.ruler_box = numpy.array([[0, 0], [0, 0]])

        self.widget = widget
        self.beamline = Registry.get_utility(IBeamline)
        self.camera = self.beamline.sample_video
        self.centering = centering.Centering()
        self.setup()

        Registry.add_utility(IMicroscope, self)
        self.load_from_cache()

    def setup(self):

        # zoom
        low, med, high = self.beamline.config['zoom_levels']
        self.widget.microscope_zoomout_btn.connect('clicked', self.on_zoom,
                                                   low)
        self.widget.microscope_zoom100_btn.connect('clicked', self.on_zoom,
                                                   med)
        self.widget.microscope_zoomin_btn.connect('clicked', self.on_zoom,
                                                  high)

        # rotate sample
        self.widget.microscope_ccw90_btn.connect('clicked', self.on_rotate,
                                                 -90)
        self.widget.microscope_cw90_btn.connect('clicked', self.on_rotate, 90)
        self.widget.microscope_rot180_btn.connect('clicked', self.on_rotate,
                                                  180)

        # centering
        self.widget.microscope_loop_btn.connect('clicked', self.on_auto_center,
                                                'loop')
        self.widget.microscope_capillary_btn.connect('clicked',
                                                     self.on_auto_center,
                                                     'capillary')
        self.widget.microscope_diff_btn.connect('clicked', self.on_auto_center,
                                                'diffraction')
        self.widget.microscope_ai_btn.connect('clicked', self.on_auto_center,
                                              'external')

        self.beamline.manager.connect('mode', self.on_gonio_mode)
        self.beamline.goniometer.stage.connect('changed', self.update_grid)
        self.beamline.sample_zoom.connect('changed', self.update_grid)
        self.beamline.aperture.connect('changed', self.on_aperture)

        # Video Area
        self.video = VideoWidget(self.camera)
        self.beamline.camera_scale.connect('changed', self.on_camera_scale)
        self.widget.microscope_video_frame.add(self.video)

        # status, save, etc
        self.widget.microscope_save_btn.connect('clicked', self.on_save)
        self.widget.microscope_grid_btn.connect('toggled',
                                                self.toggle_grid_mode)
        self.widget.microscope_colorize_tbtn.connect('toggled', self.colorize)
        self.widget.microscope_point_btn.connect('clicked', self.on_save_point)
        self.widget.microscope_clear_btn.connect('clicked', self.clear_objects)

        # disable centering buttons on click
        self.centering.connect('started', self.on_scripts_started)
        self.centering.connect('done', self.on_scripts_done)

        aicenter = Registry.get_utility(ICenter)
        self.widget.microscope_ai_btn.set_sensitive(False)
        if aicenter:
            aicenter.connect(
                'active', lambda obj, state: self.widget.microscope_ai_btn.
                set_sensitive(state))

        # lighting monitors
        self.monitors = []
        for key in ['backlight', 'frontlight', 'uvlight']:
            light = getattr(self.beamline, 'sample_{}'.format(key), None)
            scale = getattr(self.widget, 'microscope_{}_scale'.format(key),
                            None)
            box = getattr(self.widget, '{}_box'.format(key), None)
            if all([light, scale, box]):
                scale.set_adjustment(
                    Gtk.Adjustment(0, 0.0, 100.0, 1.0, 1.0, 10))
                self.monitors.append(common.ScaleMonitor(scale, light), )
                box.set_sensitive(True)
            else:
                box.destroy()
            if key == 'uvlight':
                color = Gdk.RGBA()
                color.parse("#9B59B6")
                box.override_color(Gtk.StateFlags.NORMAL, color)

        self.video.connect('motion-notify-event', self.on_mouse_motion)
        self.video.connect('scroll-event', self.on_mouse_scroll)
        self.video.connect('button-press-event', self.on_mouse_press)
        self.video.connect('button-release-event', self.on_mouse_release)
        self.video.set_overlay_func(self.overlay_function)
        self.video.connect('configure-event', self.setup_grid)
        self.scripts = get_scripts()

        # Connect Grid signals
        self.connect('notify::grid-xyz', self.update_grid)
        self.connect('notify::tool', self.on_tool_changed)

    def change_tool(self, tool=None):
        if tool is None:
            self.props.tool, self.prev_tool = self.prev_tool, self.tool
        elif self.props.tool != tool:
            self.props.tool, self.prev_tool = tool, self.tool

    def setup_grid(self, *args, **kwargs):
        if not self.video_ready:
            self.video_ready = True
            for param in ['grid-xyz', 'points', 'grid-params']:
                self.connect('notify::{}'.format(param), self.save_to_cache)
        self.update_grid()

    def save_to_cache(self, *args, **kwargs):
        cache = {
            'points':
            self.props.points,
            'grid-xyz':
            None
            if self.props.grid_xyz is None else self.props.grid_xyz.tolist(),
            'grid-params':
            self.props.grid_params,
            'grid-scores':
            self.props.grid_scores,
            'grid-state':
            self.props.grid_state,
        }
        save_cache(cache, 'microscope')

    def load_from_cache(self):
        cache = load_cache('microscope')
        if cache and isinstance(cache, dict):
            for name, value in list(cache.items()):
                if name == 'grid-xyz':
                    value = None if not isinstance(
                        value, list) else numpy.array(value)
                if name == 'points':
                    value = [tuple(point) for point in value]
                self.set_property(name, value)

    def save_image(self, filename):
        self.video.save_image(filename)

    def draw_beam(self, cr):
        radius = 0.5e-3 * self.beamline.aperture.get() / self.video.mm_scale()
        tick_in = radius * 0.8
        tick_out = radius * 1.2
        center = numpy.array(self.video.get_size()) / 2

        cr.set_source_rgba(1.0, 0.25, 0.0, 0.5)
        cr.set_line_width(2.0)

        # beam circle
        cr.arc(center[0], center[1], radius, 0, 2.0 * 3.14)
        cr.stroke()

        # beam target ticks
        cr.move_to(center[0], center[1] - tick_in)
        cr.line_to(center[0], center[1] - tick_out)
        cr.stroke()

        cr.move_to(center[0], center[1] + tick_in)
        cr.line_to(center[0], center[1] + tick_out)
        cr.stroke()

        cr.move_to(center[0] - tick_in, center[1])
        cr.line_to(center[0] - tick_out, center[1])
        cr.stroke()

        cr.move_to(center[0] + tick_in, center[1])
        cr.line_to(center[0] + tick_out, center[1])
        cr.stroke()

    def draw_measurement(self, cr):
        if self.tool == self.ToolState.MEASUREMENT:
            cr.set_font_size(10)
            (x1, y1), (x2, y2) = self.ruler_box
            dist = 1000 * self.video.mm_scale() * math.sqrt((x2 - x1)**2.0 +
                                                            (y2 - y1)**2.0)
            cr.set_source_rgba(0.0, 0.5, 1.0, 1.0)
            cr.set_line_width(1.0)
            cr.move_to(x1, y1)
            cr.line_to(x2, y2)
            cr.stroke()
            label = '{:0.0f} µm'.format(dist)
            xb, yb, w, h = cr.text_extents(label)[:4]
            cr.move_to(x1 - w * (int(x2 > x1) + xb / w),
                       y1 - h * (int(y2 > y1) + yb / h))
            cr.show_text(label)

    def draw_bbox(self, cr):
        if self.tool == self.ToolState.GRID and len(self.props.grid_bbox):
            cr.set_font_size(10)
            cr.set_line_width(1.0)
            cr.set_source_rgba(0.0, 0.5, 1.0, 1.0)

            # rectangle
            (x1, y1), (x2, y2) = self.props.grid_bbox
            cr.rectangle(x1, y1, x2 - x1, y2 - y1)
            cr.stroke()

            # center
            cx = (x1 + x2) / 2
            cy = (y1 + y2) / 2

            # width
            width = 1000 * self.video.mm_scale() * abs(x2 - x1)
            w_label = '{:0.0f} µm'.format(width)
            xb, yb, w, h = cr.text_extents(w_label)[:4]
            cr.move_to(cx - w / 2, y1 - h / 2)
            cr.show_text(w_label)

            # height
            height = 1000 * self.video.mm_scale() * abs(y2 - y1)
            h_label = '{:0.0f} µm'.format(height)
            cr.move_to(x2 + h / 2, cy)
            cr.show_text(h_label)

    def draw_grid(self, cr):
        if self.props.grid is not None:
            radius = 0.5e-3 * self.beamline.aperture.get(
            ) / self.video.mm_scale()
            cr.set_line_width(1.0)
            cr.set_font_size(8)
            for i, (x, y, z) in enumerate(self.props.grid):
                if i + 1 in self.props.grid_scores:
                    col = self.props.grid_cmap.rgba_values(
                        self.props.grid_scores[i + 1], alpha=0.5)
                    cr.set_source_rgba(*col)
                    cr.arc(x, y, radius, 0, 2.0 * 3.14)
                    cr.fill()
                cr.set_source_rgba(0.0, 0.5, 1.0, 1.0)
                cr.arc(x, y, radius, 0, 2.0 * 3.14)
                cr.stroke()
                name = '{}'.format(i + 1)
                xb, yb, w, h = cr.text_extents(name)[:4]
                cr.move_to(x - w / 2. - xb, y - h / 2. - yb)
                cr.show_text(name)
                cr.stroke()

    def draw_points(self, cr):
        if self.props.points:
            cr.save()
            mm_scale = self.video.mm_scale()
            radius = 0.5e-3 * self.beamline.aperture.get() / (16 * mm_scale)
            cur_point = numpy.array(self.beamline.goniometer.stage.get_xyz())
            center = numpy.array(self.video.get_size()) / 2
            points = numpy.array(self.props.points) - cur_point
            xyz = numpy.zeros_like(points)
            xyz[:,
                0], xyz[:,
                        1], xyz[:,
                                2] = self.beamline.goniometer.stage.xyz_to_screen(
                                    points[:, 0], points[:, 1], points[:, 2])
            xyz /= mm_scale
            radii = (4.0 - (xyz[:, 2] / (center[1] * 0.25))) * radius
            xyz[:, :2] += center
            cr.set_source_rgba(1.0, 0.25, 0.75, 0.5)
            for i, (x, y, z) in enumerate(xyz):
                cr.arc(x, y, radii[i], 0, 2.0 * 3.14)
                cr.fill()
                cr.move_to(x + 6, y)
                cr.show_text('P{}'.format(i + 1))
                cr.stroke()
            cr.restore()

    def clear_objects(self, *args, **kwargs):
        self.props.grid = None
        self.props.grid_xyz = None
        self.props.grid_scores = {}
        self.props.points = []
        self.props.grid_bbox = []
        if self.tool == self.ToolState.GRID:
            self.change_tool(self.ToolState.CENTERING)
        self.widget.microscope_grid_btn.set_active(False)
        self.queue_overlay()

    def toggle_grid_mode(self, *args, **kwargs):
        if self.widget.microscope_grid_btn.get_active():
            self.change_tool(self.ToolState.GRID)
            self.props.grid_bbox = []
        else:
            self.widget.microscope_grid_btn.set_active(False)
            self.change_tool()
        self.queue_overlay()

    def auto_grid(self, *args, **kwargs):
        img = self.camera.get_frame()
        polygon = imgproc.find_profile(img, scale=0.25)
        points = numpy.array(polygon)
        self.props.grid_bbox = numpy.array(
            [points.min(axis=0), points.max(axis=0)])

    def add_point(self, point):
        self.props.points = self.props.points + [point]
        self.queue_overlay()

    def make_grid(self, bbox=None, points=None, scaled=True, center=True):
        if points is not None:
            points = numpy.array(points)
            bbox = numpy.array([points.min(axis=0), points.max(axis=0)])
        elif bbox is None:
            bbox = self.props.grid_bbox

        if not isinstance(bbox, numpy.ndarray):
            bbox = numpy.array(bbox)

        factor = 1.0 if scaled else self.video.scale
        step_size = 1e-3 * self.beamline.aperture.get() / self.video.mm_scale()
        w, h = 1000 * numpy.abs(
            numpy.diff(bbox, axis=0).ravel() * self.video.mm_scale()).round(4)

        # grid too small exit and clear
        if max(w, h) < 2 * step_size:
            self.props.grid_bbox = []
            self.queue_overlay()
            self.props.grid_params = {}
            return

        bounds = bbox * factor

        grid = misc.grid_from_bounds(bounds, step_size, tight=False)
        dx, dy = self.video.screen_to_mm(*bounds.mean(axis=0))[2:]

        angle = self.beamline.goniometer.omega.get_position()
        ox, oy, oz = self.beamline.goniometer.stage.get_xyz()
        xmm, ymm = self.video.screen_to_mm(grid[:, 0], grid[:, 1])[2:]
        gx, gy, gz = self.beamline.goniometer.stage.xvw_to_xyz(
            -xmm, -ymm, numpy.radians(angle))
        grid_xyz = numpy.dstack([gx + ox, gy + oy, gz + oz])[0]

        properties = {
            'grid_state': self.GridState.PENDING,
            'grid_xyz': grid_xyz.round(4),
            'grid_bbox': [],
            'grid_params': {
                'width': w,
                'height': h,
                'angle': angle,
            },
            'grid_scores': {}
        }

        for k, v in properties.items():
            self.set_property(k, v)
        if center:
            self.beamline.goniometer.stage.move_screen_by(-dx, -dy, 0.0)

    def add_grid_score(self, position, score):
        self.props.grid_scores[position] = score
        self.props.grid_cmap.autoscale(list(self.props.grid_scores.values()))
        self.props.grid_state = self.GridState.COMPLETE

    def load_grid(self, grid_xyz, params, scores):
        self.props.grid_xyz = grid_xyz
        self.props.grid_scores = scores
        self.props.grid_params = params
        self.props.grid_state = self.GridState.COMPLETE
        self.props.grid_cmap.autoscale(list(self.props.grid_scores.values()))

    @async_call
    def center_pixel(self, x, y, force=False):
        if self.tool == self.ToolState.CENTERING or force:
            ix, iy, xmm, ymm = self.video.screen_to_mm(x, y)
            if not self.beamline.goniometer.stage.is_busy():
                self.beamline.goniometer.stage.move_screen_by(-xmm, -ymm, 0.0)

    def create_overlay_surface(self):
        self.overlay_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                                                  self.video.display_width,
                                                  self.video.display_height)
        self.overlay_ctx = cairo.Context(self.overlay_surface)

    def overlay_function(self, cr):
        self.update_overlay()
        cr.set_source_surface(self.overlay_surface, 0, 0)
        cr.paint()

    def queue_overlay(self):
        self.overlay_dirty = True

    def update_overlay(self):
        if self.overlay_dirty or self.overlay_surface is None:
            self.create_overlay_surface()
            self.draw_beam(self.overlay_ctx)
            self.draw_grid(self.overlay_ctx)
            self.draw_bbox(self.overlay_ctx)
            self.draw_points(self.overlay_ctx)
            self.draw_measurement(self.overlay_ctx)
            self.overlay_dirty = False

    # callbacks
    def update_grid(self, *args, **kwargs):
        if self.props.grid_xyz is not None:
            center = numpy.array(self.video.get_size()) * 0.5
            points = self.grid_xyz - numpy.array(
                self.beamline.goniometer.stage.get_xyz())
            xyz = numpy.empty_like(points)
            xyz[:,
                0], xyz[:,
                        1], xyz[:,
                                2] = self.beamline.goniometer.stage.xyz_to_screen(
                                    points[:, 0], points[:, 1], points[:, 2])
            xyz /= self.video.mm_scale()
            xyz[:, :2] += center
            self.props.grid = xyz
        else:
            self.props.grid = None
        self.queue_overlay()

    def colorize(self, button):
        self.video.set_colorize(state=button.get_active())

    def on_save_point(self, *args, **kwargs):
        self.add_point(self.beamline.goniometer.stage.get_xyz())
        self.save_to_cache()

    def on_tool_changed(self, *args, **kwargs):
        window = self.widget.microscope_bkg.get_window()
        if window:
            window.set_cursor(self.tool_cursors[self.props.tool])

    def on_camera_scale(self, obj, value):
        self.queue_overlay()
        self.video.set_pixel_size(value)

    def on_gonio_mode(self, obj, mode):
        self.props.mode = mode
        centering_tool = self.mode.name in ['CENTER', 'ALIGN']
        self.widget.microscope_centering_box.set_sensitive(centering_tool)
        self.widget.microscope_grid_box.set_sensitive(centering_tool)
        if centering_tool:
            self.change_tool(self.ToolState.CENTERING)
        else:
            self.change_tool(self.ToolState.DEFAULT)

        if self.mode.name == 'ALIGN':
            self.widget.microscope_colorize_tbtn.set_active(True)
        elif self.mode.name not in ['BUSY', 'UNKNOWN']:
            self.widget.microscope_colorize_tbtn.set_active(False)

    def on_scripts_started(self, obj, event=None):
        self.widget.microscope_toolbar.set_sensitive(False)

    def on_scripts_done(self, obj, event=None):
        self.widget.microscope_toolbar.set_sensitive(True)

    def on_save(self, obj=None, arg=None):
        img_filename, _ = dialogs.select_save_file('Save Video Snapshot',
                                                   formats=[
                                                       ('PNG Image', 'png'),
                                                       ('JPEG Image', 'jpg')
                                                   ])
        if not img_filename:
            return
        if os.access(os.path.split(img_filename)[0], os.W_OK):
            self.save_image(img_filename)

    def on_auto_center(self, widget, method='loop'):
        self.centering.configure(method=method)
        self.centering.start()
        return True

    def on_zoom(self, widget, position):
        self.camera.zoom(position)

    def on_aperture(self, obj, value):
        if self.grid_xyz is not None:
            center = numpy.array(self.video.get_size()) * 0.5
            points = self.grid_xyz - numpy.array(
                self.beamline.goniometer.stage.get_xyz())
            xyz = numpy.empty_like(points)
            xyz[:,
                0], xyz[:,
                        1], xyz[:,
                                2] = self.beamline.goniometer.stage.xyz_to_screen(
                                    points[:, 0], points[:, 1], points[:, 2])
            xyz /= self.video.mm_scale()
            xyz[:, :2] += center
            self.make_grid(points=xyz[:, :2], center=False)
        else:
            self.queue_overlay()

    def on_rotate(self, widget, angle):
        cur_omega = int(self.beamline.goniometer.omega.get_position())
        target = (cur_omega + angle)
        target = (target > 360) and (target % 360) or target
        self.beamline.goniometer.omega.move_to(target)

    def on_mouse_scroll(self, widget, event):
        if 'GDK_CONTROL_MASK' in event.get_state(
        ).value_names and self.mode.name in ['CENTER', 'ALIGN']:
            if event.direction == Gdk.ScrollDirection.UP:
                self.on_rotate(widget, 45)
            elif event.direction == Gdk.ScrollDirection.DOWN:
                self.on_rotate(widget, -45)

    def on_mouse_motion(self, widget, event):
        if event.is_hint:
            _, x, y, state = event.window.get_pointer()
        else:
            x, y = event.x, event.y
        ix, iy, xmm, ymm = self.video.screen_to_mm(x, y)
        self.widget.microscope_pos_lbl.set_markup(
            f"<small><tt>X:{ix:5.0f} {xmm:6.3f} mm\nY:{iy:5.0f} {ymm:6.3f} mm</tt></small>"
        )

        if Gdk.ModifierType.BUTTON2_MASK & event.state:
            self.ruler_box[1] = (x, y)
            self.queue_overlay()
        elif Gdk.ModifierType.CONTROL_MASK & event.state and self.mode.name in [
                'COLLECT'
        ]:
            self.change_tool(tool=self.ToolState.CENTERING)
        elif self.tool == self.ToolState.GRID and len(self.props.grid_bbox):
            if Gdk.ModifierType.BUTTON1_MASK & event.state:
                self.props.grid_bbox[-1] = (x, y)
            self.queue_overlay()
        elif self.tool == self.ToolState.MEASUREMENT:
            self.change_tool()
            self.queue_overlay()
        elif self.tool == self.ToolState.CENTERING and self.mode.name in [
                'COLLECT'
        ]:
            self.change_tool()

    def on_mouse_press(self, widget, event):
        if event.button == 1:
            if self.tool == self.ToolState.GRID:
                self.props.grid_bbox = [(event.x, event.y), (event.x, event.y)]
                self.queue_overlay()
            else:
                self.center_pixel(event.x, event.y)
        elif event.button == 2:
            self.change_tool(self.ToolState.MEASUREMENT)
            self.ruler_box[0] = (event.x, event.y)
            self.ruler_box[1] = (event.x, event.y)
            self.queue_overlay()

    def on_mouse_release(self, widget, event):
        if event.button == 1:
            if self.tool == self.ToolState.GRID and self.grid_bbox:
                self.make_grid()
                self.widget.microscope_grid_btn.set_active(False)
예제 #21
0
class RunItem(Object):
    class StateType:
        (ADD, DRAFT, ACTIVE, PAUSED, ERROR, COMPLETE) = range(6)

    state = Property(type=int, default=StateType.DRAFT)
    position = Property(type=int, default=0)
    size = Property(type=int, default=0)
    info = Property(type=object)
    uuid = Property(type=str, default="")
    progress = Property(type=float, default=0.0)
    warning = Property(type=str, default="")
    title = Property(type=str, default="Add run ...")
    subtitle = Property(type=str, default="")
    created = Property(type=float, default=0.0)

    def __init__(self, info=None, state=StateType.DRAFT, uid=None, created=None):
        super().__init__()
        self.connect('notify::info', self.info_changed)
        self.props.created = created if created else time.time()
        self.props.uuid = uid if uid else str(uuid.uuid4())
        self.props.state = state
        self.props.info = info

    def info_changed(self, *args, **kwargs):
        if self.props.info:
            self.props.size = datatools.count_frames(self.props.info)
            self.props.title = '{}_{:04d}, ...'.format(self.info['name'], self.info['first'])
            self.props.subtitle = '{}f {:0.4g}°/{:0.2g}s  @ {:0.5g} keV {}'.format(
                self.props.size, self.props.info.get('delta'), self.props.info.get('exposure'),
                self.props.info.get('energy'),
                '[INV]' if self.props.info.get('inverse') else ''
            )

    def set_progress(self, progress):
        state = self.props.state

        if state == RunItem.StateType.ADD:
            return False

        self.props.progress = progress
        if progress >= 0.95:
            self.props.state = RunItem.StateType.COMPLETE
        return state != self.props.state  # return True if state changed

    @staticmethod
    def sorter(a_pointer, b_pointer):
        # if objects correctly translated do not translate again
        if isinstance(a_pointer, RunItem):
            a = a_pointer
            b = b_pointer
        else:
            a = glibref.capi.to_object(a_pointer)
            b = glibref.capi.to_object(b_pointer)

        if a.props.state == b.props.state == RunItem.StateType.ADD:
            return 0
        elif a.props.state == RunItem.StateType.ADD:
            return 1
        elif b.props.state == RunItem.StateType.ADD:
            return -1
        else:
            if a.props.created > b.props.created:
                return 1
            elif a.props.created < b.props.created:
                return -1
            else:
                return 0

    def get_color(self):
        return Gdk.RGBA(*STATE_COLORS[self.state])

    def __getitem__(self, item):
        if self.props.info:
            return self.props.info[item]

    def __str__(self):
        return '<Run Item: {} - {}|{}>'.format(self.props.position, self.props.title, self.props.subtitle)