Beispiel #1
0
    def _update_output(self):
        """ Runs the power monitor """

        # Check for/implement changes to settings
        #self.update_settings(0)

        # Get all current values
        try:
            p_in = self.pm.get_power(1)
            split_in = split(p_in)

        # Handle zero error
        except OverflowError:
            p_in = 0
            split_in = (0, 0)
        try:
            p_ref = self.pm.get_power(2)
            split_ref = split(p_ref)
        except OverflowError:
            p_ref = 0
            split_ref = (0, 0)
        try:
            efficiency = np.sqrt(p_ref / (p_in * self.calibration[0]))
        except ZeroDivisionError:
            efficiency = 0
        values = [p_in, p_ref, efficiency]

        # For the two power readings, reformat.
        # E.g., split(0.003) will return (3, -3)
        # And prefix(-3) will return 'm'
        formatted_values = [split_in[0], split_ref[0], efficiency]
        value_prefixes = [
            prefix(split_val[1]) for split_val in [split_in, split_ref]
        ]

        # Update GUI
        for plot_no in range(self.num_plots):
            # Update Number
            self.widgets['number_widget'][plot_no].setValue(
                formatted_values[plot_no])

            # Update Curve
            self.plotdata[plot_no] = np.append(self.plotdata[plot_no][1:],
                                               values[plot_no])
            self.widgets[f'curve_{plot_no}'].setData(self.plotdata[plot_no])

            if plot_no < 2:
                self.widgets["label_widget"][plot_no].setText(
                    f'{value_prefixes[plot_no]}W')
Beispiel #2
0
    def _on_channels_updated(message):
        '''
        Message keys:
            - ``"n"``: number of actuated channel
            - ``"actuated"``: list of actuated channel identifiers.
            - ``"start"``: ms counter before setting shift registers
            - ``"end"``: ms counter after setting shift registers
        '''
        global actuated_area
        global actuated_channels

        actuated_channels = message['actuated']

        if actuated_channels:
            actuated_electrodes = \
                dmf_device.actuated_electrodes(actuated_channels).dropna()
            actuated_areas = \
                dmf_device.electrode_areas.ix[actuated_electrodes.values]
            actuated_area = actuated_areas.sum()
        else:
            actuated_area = 0
        # m^2 area
        area = actuated_area * (1e-3**2)
        # Approximate area in SI units.
        value, pow10 = si.split(np.sqrt(area))
        si_unit = si.SI_PREFIX_UNITS[len(si.SI_PREFIX_UNITS) // 2 + pow10 // 3]
        status = ('actuated electrodes: %s (%.1f %sm^2)' %
                  (actuated_channels, value**2, si_unit))
        _L().debug(status)
        channels_updated.set()
Beispiel #3
0
def si(value, wrap_span_class=None):
    base, exp = split(int(value), precision=1)
    prefix_unit = ""

    if exp:
        prefix_unit = prefix(exp)

        if wrap_span_class:
            prefix_unit = f'<span class="{wrap_span_class}">{prefix_unit}</span>'

    if base == int(base):
        return mark_safe(f"{base:.0f}{prefix_unit}")
    else:
        return mark_safe(f"{base:.1f}{prefix_unit}")
Beispiel #4
0
    def scale_f(frequency):
        """Scale the axis and add the corresponding SI prefix.

        Arguments:
            frequency {np.ndarray} -- the variable along an axis

        Returns:
            str, np.ndarray, int -- the prefix, the scaled variables, the
                                    exponent corresponding to the prefix
        """
        freq = np.copy(frequency)
        exp = sip.split(np.max(freq))[1]
        freq /= 10 ** exp
        pre = sip.prefix(exp)
        return pre, freq, exp
Beispiel #5
0
def actuate(proxy,
            dmf_device,
            electrode_states,
            duration_s=0,
            volume_threshold=0,
            c_unit_area=None):
    '''
    XXX Coroutine XXX

    Actuate electrodes according to specified states.

    Parameters
    ----------
    electrode_states : pandas.Series
    duration_s : float, optional
        If ``volume_threshold`` step option is set, maximum duration before
        timing out.  Otherwise, time to actuate before actuation is
        considered completed.

    c_unit_area : float, optional
        Specific capacitance, i.e., units of $F/mm^2$.

    Returns
    -------
    actuated_electrodes : list
        List of actuated electrode IDs.


    .. versionchanged:: 2.39.0
        Do not save actuation uuid for volume threshold actuations.
    .. versionchanged:: 2.39.0
        Fix actuated area field typo.
    .. versionchanged:: 2.39.0
        Compute actuated area for static (i.e., delay-based) actuations.
    '''
    requested_electrodes = electrode_states[electrode_states > 0].index
    requested_channels = (
        dmf_device.channels_by_electrode.loc[requested_electrodes])

    actuated_channels = pd.Series()
    actuated_area = 0

    channels_updated = asyncio.Event()

    def _on_channels_updated(message):
        '''
        Message keys:
            - ``"n"``: number of actuated channel
            - ``"actuated"``: list of actuated channel identifiers.
            - ``"start"``: ms counter before setting shift registers
            - ``"end"``: ms counter after setting shift registers
        '''
        global actuated_area
        global actuated_channels

        actuated_channels = message['actuated']

        if actuated_channels:
            actuated_electrodes = \
                dmf_device.actuated_electrodes(actuated_channels).dropna()
            actuated_areas = \
                dmf_device.electrode_areas.ix[actuated_electrodes.values]
            actuated_area = actuated_areas.sum()
        else:
            actuated_area = 0
        # m^2 area
        area = actuated_area * (1e-3**2)
        # Approximate area in SI units.
        value, pow10 = si.split(np.sqrt(area))
        si_unit = si.SI_PREFIX_UNITS[len(si.SI_PREFIX_UNITS) // 2 + pow10 // 3]
        status = ('actuated electrodes: %s (%.1f %sm^2)' %
                  (actuated_channels, value**2, si_unit))
        _L().debug(status)
        channels_updated.set()

    proxy.signals.signal('channels-updated').connect(_on_channels_updated)

    # Criteria that must be met to set target capacitance.
    threshold_criteria = [
        duration_s > 0, volume_threshold > 0,
        len(requested_electrodes) > 0, c_unit_area is not None
    ]
    _L().debug('threshold_criteria: `%s`', threshold_criteria)

    result = {}

    actuated_areas = (
        dmf_device.electrode_areas.ix[requested_electrodes.values])
    actuated_area = actuated_areas.sum()

    if not all(threshold_criteria):
        # ## Case 1: no volume threshold specified.
        #  1. Set control board state of channels according to requested
        #     actuation states.
        #  2. Wait for channels to be actuated.
        actuated_channels = \
            db.threshold.actuate_channels(proxy, requested_channels, timeout=5)

        #  3. Connect to `capacitance-updated` signal to record capacitance
        #     values measured during the step.
        capacitance_messages = []

        def _on_capacitance_updated(message):
            message['actuated_channels'] = actuated_channels
            message['actuated_area'] = actuated_area
            capacitance_messages.append(message)

        proxy.signals.signal('capacitance-updated')\
            .connect(_on_capacitance_updated)
        #  4. Delay for specified duration.
        try:
            yield asyncio.From(asyncio.sleep(duration_s))
        finally:
            proxy.signals.signal('capacitance-updated')\
                .disconnect(_on_capacitance_updated)
    else:
        # ## Case 2: volume threshold specified.
        #
        # A volume threshold has been set for this step.

        # Calculate target capacitance based on actuated area.
        #
        # Note: `app_values['c_liquid']` represents a *specific
        # capacitance*, i.e., has units of $F/mm^2$.
        meters_squared_area = actuated_area * (1e-3**2)  # m^2 area
        # Approximate length of unit side in SI units.
        si_length, pow10 = si.split(np.sqrt(meters_squared_area))
        si_unit = si.SI_PREFIX_UNITS[len(si.SI_PREFIX_UNITS) // 2 + pow10 // 3]

        target_capacitance = volume_threshold * actuated_area * c_unit_area

        logger = _L()  # use logger with function context
        if logger.getEffectiveLevel() <= logging.DEBUG:
            message = ('target capacitance: %sF (actuated area: (%.1f '
                       '%sm^2) actuated channels: %s)' %
                       (si.si_format(target_capacitance), si_length**
                        2, si_unit, requested_channels))
            map(logger.debug, message.splitlines())
        # Wait for target capacitance to be reached in background thread,
        # timing out if the specified duration is exceeded.
        co_future = \
            db.threshold.co_target_capacitance(proxy,
                                               requested_channels,
                                               target_capacitance,
                                               allow_disabled=False,
                                               timeout=duration_s)
        try:
            dropbot_event = yield asyncio.From(
                asyncio.wait_for(co_future, duration_s))
            _L().debug('target capacitance reached: `%s`', dropbot_event)
            actuated_channels = dropbot_event['actuated_channels']

            capacitance_messages = dropbot_event['capacitance_updates']
            # Add actuated area to capacitance update messages.
            for capacitance_i in capacitance_messages:
                capacitance_i['actuated_area'] = actuated_area
                capacitance_i.pop('actuation_uuid1', None)

            result['threshold'] = {
                'target': dropbot_event['target'],
                'measured': dropbot_event['new_value'],
                'start': dropbot_event['start'],
                'end': dropbot_event['end']
            }

            # Show notification in main window status bar.
            if logger.getEffectiveLevel() <= logging.DEBUG:
                status = ('reached %sF (> %sF) over electrodes: %s (%.1f '
                          '%sm^2) after %ss' %
                          (si.si_format(result['threshold']['measured']),
                           si.si_format(result['threshold']['target']),
                           actuated_channels, si_length**2, si_unit,
                           (dropbot_event['end'] -
                            dropbot_event['start']).total_seconds()))
                logger.debug(status)
        except asyncio.TimeoutError:
            raise RuntimeError('Timed out waiting for target capacitance.')

    yield asyncio.From(channels_updated.wait())
    actuated_electrodes = (
        dmf_device.electrodes_by_channel.loc[actuated_channels])

    # Return list of actuated channels (which _may_ be fewer than the
    # number of requested actuated channels if one or more channels is
    # _disabled_).
    result.update({
        'actuated_electrodes': actuated_electrodes,
        'capacitance_messages': capacitance_messages,
        'actuated_channels': actuated_channels,
        'actuated_area': actuated_area
    })

    raise asyncio.Return(result)