Ejemplo n.º 1
0
 def expose(self, t, name, frametype='frame', trigger_duration=None):
     """Request an exposure at the given time. A trigger will be produced by the
     parent trigger object, with duration trigger_duration, or if not specified, of
     self.trigger_duration. The frame should have a `name, and optionally a
     `frametype`, both strings. These determine where the image will be stored in the
     hdf5 file. `name` should be a description of the image being taken, such as
     "insitu_absorption" or "fluorescence" or similar. `frametype` is optional and is
     the type of frame being acquired, for imaging methods that involve multiple
     frames. For example an absorption image of atoms might have three frames:
     'probe', 'atoms' and 'background'. For this one might call expose three times
     with the same name, but three different frametypes.
     """
     # Backward compatibility with code that calls expose with name as the first
     # argument and t as the second argument:
     if isinstance(t, str) and isinstance(name, (int, float)):
         msg = """expose() takes `t` as the first argument and `name` as the second
             argument, but was called with a string as the first argument and a
             number as the second. Swapping arguments for compatibility, but you are
             advised to modify your code to the correct argument order."""
         print(dedent(msg), file=sys.stderr)
         t, name = name, t
     if trigger_duration is None:
         trigger_duration = self.trigger_duration
     if trigger_duration is None:
         msg = """%s %s has not had an trigger_duration set as an instantiation
             argument, and none was specified for this exposure"""
         raise ValueError(dedent(msg) % (self.description, self.name))
     if not trigger_duration > 0:
         msg = "trigger_duration must be > 0, not %s" % str(trigger_duration)
         raise ValueError(msg)
     self.trigger(t, trigger_duration)
     self.exposures.append((t, name, frametype, trigger_duration))
     return trigger_duration
Ejemplo n.º 2
0
def serialise_function(function, *args, **kwargs):
    """Serialise a function based on its source code, and serialise the additional args
    and kwargs that it will be called with. Raise an exception if the function signature
    does not begin with (shot_context, t) or if the additional args and kwargs are
    incompatible with the rest of the function signature"""
    signature = inspect.signature(function)
    if not tuple(signature.parameters)[:2] == ('shot_context', 't'):
        msg = """function must be defined with (shot_context, t, ...) as its first two
            arguments"""
        raise ValueError(dedent(msg))
    # This will raise an error if the arguments do not match the function's call
    # signature:
    _ = signature.bind(None, None, *args, **kwargs)

    # Enure it's a bona fide function and not some other callable:
    if not isinstance(function, FunctionType):
        msg = f"""callable of type {type(function)} is not a function. Only functions
            can be used, not other callables"""
        raise TypeError(dedent(msg))

    # Serialise the function, args and kwargs:
    source = textwrap.dedent(inspect.getsource(function))
    args = serialise(args)
    kwargs = serialise(kwargs)

    return function.__name__, source, args, kwargs
Ejemplo n.º 3
0
    def __init__(
        self,
        config_path=default_config_path,
        required_params=None,
        defaults=None,
    ):
        if required_params is None:
            required_params = {}
        if defaults is None:
            defaults = {}
        defaults['labscript_suite'] = LABSCRIPT_SUITE_PROFILE
        if isinstance(config_path, list):
            self.config_path = config_path[0]
        else:
            self.config_path = config_path

        self.file_format = ""
        for section, options in required_params.items():
            self.file_format += "[%s]\n" % section
            for option in options:
                self.file_format += "%s = <value>\n" % option

        # Load the config file
        configparser.ConfigParser.__init__(self,
                                           defaults=defaults,
                                           interpolation=EnvInterpolation())
        # read all files in the config path if it is a list (self.config_path only
        # contains one string):
        self.read(config_path)

        # Rename experiment_name to apparatus_name and raise a DeprectionWarning
        experiment_name = self.get("DEFAULT", "experiment_name", fallback=None)
        if experiment_name:
            msg = """The experiment_name keyword has been renamed apparatus_name in
                labscript_utils 3.0, and will be removed in a future version. Please
                update your labconfig to use the apparatus_name keyword."""
            warnings.warn(dedent(msg), FutureWarning)
            if self.get("DEFAULT", "apparatus_name", fallback=None):
                msg = """You have defined both experiment_name and apparatus_name in
                    your labconfig. Please omit the deprecate experiment_name
                    keyword."""
                raise Exception(dedent(msg))
            else:
                self.set("DEFAULT", "apparatus_name", experiment_name)

        try:
            for section, options in required_params.items():
                for option in options:
                    self.get(section, option)
        except configparser.NoOptionError:
            msg = f"""The experiment configuration file located at {config_path} does
                not have the required keys. Make sure the config file contains the
                following structure:\n{self.file_format}"""
            raise Exception(dedent(msg))
Ejemplo n.º 4
0
 def _check_even_children(self, analogs, digitals):
     """Check that there are an even number of children of each type."""
     errmsg = """{0} {1} must have an even number of {2}s in order to guarantee an
         even total number of samples, which is a limitation of the DAQmx library.
         Please add a dummy {2} device or remove one you're not using, so that there
         is an even number."""
     if len(analogs) % 2:
         msg = errmsg.format(self.description, self.name, 'analog output')
         raise LabscriptError(dedent(msg))
     if len(digitals) % 2:
         msg = errmsg.format(self.description, self.name, 'digital output')
         raise LabscriptError(dedent(msg))
Ejemplo n.º 5
0
def register_plot_class(identifier, cls):
    if not spinning_top:
        msg = """Warning: lyse.register_plot_class has no effect on scripts not run with
            the lyse GUI.
            """
        sys.stderr.write(dedent(msg))
    _plot_classes[identifier] = cls
    def __init__(self,
                 port=None,
                 dtype='pyobj',
                 pull_only=False,
                 bind_address='tcp://0.0.0.0',
                 timeout_interval=None,
                 **kwargs):
        # There are ways to process args and exclude the keyword arguments we disallow
        # without having to include the whole function signature above, but they are
        # Python 3 only, so we avoid them for now.
        msg = """keyword argument {} not allowed - it will be set according to
            LabConfig. To make a custom ZMQServer, use zprocess.ZMQserver instead of
            labscript_utils.zprocess.ZMQServer"""

        # Error if these args are provided, since we provide them:
        for kwarg in ['shared_secret', 'allow_insecure']:
            if kwarg in kwargs:
                raise ValueError(dedent(msg.format(kwarg)))

        config = get_config()
        shared_secret = config['shared_secret']
        allow_insecure = config['allow_insecure']

        zprocess.ZMQServer.__init__(self,
                                    port=port,
                                    dtype=dtype,
                                    pull_only=pull_only,
                                    bind_address=bind_address,
                                    shared_secret=shared_secret,
                                    allow_insecure=allow_insecure,
                                    timeout_interval=timeout_interval,
                                    **kwargs)
 def _decode_image_data(self, img):
     """Formats returned FlyCapture2 API image buffers.
     
     FlyCapture2 image buffers require significant formatting.
     This returns what one would expect from a camera.
     :obj:`configure_acquisition` must be called first to set image format parameters.
     
     Args:
         img (numpy.array): A 1-D array image buffer of uint8 values to format
         
     Returns:
         numpy.array: Formatted array based on :obj:`width`, :obj:`height`, 
             and :obj:`pixelFormat`.
     """
     pix_fmt = self.pixelFormat
     if pix_fmt.startswith('MONO'):
         if pix_fmt.endswith('8'):
             dtype = 'uint8'
         else:
             dtype = 'uint16'
         image = np.frombuffer(img,
                               dtype=dtype).reshape(self.height, self.width)
     else:
         msg = """Only MONO image types currently supported.
         To add other image types, add conversion logic from returned 
         uint8 data to desired format in _decode_image_data() method."""
         raise ValueError(dedent(msg))
     return image.copy()
Ejemplo n.º 8
0
    def __getitem__(self, name):
        try:
            # Ensure the module's code has run (this does not re-import it if it is already in sys.modules)
            importlib.import_module('.' + name, __name__)
        except ImportError:
            msg = """No %s registered for a device named %s. Ensure that there is a file
                'register_classes.py' with a call to
                labscript_devices.register_classes() for this device, with the device
                name passed to register_classes() matching the name of the device class.

                Fallback method of looking for and importing a module in
                labscript_devices with the same name as the device also failed. If using
                this method, check that the module exists, has the same name as the
                device class, and can be imported with no errors. Import error
                was:\n\n"""
            msg = dedent(msg) % (self.instancename,
                                 name) + traceback.format_exc()
            raise ImportError(msg)
        # Class definitions in that module have executed now, check to see if class is in our register:
        try:
            return self.registered_classes[name]
        except KeyError:
            # No? No such class is defined then, or maybe the user forgot to decorate it.
            raise ValueError(
                'No class decorated as a %s found in module %s, ' %
                (self.instancename, __name__ + '.' + name) +
                'Did you forget to decorate the class definition with @%s?' %
                (self.instancename))
 def wait_monitor(self):
     try:
         # Read edge times from the counter input task, indiciating the times of the
         # pulses that occur at the start of the experiment and after every wait. If a
         # timeout occurs, pulse the timeout output to force a resume of the master
         # pseudoclock. Save the resulting
         self.logger.debug('Wait monitor thread starting')
         with self.kill_lock:
             self.logger.debug('Waiting for start of experiment')
             # Wait for the pulse indicating the start of the experiment:
             if self.incomplete_sample_detection:
                 semiperiods = self.read_edges(1, timeout=None)
             else:
                 semiperiods = self.read_edges(2, timeout=None)
             self.logger.debug('Experiment started, got edges:' +
                               str(semiperiods))
             # May have been one or two edges, depending on whether the device has
             # incomplete sample detection. We are only interested in the second one
             # anyway, it tells us how long the initial pulse was. Store the pulse width
             # for later, we will use it for making timeout pulses if necessary. Note
             # that the variable current_time is labscript time, so it will be reset
             # after each wait to the time of that wait plus pulse_width.
             current_time = pulse_width = semiperiods[-1]
             self.semiperiods.append(semiperiods[-1])
             # Alright, we're now a short way into the experiment.
             for wait in self.wait_table:
                 # How long until when the next wait should timeout?
                 timeout = wait['time'] + wait['timeout'] - current_time
                 timeout = max(timeout, 0)  # ensure non-negative
                 # Wait that long for the next pulse:
                 self.logger.debug(
                     'Waiting for pulse indicating end of wait')
                 semiperiods = self.read_edges(2, timeout)
                 # Did the wait finish of its own accord, or time out?
                 if semiperiods is None:
                     # It timed out. Better trigger the clock to resume!
                     msg = """Wait timed out; retriggering clock with {:.3e} s pulse
                         ({} edge)"""
                     msg = dedent(msg).format(pulse_width,
                                              self.timeout_trigger_type)
                     self.logger.debug(msg)
                     self.send_resume_trigger(pulse_width)
                     # Wait for it to respond to that:
                     self.logger.debug(
                         'Waiting for pulse indicating end of wait')
                     semiperiods = self.read_edges(2, timeout=None)
                 # Alright, now we're at the end of the wait.
                 self.semiperiods.extend(semiperiods)
                 self.logger.debug('Wait completed')
                 current_time = wait['time'] + semiperiods[-1]
                 # Inform any interested parties that a wait has completed:
                 postdata = _ensure_str(wait['label'])
                 self.wait_completed.post(self.h5_file, data=postdata)
             # Inform any interested parties that waits have all finished:
             self.logger.debug('All waits finished')
             self.all_waits_finished.post(self.h5_file)
     except Exception:
         self.logger.exception('Exception in wait monitor thread:')
         # Save the exception so it can be raised in transition_to_manual
         self.wait_monitor_thread_exception = sys.exc_info()
Ejemplo n.º 10
0
def delay_results_return():
    global _delay_flag
    if not spinning_top:
        msg = """Warning: lyse.delay_results_return has no effect on scripts not run 
            with the lyse GUI.
            """
        sys.stderr.write(dedent(msg))
    _delay_flag = True
Ejemplo n.º 11
0
def split_conn_AI(connection):
    """Return analog input number of a connection string such as 'ai1' as an
    integer, or raise ValueError if format is invalid"""
    try:
        return int(connection.split('ai', 1)[1])
    except (ValueError, IndexError):
        msg = """Analog input connection string %s does not match format 'ai<N>' for
            integer N"""
        raise ValueError(dedent(msg) % str(connection))
Ejemplo n.º 12
0
 def _check_wait_monitor_timeout_device_config(self):
     """Check that if we are the wait monitor acquisition device and another device
     is the wait monitor timeout device, that a) the other device is a DAQmx device
     and b) the other device has a start_order lower than us and a stop_order higher
     than us."""
     if compiler.wait_monitor is None:
         return
     acquisition_device = compiler.wait_monitor.acquisition_device
     timeout_device = compiler.wait_monitor.timeout_device
     if acquisition_device is not self or timeout_device is None:
         return
     if timeout_device is self:
         return
     if not isinstance(timeout_device, NI_DAQmx):
         msg = """If using an NI DAQmx device as a wait monitor acquisition device,
             then the wait monitor timeout device must also be an NI DAQmx device,
             not {}."""
         raise TypeError(dedent(msg).format(type(timeout_device)))
     timeout_start = timeout_device.start_order
     if timeout_start is None:
         timeout_start = 0
     timeout_stop = timeout_device.stop_order
     if timeout_stop is None:
         timeout_stop = 0
     self_start = self.start_order
     if self_start is None:
         self_start = 0
     self_stop = self.stop_order
     if self_stop is None:
         self_stop = 0
     if timeout_start >= self_start or timeout_stop <= self_stop:
         msg = """If using different DAQmx devices as the wait monitor acquisition
             and timeout devices, the timeout device must transition_to_buffered
             before the acquisition device, and transition_to_manual after it, in
             order to ensure the output port for timeout pulses is not in use (by the
             manual mode DO task) when the wait monitor subprocess attempts to use
             it. To achieve this, pass the start_order and stop_order keyword
             arguments to the devices in your connection table, ensuring that the
             timeout device has a lower start_order and a higher stop_order than the
             acquisition device. The default start_order and stop_order is zero, so
             if you are not otherwise controlling the order that devices are
             programmed, you can set start_order=-1, stop_order=1 on the timeout
             device only."""
         raise RuntimeError(dedent(msg))
    def __init__(self, com_port):
        global zaber
        try:
            import zaber.serial as zaber
        except ImportError:
            msg = """Could not import zaber.serial module. Please ensure it is
                installed. It is installable via pip with 'pip install zaber.serial'"""
            raise ImportError(dedent(msg))

        self.port = zaber.BinarySerial(com_port)
Ejemplo n.º 14
0
 def wrapper(*args, **kwargs):
     if not cls:
         cls.append(import_class_by_fullname(fullname))
         shortname = fullname.split('.')[-1]
         newmodule = '.'.join(fullname.split('.')[:-1])
         msg = """Importing %s from %s is deprecated, please instead import it from
            %s. Importing anyway for backward compatibility, but this may cause some
            unexpected behaviour."""
         msg = dedent(msg) % (shortname, calling_module_name, newmodule)
         warnings.warn(msg, stacklevel=2)
     return cls[0](*args, **kwargs)
Ejemplo n.º 15
0
 def init(self):
     """Initializes basic worker and opens VISA connection to device.
     
     Default connection timeout is 2 seconds"""
     self.VISA_name = self.address
     self.resourceMan = visa.ResourceManager()
     try:
         self.connection = self.resourceMan.open_resource(self.VISA_name)
     except visa.VisaIOError:
         msg = '''{:s} not found! Is it connected?'''.format(self.VISA_name)
         raise LabscriptError(dedent(msg)) from None
     self.connection.timeout = 2000
Ejemplo n.º 16
0
def get_device_number(connection_str):
    """Return the integer device number from the connection string or raise ValueError
    if the connection string is not in the format "device <n>" with positive n."""
    try:
        prefix, num = connection_str.split(' ')
        num = int(num)
        if prefix != 'device' or num <= 0:
            raise ValueError
    except (TypeError, ValueError):
        msg = f"""Connection string '{connection_str}' not in required format 'device
            <n>' with n > 0"""
        raise ValueError(dedent(msg)) from None
    return num
    def check_version(self):
        """Check the version of PyDAQmx is high enough to avoid a known bug"""
        major = uInt32()
        minor = uInt32()
        patch = uInt32()
        DAQmxGetSysNIDAQMajorVersion(major)
        DAQmxGetSysNIDAQMinorVersion(minor)
        DAQmxGetSysNIDAQUpdateVersion(patch)

        if major.value == 14 and minor.value < 2:
            msg = """There is a known bug with buffered shots using NI DAQmx v14.0.0.
                This bug does not exist on v14.2.0. You are currently using v%d.%d.%d.
                Please ensure you upgrade to v14.2.0 or higher."""
            raise Exception(dedent(msg) % (major.value, minor.value, patch.value))
    def transition_to_manual(self, abort=False):
        self.logger.debug('transition_to_manual')
        #  If we were doing buffered mode acquisition, stop the buffered mode task and
        # start the manual mode task. We might not have been doing buffered mode
        # acquisition if abort() was called when we are not in buffered mode, or if
        # there were no acuisitions this shot.
        if not self.buffered_mode:
            return True
        if self.buffered_chans is not None:
            self.stop_task()
        self.buffered_mode = False
        self.logger.info('transitioning to manual mode, task stopped')
        self.start_task(self.manual_mode_chans, self.manual_mode_rate)

        if abort:
            self.acquired_data = None
            self.buffered_chans = None
            self.h5_file = None
            self.buffered_rate = None
            return True

        with h5py.File(self.h5_file, 'a') as hdf5_file:
            data_group = hdf5_file['data']
            data_group.create_group(self.device_name)
            waits_in_use = len(hdf5_file['waits']) > 0

        if self.buffered_chans is not None and not self.acquired_data:
            msg = """No data was acquired. Perhaps the acquisition task was not
                triggered to start, is the device connected to a pseudoclock?"""
            raise RuntimeError(dedent(msg))
        # Concatenate our chunks of acquired data and recast them as a structured
        # array with channel names:
        if self.acquired_data:
            start_time = time.time()
            dtypes = [(chan, np.float32) for chan in self.buffered_chans]
            raw_data = np.concatenate(self.acquired_data).view(dtypes)
            raw_data = raw_data.reshape((len(raw_data), ))
            self.acquired_data = None
            self.buffered_chans = None
            self.extract_measurements(raw_data, waits_in_use)
            self.h5_file = None
            self.buffered_rate = None
            msg = 'data written, time taken: %ss' % str(time.time() -
                                                        start_time)
        else:
            msg = 'No acquisitions in this shot.'
        self.logger.info(msg)

        return True
Ejemplo n.º 19
0
 def _check_AI_not_too_fast(self, AI_table):
     if AI_table is None:
         return
     n = len(set(AI_table['connection']))
     if n < 2:
         # Either no AI in use, or already checked against single channel rate in
         # __init__.
         return
     if self.acquisition_rate <= self.max_AI_multi_chan_rate / n:
         return
     msg = """Requested acqusition_rate %f for device %s with %d analog input
         channels in use is too fast. Device supports a rate of %f per channel when
         multiple channels are in use."""
     msg = msg % (self.acquisition_rate, self.name, n,
                  self.max_AI_multi_chan_rate)
     raise ValueError(dedent(msg))
    def set_image_mode(self, image_settings):
        """Configures ROI and image control via Format 7, Mode 0 interface.
        
        Args:
            image_settings (dict): dictionary of image settings. Allowed keys:
                
                * 'pixelFormat': valid pixel format string, i.e. 'MONO8'
                * 'offsetX': int
                * 'offsetY': int
                * 'width': int
                * 'height': int
        """
        image_info, supported = self.camera.getFormat7Info(0)
        Hstep = image_info.offsetHStepSize
        Vstep = image_info.offsetVStepSize
        image_dict = image_settings.copy()
        if supported:
            image_mode, packetSize, percentage = self.camera.getFormat7Configuration(
            )
            image_mode.mode = 0

            # validate and set the ROI settings
            # this rounds the ROI settings to nearest allowed pixel
            if 'offsetX' in image_dict:
                image_dict['offsetX'] -= image_dict['offsetX'] % Hstep
            if 'offsetY' in image_dict:
                image_dict['offsetY'] -= image_dict['offsetY'] % Vstep
            if 'width' in image_dict:
                image_dict['width'] -= image_dict['width'] % Hstep
            if 'height' in image_dict:
                image_dict['height'] -= image_dict['height'] % Vstep

            # need to set pixel format separately to get correct enum value
            if 'pixelFormat' in image_dict:
                fmt = image_dict.pop('pixelFormat')
                image_mode.pixelFormat = self.pixel_formats[fmt].value

            for k, v in image_dict.items():
                setattr(image_mode, k, v)

            self._send_format7_config(image_mode)

        else:
            msg = """Camera does not support Format7, Mode 0 custom image
            configuration. This driver is therefore not compatible, as written."""
            raise RuntimeError(dedent(msg))
Ejemplo n.º 21
0
 def _decode_image_data(self, img):
     """Spinnaker image buffers require significant formatting.
     This returns what one would expect from a camera.
     configure_acquisition must be called first to set image format parameters."""
     if self.pix_fmt.startswith('Mono'):
         if self.pix_fmt.endswith('8'):
             dtype = 'uint8'
         else:
             dtype = 'uint16'
         image = np.frombuffer(img,
                               dtype=dtype).reshape(self.height, self.width)
     else:
         msg = """Only MONO image types currently supported.
         To add other image types, add conversion logic from returned
         uint8 data to desired format in _decode_image_data() method."""
         raise ValueError(dedent(msg))
     return image.copy()
Ejemplo n.º 22
0
def split_conn_DO(connection):
    """Return the port and line number of a connection string such as 'port0/line1 as
    two integers, or raise ValueError if format is invalid. Accepts connection strings
    such as port1/line0 (PFI0) - the PFI bit is just ignored"""
    try:
        if len(connection.split()) == 2:
            # Just raise a ValueError if the second bit isn't of the form ('PFI<n>')
            connection, PFI_bit = connection.split()
            if not (PFI_bit.startswith('(') and PFI_bit.endswith(')')):
                raise ValueError
            split_conn_PFI(PFI_bit[1:-1])
        port, line = [int(n) for n in connection.split('port', 1)[1].split('/line')]
    except (ValueError, IndexError):
        msg = """Digital output connection string %s does not match format
            'port<N>/line<M>' for integers N, M"""
        raise ValueError(dedent(msg) % str(connection))
    return port, line
Ejemplo n.º 23
0
def reformat_files(filepaths):
    """Apply black formatter to a list of source files"""
    try:
        import black
    except ImportError:
        msg = """Cannot import code formatting library 'black'. Generated labscript
            device code may be poorly formatted. Install black (Python 3.6+ only) via
            pip and run again to produce better formatted files"""
        warnings.warn(dedent(msg))
        return

    from click.testing import CliRunner

    runner = CliRunner()
    result = runner.invoke(black.main, ["-S"] + filepaths)
    print(result.output)
    assert result.exit_code == 0, result.output
Ejemplo n.º 24
0
    def set_image_mode(self, image_settings):
        """Configures ROI and image control via Format 7, Mode 0 interface."""
        image_info, supported = self.camera.getFormat7Info(0)
        Hstep = image_info.offsetHStepSize
        Vstep = image_info.offsetVStepSize
        image_dict = image_settings.copy()
        if supported:
            image_mode, packetSize, percentage = self.camera.getFormat7Configuration(
            )
            image_mode.mode = 0

            # validate and set the ROI settings
            # this rounds the ROI settings to nearest allowed pixel
            if 'offsetX' in image_dict:
                image_dict['offsetX'] -= image_dict['offsetX'] % Hstep
            if 'offsetY' in image_dict:
                image_dict['offsetY'] -= image_dict['offsetY'] % Vstep
            if 'width' in image_dict:
                image_dict['width'] -= image_dict['width'] % Hstep
            if 'height' in image_dict:
                image_dict['height'] -= image_dict['height'] % Vstep

            # need to set pixel format separately to get correct enum value
            if 'pixelFormat' in image_dict:
                fmt = image_dict.pop('pixelFormat')
                image_mode.pixelFormat = self.pixel_formats[fmt].value

            for k, v in image_dict.items():
                setattr(image_mode, k, v)

            try:
                fmt7PktInfo, valid = self.camera.validateFormat7Settings(
                    image_mode)
                if valid:
                    self.camera.setFormat7ConfigurationPacket(
                        fmt7PktInfo.recommendedBytesPerPacket, image_mode)
            except PyCapture2.Fc2error as e:
                raise RuntimeError('Error configuring image settings') from e
        else:
            msg = """Camera does not support Format7, Mode 0 custom image
            configuration. This driver is therefore not compatible, as written."""
            raise RuntimeError(dedent(msg))
Ejemplo n.º 25
0
    def _make_analog_input_table(self, inputs):
        """Collect analog input instructions and create the acquisition table"""
        if not inputs:
            return None
        acquisitions = []
        for connection, input in inputs.items():
            for acq in input.acquisitions:
                acquisitions.append((
                    connection,
                    acq['label'],
                    acq['start_time'],
                    acq['end_time'],
                    acq['wait_label'],
                    acq['scale_factor'],
                    acq['units'],
                ))
        if acquisitions and compiler.wait_table and compiler.wait_monitor is None:
            msg = """Cannot do analog input on an NI DAQmx device in an experiment that
                uses waits without a wait monitor. This is because input data cannot be
                'chunked' into requested segments without knowledge of the durations of
                the waits. See labscript.WaitMonitor for details."""
            raise LabscriptError(dedent(msg))

        # The 'a256' dtype below limits the string fields to 256
        # characters. Can't imagine this would be an issue, but to not
        # specify the string length (using dtype=str) causes the strings
        # to all come out empty.
        acquisitions_table_dtypes = [
            ('connection', 'a256'),
            ('label', 'a256'),
            ('start', float),
            ('stop', float),
            ('wait label', 'a256'),
            ('scale factor', float),
            ('units', 'a256'),
        ]
        acquisition_table = np.empty(len(acquisitions),
                                     dtype=acquisitions_table_dtypes)
        for i, acq in enumerate(acquisitions):
            acquisition_table[i] = acq

        return acquisition_table
Ejemplo n.º 26
0
def register_classes(labscript_device_name, BLACS_tab=None, runviewer_parser=None):
    """Register the name of the BLACS tab and/or runviewer parser that belong to a
    particular labscript device. labscript_device_name should be a string of just the
    device name, i.e. "DeviceName". BLACS_tab_fullname and runviewer_parser_fullname
    should be strings containing the fully qualified import paths for the BLACS tab and
    runviewer parser classes, such as "labscript_devices.DeviceName.DeviceTab" and
    "labscript_devices.DeviceName.DeviceParser". These need not be in the same module as
    the device class as in this example, but should be within labscript_devices. This
    function should be called from a file called "register_classes.py" within a
    subfolder of labscript_devices. When BLACS or runviewer start up, they will call
    populate_registry(), which will find and run all such files to populate the class
    registries prior to looking up the classes they need"""
    if labscript_device_name in _register_classes_script_files:
        other_script =_register_classes_script_files[labscript_device_name]
        msg = """A device named %s has already been registered by the script %s.
            Labscript devices must have unique names."""
        raise ValueError(dedent(msg) % (labscript_device_name, other_script))
    BLACS_tab_registry[labscript_device_name] = BLACS_tab
    runviewer_parser_registry[labscript_device_name] = runviewer_parser
    script_filename = os.path.abspath(inspect.stack()[1][0].f_code.co_filename)
    _register_classes_script_files[labscript_device_name] = script_filename
Ejemplo n.º 27
0
 def _check_bounds(self, analogs):
     """Check that all analog outputs are in bounds"""
     if not analogs:
         return
     vmin, vmax = self.AO_range
     # Floating point rounding error can produce values that would mathematically be
     # within bounds, but have ended up numerically out of bounds. We allow
     # out-of-bounds values within a small threshold through, but apply clipping to
     # keep them numerically within bounds. 1e-10 of the total range corresponds to >
     # 32 bits of precision, so this is not changing the voltages at all since none
     # of the DACs are that precise.
     eps = abs(vmax - vmin) * 1e-10
     for output in analogs.values():
         if any((output.raw_output < vmin - eps)
                | (output.raw_output > vmax + eps)):
             msg = """%s %s can only have values between %e and %e Volts, the limit
                 imposed by %s."""
             msg = msg % (output.description, output.name, vmin, vmax,
                          self.name)
             raise LabscriptError(dedent(msg))
         np.clip(output.raw_output, vmin, vmax, out=output.raw_output)
    def trigger(self, t, duration):
        """Request parent trigger device to produce a trigger at time t with given
        duration."""
        # Only ask for a trigger if one has not already been requested by another device
        # attached to the same trigger:
        already_requested = False
        for other_device in self.trigger_device.child_devices:
            if other_device is not self:
                for other_t, other_duration in other_device.__triggers:
                    if t == other_t and duration == other_duration:
                        already_requested = True
        if not already_requested:
            self.trigger_device.trigger(t, duration)

        # Check for triggers too close together (check for overlapping triggers already
        # performed in Trigger.trigger()):
        start = t
        end = t + duration
        for other_t, other_duration in self.__triggers:
            other_start = other_t
            other_end = other_t + other_duration
            if (abs(other_start - end) < self.minimum_recovery_time
                    or abs(other_end - start) < self.minimum_recovery_time):
                msg = """%s %s has two triggers closer together than the minimum
                    recovery time: one at t = %fs for %fs, and another at t = %fs for
                    %fs. The minimum recovery time is %fs."""
                msg = msg % (
                    self.description,
                    self.name,
                    t,
                    duration,
                    start,
                    duration,
                    self.minimum_recovery_time,
                )
                raise ValueError(dedent(msg))

        self.__triggers.append([t, duration])
    def __init__(self, serial_number):
        """Initialize FlyCapture2 API camera.
        
        Searches all cameras reachable by the host using the provided serial
        number. Fails with API error if camera not found.
        
        This function also does a significant amount of default configuration.
        
        * It defaults the grab timeout to 1 s
        * Ensures use of the API's HighPerformanceRetrieveBuffer
        * Ensures the camera is in Format 7, Mode 0 with full frame readout and MONO8 pixels
        * If using a GigE camera, automatically maximizes the packet size and warns if Jumbo packets are not enabled on the NIC
        
        Args:
            serial_number (int): serial number of camera to connect to
        """

        global PyCapture2
        import PyCapture2

        ver = PyCapture2.getLibraryVersion()
        min_ver = (2, 12, 3, 31)  # first release with python 3.6 support
        if ver < min_ver:
            raise RuntimeError(
                f"PyCapture2 version {ver} must be >= {min_ver}")

        print('Connecting to SN:%d ...' % serial_number)
        bus = PyCapture2.BusManager()
        self.camera = PyCapture2.Camera()
        self.camera.connect(bus.getCameraFromSerialNumber(serial_number))

        # set which values of properties to return
        self.get_props = [
            'present', 'absControl', 'absValue', 'onOff', 'autoManualMode',
            'valueA', 'valueB'
        ]

        fmts = {
            prop: getattr(PyCapture2.PIXEL_FORMAT, prop)
            for prop in dir(PyCapture2.PIXEL_FORMAT)
            if not prop.startswith('_')
        }

        self.pixel_formats = IntEnum('pixel_formats', fmts)

        self._abort_acquisition = False

        # check if GigE camera. If so, ensure max packet size is used
        cam_info = self.camera.getCameraInfo()
        if cam_info.interfaceType == PyCapture2.INTERFACE_TYPE.GIGE:
            # need to close generic camera first to avoid strange interactions
            print('Checking Packet size for GigE Camera...')
            self.camera.disconnect()
            gige_camera = PyCapture2.GigECamera()
            gige_camera.connect(bus.getCameraFromSerialNumber(serial_number))
            mtu = gige_camera.discoverGigEPacketSize()
            if mtu <= 1500:
                msg = """WARNING: Maximum Transmission Unit (MTU) for ethernet 
                NIC FlyCapture2_Camera SN:%d is connected to is only %d. 
                Reliable operation not expected. 
                Please enable Jumbo frames on NIC."""
                print(dedent(msg % (serial_number, mtu)))

            gige_pkt_size = gige_camera.getGigEProperty(
                PyCapture2.GIGE_PROPERTY_TYPE.GIGE_PACKET_SIZE)
            # only set if not already at correct value
            if gige_pkt_size.value != mtu:
                gige_pkt_size.value = mtu
                gige_camera.setGigEProperty(gige_pkt_size)
                print('  Packet size set to %d' % mtu)
            else:
                print('  GigE Packet size is %d' % gige_pkt_size.value)

            # close GigE handle to camera, re-open standard handle
            gige_camera.disconnect()
            self.camera.connect(bus.getCameraFromSerialNumber(serial_number))

        # set standard device configuration
        config = self.camera.getConfiguration()
        config.grabTimeout = 1000  # in ms
        config.highPerformanceRetrieveBuffer = True
        self.camera.setConfiguration(config)

        # ensure camera is in Format7,Mode 0 custom image mode
        fmt7_info, supported = self.camera.getFormat7Info(0)
        if supported:
            # to ensure Format7, must set custom image settings
            # defaults to full sensor size and 'MONO8' pixel format
            print('Initializing to default Format7, Mode 0 configuration...')
            fmt7_default = PyCapture2.Format7ImageSettings(
                0, 0, 0, fmt7_info.maxWidth, fmt7_info.maxHeight,
                self.pixel_formats['MONO8'].value)
            self._send_format7_config(fmt7_default)

        else:
            msg = """Camera does not support Format7, Mode 0 custom image
            configuration. This driver is therefore not compatible, as written."""
            raise RuntimeError(dedent(msg))
Ejemplo n.º 30
0
    def transition_to_manual(self):
        if self.h5_filepath is None:
            print('No camera exposures in this shot.\n')
            return True
        assert self.acquisition_thread is not None
        self.acquisition_thread.join(timeout=self.stop_acquisition_timeout)
        if self.acquisition_thread.is_alive():
            msg = """Acquisition thread did not finish. Likely did not acquire expected
                number of images. Check triggering is connected/configured correctly"""
            if self.exception_on_failed_shot:
                self.abort()
                raise RuntimeError(dedent(msg))
            else:
                self.camera.abort_acquisition()
                self.acquisition_thread.join()
                print(dedent(msg), file=sys.stderr)
        self.acquisition_thread = None

        print("Stopping acquisition.")
        self.camera.stop_acquisition()

        print(f"Saving {len(self.images)}/{len(self.exposures)} images.")

        with h5py.File(self.h5_filepath, 'r+') as f:
            # Use orientation for image path, device_name if orientation unspecified
            if self.orientation is not None:
                image_path = 'images/' + self.orientation
            else:
                image_path = 'images/' + self.device_name
            image_group = f.require_group(image_path)
            image_group.attrs['camera'] = self.device_name

            # Save camera attributes to the HDF5 file:
            if self.attributes_to_save is not None:
                set_attributes(image_group, self.attributes_to_save)

            # Whether we failed to get all the expected exposures:
            image_group.attrs['failed_shot'] = len(self.images) != len(
                self.exposures)

            # key the images by name and frametype. Allow for the case of there being
            # multiple images with the same name and frametype. In this case we will
            # save an array of images in a single dataset.
            images = {(exposure['name'], exposure['frametype']): []
                      for exposure in self.exposures}

            # Iterate over expected exposures, sorted by acquisition time, to match them
            # up with the acquired images:
            self.exposures.sort(order='t')
            for image, exposure in zip(self.images, self.exposures):
                images[(exposure['name'], exposure['frametype'])].append(image)

            # Save images to the HDF5 file:
            for (name, frametype), imagelist in images.items():
                data = imagelist[0] if len(imagelist) == 1 else np.array(
                    imagelist)
                print(f"Saving frame(s) {name}/{frametype}.")
                group = image_group.require_group(name)
                dset = group.create_dataset(frametype,
                                            data=data,
                                            dtype='uint16',
                                            compression='gzip')
                # Specify this dataset should be viewed as an image
                dset.attrs['CLASS'] = np.string_('IMAGE')
                dset.attrs['IMAGE_VERSION'] = np.string_('1.2')
                dset.attrs['IMAGE_SUBCLASS'] = np.string_('IMAGE_GRAYSCALE')
                dset.attrs['IMAGE_WHITE_IS_ZERO'] = np.uint8(0)

        # If the images are all the same shape, send them to the GUI for display:
        try:
            image_block = np.stack(self.images)
        except ValueError:
            print(
                "Cannot display images in the GUI, they are not all the same shape"
            )
        else:
            self._send_image_to_parent(image_block)

        self.images = None
        self.n_images = None
        self.attributes_to_save = None
        self.exposures = None
        self.h5_filepath = None
        self.stop_acquisition_timeout = None
        self.exception_on_failed_shot = None
        print("Setting manual mode camera attributes.\n")
        self.set_attributes_smart(self.manual_mode_camera_attributes)
        if self.continuous_dt is not None:
            # If continuous manual mode acquisition was in progress before the bufferd
            # run, resume it:
            self.start_continuous(self.continuous_dt)
        return True