Esempio n. 1
0
    def test_units(self):

        hdl = MemHandler()

        class Spam(Driver):
            _logger = get_logger('test.feat')
            _logger.addHandler(hdl)
            _logger.setLevel(logging.DEBUG)

            _eggs = {'answer': 42}

            @DictFeat(units='s')
            def eggs(self_, key):
                return self_._eggs[key]

            @eggs.setter
            def eggs(self_, key, value):
                self_._eggs[key] = value

        obj = Spam()
        self.assertQuantityEqual(obj.eggs['answer'], Q_(42, 's'))
        obj.eggs['answer'] = Q_(46, 'ms')
        self.assertQuantityEqual(obj.eggs['answer'], Q_(46 / 1000, 's'))

        with must_warn(DimensionalityWarning, 1) as msg:
            obj.eggs['answer'] = 42

        self.assertFalse(msg, msg=msg)
Esempio n. 2
0
 def test_action(self):
     obj = aDriver()
     self.assertEqual(obj.run(), 42)
     self.assertEqual(obj.run2(2), 42 * 2)
     self.assertEqual(obj.run3(3), 42 * 3)
     self.assertEqual(obj.run4(Q_(3, 'ms')), 3)
     self.assertEqual(obj.run4b(Q_(3, 'ms')), 3)
     self.assertEqual(obj.run4(Q_(3, 's')), 3000)
Esempio n. 3
0
 def at_pos(self,
            axis,
            pos,
            delta_z=Q_(0.1, 'um'),
            iter_n=10,
            delay=Q_(0.01, 's')):
     for i in range(iter_n):
         time.sleep(delay)
         if abs(self.position[axis].to('um').magnitude - pos) > delta_z:
             return False
     return True
Esempio n. 4
0
    def test_Self_exceptions(self):

        class X(Driver):

            @Feat(units=Self.units)
            def value(self):
                return 1

            @value.setter
            def value(self, value):
                pass

            @Feat()
            def units(self):
                return self._units

            @units.setter
            def units(self, value):
                self._units = value

        x = X()
        self.assertRaises(Exception, getattr, x, 'value')
        self.assertRaises(Exception, setattr, x, 'value', 1)
        x.units = 'ms'
        self.assertEqual(x.feats.value.units, 'ms')
        self.assertEqual(x.value, Q_(1, 'ms'))
Esempio n. 5
0
    def test_units_tuple(self):

        hdl = MemHandler()

        class Spam(Driver):
            _logger = get_logger('test.feat', False)
            _logger.addHandler(hdl)
            _logger.setLevel(logging.DEBUG)

            _eggs = (8, 1)

            # Transform each element of the return vector
            # based on the set signature
            @Feat(units=('s', 's'))
            def eggs(self_):
                return self_._eggs

            @eggs.setter
            def eggs(self_, values):
                self_._eggs = values

        class Spam2(Driver):
            _logger = get_logger('test.feat', False)
            _logger.addHandler(hdl)
            _logger.setLevel(logging.DEBUG)

            _eggs = (8, 1)

            # Transform each element of the return vector
            # based on the set signature
            @Feat(units=('s', None))
            def eggs(self_):
                return self_._eggs

            @eggs.setter
            def eggs(self_, values):
                self_._eggs = values

        obj = Spam()
        self.assertQuantityEqual(obj.eggs, (Q_(8, 's'),  Q_(1, 's')))
        self.assertEqual(setattr(obj, "eggs", (Q_(3, 'ms'), Q_(4, 'ms'))), None)
        self.assertQuantityEqual(obj.eggs, (Q_(3 / 1000, 's'), Q_(4 / 1000, 's')))

        with must_warn(DimensionalityWarning, 2) as msg:
            self.assertEqual(setattr(obj, "eggs", (3, 1)), None)
        self.assertFalse(msg, msg=msg)

        obj = Spam2()
        self.assertQuantityEqual(obj.eggs, (Q_(8, 's'),  1))
        self.assertEqual(setattr(obj, "eggs", (Q_(3, 'ms'), 4)), None)
        self.assertQuantityEqual(obj.eggs, (Q_(3 / 1000, 's'), 4))
Esempio n. 6
0
 def test_instance_specific(self):
     x = aDriver()
     y = aDriver()
     val = Q_(3, 's')
     self.assertEqual(x.run4(val), y.run4(val))
     x.actions.run4.param('value', units='s')
     self.assertNotEqual(x.run4(val), y.run4(val))
     self.assertEqual(x.run4(val), 3)
Esempio n. 7
0
    def assertQuantityEqual(self, q1, q2, msg=None, delta=None):
        """
        Make sure q1 and q2 are the same quantities to within the given
        precision.
        """

        delta = 1e-5 if delta is None else delta
        msg = '' if msg is None else ' (%s)' % msg

        q1 = Q_(q1)
        q2 = Q_(q2)

        d1 = getattr(q1, '_dimensionality', None)
        d2 = getattr(q2, '_dimensionality', None)
        if (d1 or d2) and not (d1 == d2):
            raise self.failureException(
                "Dimensionalities are not equal (%s vs %s)%s" % (d1, d2, msg))
Esempio n. 8
0
 def __init__(self,
              channel_dict=default_digi_dict,
              laser_time=150 * Q_(1, "us"),
              readout_time=150 * Q_(1, "us"),
              buffer_after_init=450 * Q_(1, "us"),
              buffer_after_readout=2 * Q_(1, "us"),
              polarize_time=900 * Q_(1, "us"),
              settle=150 * Q_(1, "us"),
              reset=100 * Q_(1, "ns"),
              IQ=[0.5, 0],
              ip="192.168.1.111"):
     """
     :param channel_dict: Dictionary of which channels correspond to which instr controls
     :param laser_time: Laser time in us
     :param CTR: When False CTR0 When True CTR1
     :param readout_time: Readout time in us
     :param buffer_after_init: Buffer after laser turns off in us to allow the population to thermalize
     :param buffer_after_readout: Buffer between the longest pulse and the readout in us to prevent laser to leak in
     :param IQ: IQ vector that rotates the spin
     """
     super().__init__()
     self.channel_dict = channel_dict
     self.laser_time = int(round(laser_time.to("ns").magnitude))
     self.readout_time = int(round(readout_time.to("ns").magnitude))
     self.buffer_after_init = int(
         round(buffer_after_init.to("ns").magnitude))
     self.buffer_after_readout = int(
         round(buffer_after_readout.to("ns").magnitude))
     self.polarize_time = int(round(polarize_time.to("ns").magnitude))
     self.settle = int(round(settle.to("ns").magnitude))
     self.reset = int(round(reset.to("ns").magnitude))
     self._normalize_IQ(IQ)
     self.Pulser = PulseStreamer(ip)
Esempio n. 9
0
 def relative_move(self, axis, delta):
     delta = Q_(delta, 'um')
     if abs(delta) > MAX_RELATIVE_MOVE:
         raise Exception(
             "Relative move <delta> is greater then the MAX_RELATIVE_MOVE")
     else:
         target = self.position + delta
         target = target.to('m').magnitude
         print(target)
Esempio n. 10
0
    def __init__(self, input_ch, trigger_ch, acq_time=Q_('10 ms'), pts=1000):
        self.task = AnalogInputTask('SA201')
        self.task.add_channel(VoltageInputChannel(input_ch))
        self.task.configure_trigger_digital_edge_start(trigger_ch,
                                                       edge='rising')
        self.acq_time = acq_time

        self.pts = self.pts
        return
Esempio n. 11
0
    def initialize(self, devNo=None):
        if not devNo is None: self.devNo = devNo
        device = c_void_p()
        self.check_error(self.lib.connect(self.dev_no, pointer(device)))
        self.device = device

        # Wait until we get something else then 0 on the position
        while (self.position[2] == Q_(0, 'um')):
            time.sleep(0.025)
Esempio n. 12
0
    def read(self, samples_per_channel=None, timeout=Q_(10.0, 's')):
        """Read multiple 32-bit integer samples from a counter task.
        Use this function when counter samples are returned unscaled,
        such as for edge counting.

        :param samples_per_channel:
          The number of samples, per channel, to read. The default
          value of -1 (DAQmx_Val_Auto) reads all available samples. If
          readArray does not contain enough space, this function
          returns as many samples as fit in readArray.

          NI-DAQmx determines how many samples to read based on
          whether the task acquires samples continuously or acquires a
          finite number of samples.

          If the task acquires samples continuously and you set this
          parameter to -1, this function reads all the samples
          currently available in the buffer.

          If the task acquires a finite number of samples and you set
          this parameter to -1, the function waits for the task to
          acquire all requested samples, then reads those samples. If
          you set the Read All Available Samples property to TRUE, the
          function reads the samples currently available in the buffer
          and does not wait for the task to acquire all requested
          samples.

        :param timeout:
          The amount of time, in seconds, to wait for the function to
          read the sample(s). The default value is 10.0 seconds. To
          specify an infinite wait, pass -1
          (DAQmx_Val_WaitInfinitely). This function returns an error
          if the timeout elapses.

          A value of 0 indicates to try once to read the requested
          samples. If all the requested samples are read, the function
          is successful. Otherwise, the function returns a timeout
          error and returns the samples that were actually read.


        :return: The array of samples read.
        """

        if samples_per_channel is None:
            samples_per_channel = self.samples_per_channel_available()

        data = np.zeros((samples_per_channel, ), dtype=np.int32)

        err, count = self.lib.ReadCounterU32(samples_per_channel,
                                             float(timeout.to('s').magnitude),
                                             data.ctypes.data, data.size,
                                             RetValue('i32'), None)

        return data[:count]
Esempio n. 13
0
    def test_set_units(self):

        class Spam(Driver):

            _eggs = 8

            @Feat(values={1, 2.2, 10}, units='second')
            def eggs(self_):
                return self_._eggs

            @eggs.setter
            def eggs(self_, value):
                self_._eggs = value

        obj = Spam()
        for mult, units in ((1., 'second'), (1000., 'millisecond'), (0.001, 'kilosecond')):
            val = Q_(2.2 * mult, units)
            obj.eggs = val
            self.assertEqual(obj.eggs, val)
            self.assertRaises(ValueError, setattr, obj, "eggs", Q_(11. * mult, units))
            self.assertRaises(ValueError, setattr, obj, "eggs", Q_(0.9 * mult, units))
Esempio n. 14
0
 def absolute_move(self, axis, target, max_move=MAX_ABSOLUTE_MOVE):
     if not max_move is None:
         if abs(self.position[axis] - Q_(target, 'm')) > max_move:
             raise Exception(
                 "Relative move (target-current) is greater then the max_move"
             )
     self.check_error(self.lib.setTargetPosition(self.device, axis, target))
     enable = 0x01
     relative = 0x00
     self.check_error(
         self.lib.startAutoMove(self.device, axis, enable, relative))
     return
Esempio n. 15
0
    def test_Self(self):

        class X(Driver):

            @Feat(units=Self.a_value_units('s'))
            def a_value(self):
                return 1

            @Feat()
            def a_value_units(self):
                return self._units

            @a_value_units.setter
            def a_value_units(self, new_units):
                self._units = new_units

        x = X()
        self.assertEqual(x.feats.a_value.units, 's')
        self.assertEqual(x.a_value, Q_(1, 's'))
        x.a_value_units = 'ms'
        self.assertEqual(x.feats.a_value.units, 'ms')
        self.assertEqual(x.a_value, Q_(1, 'ms'))
Esempio n. 16
0
def decode_data(data):
    if not msg_complete(data):
        raise Exception("Incomplete/Invalid data")
    length = data[:10]
    d = data[10:]
    obj = pickle.loads(d)

    # Special case to handle Quantity
    if repr(
            type(obj)
    ) == "<class 'pint.quantity.build_quantity_class.<locals>.Quantity'>":
        obj = Q_(str(obj))
    return obj
Esempio n. 17
0
    def get_units(units):
        """Creates and display a UnitInputDialog and return new units.

        Parameters
        ----------
        units : str
            current units.

        Returns
        -------
        str or None
            output compatible units.
            Returns None if cancelled.

        """

        if isinstance(units, str):
            units = Q_(1, units)

        dialog = UnitInputDialog(Q_(1, units.units))
        if dialog.exec_():
            return dialog.destination_units.text()
        return None
Esempio n. 18
0
    def bind_feat(self, feat):
        super().bind_feat(feat)

        #: self._units are the current units displayed by the widget.
        #: Respects units declared in the suffix

        if feat.units:
            suf = (self.suffix()
                   if hasattr(self, 'suffix') else feat.units) or feat.units
            self._units = Q_(1, suf)
            self.change_units(self._units)
        else:
            self._units = None
            if feat.limits:
                self.change_limits(None)
Esempio n. 19
0
 def __init__(self,
              channel_dict=default_digi_dict,
              laser_time=150 * Q_(1, "us"),
              readout_time=150 * Q_(1, "us"),
              buf_after_init=450 * Q_(1, "us"),
              buf_after_readout=2 * Q_(1, "us"),
              polarize_time=900 * Q_(1, "us"),
              settle=150 * Q_(1, "us"),
              reset=100 * Q_(1, "ns"),
              IQ=[0.5, 0],
              ip="192.168.1.111"):
     """
     :param channel_dict: Dictionary of which channels correspond to which instr controls
     :param laser_time: Laser time in us
     :param CTR: When False CTR0 When True CTR1
     :param readout_time: Readout time in us
     :param buf_after_init: buf after laser turns off in us to allow the population to thermalize
     :param buf_after_readout: buf between the longest pulse and the readout in us to prevent laser to leak in
     :param IQ: IQ vector that rotates the spin
     """
     super().__init__()
     self.channel_dict = channel_dict
     self._reverse_dict = {
         0: "laser",
         1: "offr_laser",
         2: 'current_gate',
         3: 'laser2',
         4: "EOM",
         5: "CTR",
         6: "switch",
         7: "gate",
         8: "I",
         9: "V"
     }  # rev_dict
     self.laser_time = int(round(laser_time.to("ns").magnitude))
     self.readout_time = int(round(readout_time.to("ns").magnitude))
     self.buf_after_init = int(round(buf_after_init.to("ns").magnitude))
     self.buf_after_readout = int(
         round(buf_after_readout.to("ns").magnitude))
     self.polarize_time = int(round(polarize_time.to("ns").magnitude))
     self.settle = int(round(settle.to("ns").magnitude))
     self.reset = int(round(reset.to("ns").magnitude))
     self._normalize_IQ(IQ)
     self.Pulser = PulseStreamer(ip)
     self.latest_streamed = pd.DataFrame({})
     self.total_time = -1  # update when a pulse sequence is streamed
Esempio n. 20
0
    def __init__(self, ch, sw_ch, active_trig_ch, passive_trig_ch=None):

        self._points = 1000
        self._period = Q_(10, 'ms')
        self._selectivity = 0.1
        self.ch = ch
        self.sw_ch = sw_ch
        self.active_trig_ch = active_trig_ch
        self.passive_trig_ch = passive_trig_ch

        self._single_mode = True
        self._peak_locations = list()
        self._trace = np.zeros(self.points)
        self._sw_state = False
        self._trig_ch = self.passive_trig_ch

        self.acq_task = AnalogInputTask('fp_readout')
        self.sw_task = DigitalOutputTask('fp_sw_task')
        return
Esempio n. 21
0
 def check(self):
     units = self.destination_units.text().strip()
     if not units:
         return
     try:
         new_units = Q_(1, units)
         factor = self.units.to(new_units).magnitude
     except LookupError or SyntaxError:
         self.message.setText('Cannot parse units')
         self.buttonBox.setEnabled(False)
     except ValueError:
         self.message.setText('Incompatible units')
         self.buttonBox.setEnabled(False)
     except AttributeError:
         self.message.setText('Unknown units')
         self.buttonBox.setEnabled(False)
     else:
         self.message.setText('factor {:f}'.format(factor))
         self.buttonBox.setEnabled(True)
Esempio n. 22
0
 def run_calibration(self,
                     power_fun,
                     npoints=500,
                     min_pt=0,
                     max_pt=5,
                     delay_per_point=0.1):
     voltages = np.linspace(min_pt, max_pt, npoints)
     powers = np.zeros(npoints)
     for i, v in enumerate(voltages):
         self.voltage = Q_(v, 'V')
         time.sleep(delay_per_point)
         powers[i] = power_fun().to('W').m
         print('{} V = {} W'.format(v, powers[i]))
     data = np.transpose(np.array([voltages, powers]))
     np.savetxt(self.calibration_file,
                data,
                delimiter=",",
                header='voltage,power',
                comments='')
     return data
Esempio n. 23
0
def request_new_units(current_units):
    """Ask for new units using a dialog box and return them.

    Parameters
    ----------
    current_units : Quantity
        current units or magnitude.

    Returns
    -------
    None or Quantity
    """
    new_units = UnitInputDialog.get_units(current_units)
    if new_units is None:
        return None

    try:
        return Q_(1, new_units)
    except LookupError:
        # cannot parse units
        return None
Esempio n. 24
0
    def read_scalar(self, timeout=Q_(10.0, 's')):
        """Read a single floating-point sample from a counter task. Use
        this function when the counter sample is scaled to a
        floating-point value, such as for frequency and period
        measurement.

        :param float:
          The amount of time, in seconds, to wait for the function to
          read the sample(s). The default value is 10.0 seconds. To
          specify an infinite wait, pass -1
          (DAQmx_Val_WaitInfinitely). This function returns an error if
          the timeout elapses.

          A value of 0 indicates to try once to read the requested
          samples. If all the requested samples are read, the function
          is successful. Otherwise, the function returns a timeout error
          and returns the samples that were actually read.

        :return: The sample read from the task.
        """

        err, value = self.lib.ReadCounterScalarF64(
            timeout.to('s').magnitude, RetValue('f64'), None)
        return value
Esempio n. 25
0
    def _relabel(self, axis):
        """Builds the actual label using the label and units for a given axis.
        Also builds a quantity to be used to normalized the data.

        Parameters
        ----------
        axis :
            x' or 'y'

        Returns
        -------

        """
        label = self._labels[axis]
        units = self._units[axis]
        if label and units:
            label = '%s [%s]' % (label, units)
        elif units:
            label = '[%s]' % units

        self.pw.setLabel(self._axis[axis], label)

        if units:
            setattr(self, '_q' + axis, Q_(1, units))
Esempio n. 26
0
 def _set_position(self, x, y):
     target = enforce_point_units([x, y])
     step_voltages = self.ao_smooth_func(self._position, target)
     if step_voltages.size:
         clock_config = {
             'source': 'OnboardClock',
             'rate': self.ao_smooth_rate.to('Hz').m,
             'sample_mode': 'finite',
             'samples_per_channel': step_voltages.shape[0],
         }
         self.task.configure_timing_sample_clock(**clock_config)
         self.task.configure_trigger_disable_start()
         task_config = {
             'data': step_voltages,
             'auto_start': False,
             'timeout': Q_(0, 's'),
             'group_by': 'scan',
         }
         self.task.write(**task_config)
         self.task.start()
         time.sleep(
             (step_voltages.shape[0] / self.ao_smooth_rate).to('s').m)
         self.task.stop()
     self._position = target
Esempio n. 27
0
    def change_limits(self, new_units):
        """Change the limits (range) of the control taking the original values
        from the feat and scaling them to the new_units.
        """
        if not hasattr(self, 'setRange'):
            return

        rng = self._feat.limits

        if not rng:
            return

        if new_units:
            conv = lambda ndx: Q_(rng[ndx], self._feat.units).to(new_units
                                                                 ).magnitude
        else:
            conv = lambda ndx: rng[ndx]

        if len(rng) == 1:
            self.setRange(0, conv(0))
        else:
            self.setRange(conv(0), conv(1))
            if len(rng) == 3:
                self.setSingleStep(conv(2))
Esempio n. 28
0
 def cl_move(self,
             axis,
             pos,
             delta_z=Q_(0.1, 'um'),
             iter_n=10,
             delay=Q_(0.01, 's'),
             debug=False,
             max_iter=1000):
     i = 0
     while (not self.at_pos(axis,
                            Q_(pos, 'um'),
                            delta_z=Q_(delta_z, 'um'),
                            iter_n=iter_n,
                            delay=Q_(delay, 's'))):
         self.position[axis] = Q_(pos, 'um')
         i += 1
         if i >= max_iter:
             raise Exception("Reached max_iter")
     if debug: print("It took {} iterations to move to position".format(i))
     return
Esempio n. 29
0
    def read(self,
             samples_per_channel=None,
             timeout=Q_(10.0, 's'),
             group_by='channel'):
        """Reads multiple floating-point samples from a task that
        contains one or more analog input channels.

        :param samples_per_channel:
          The number of samples, per channel, to read. The default
          value of -1 (DAQmx_Val_Auto) reads all available samples. If
          readArray does not contain enough space, this function
          returns as many samples as fit in readArray.

          NI-DAQmx determines how many samples to read based on
          whether the task acquires samples continuously or acquires a
          finite number of samples.

          If the task acquires samples continuously and you set this
          parameter to -1, this function reads all the samples
          currently available in the buffer.

          If the task acquires a finite number of samples and you set
          this parameter to -1, the function waits for the task to
          acquire all requested samples, then reads those samples. If
          you set the Read All Available Samples property to TRUE, the
          function reads the samples currently available in the buffer
          and does not wait for the task to acquire all requested
          samples.

        :param timeout: float
          The amount of time, in seconds, to wait for the function to
          read the sample(s). The default value is 10.0 seconds. To
          specify an infinite wait, pass -1
          (DAQmx_Val_WaitInfinitely). This function returns an error
          if the timeout elapses.

          A value of 0 indicates to try once to read the requested
          samples. If all the requested samples are read, the function
          is successful. Otherwise, the function returns a timeout
          error and returns the samples that were actually read.

        :param group_by:

            'channel'
              Group by channel (non-interleaved)::

                ch0:s1, ch0:s2, ..., ch1:s1, ch1:s2,..., ch2:s1,..

            'scan'
              Group by scan number (interleaved)::

                ch0:s1, ch1:s1, ch2:s1, ch0:s2, ch1:s2, ch2:s2,...

        :rtype: numpy.ndarray
        """

        if samples_per_channel is None:
            samples_per_channel = self.samples_per_channel_available()

        number_of_channels = self.number_of_channels()
        if group_by == Constants.Val_GroupByScanNumber:
            data = np.zeros((samples_per_channel, number_of_channels),
                            dtype=np.float64)
        else:
            data = np.zeros((number_of_channels, samples_per_channel),
                            dtype=np.float64)

        err, count = self.lib.ReadAnalogF64(samples_per_channel, timeout,
                                            group_by, data.ctypes.data,
                                            data.size, RetValue('i32'), None)

        if samples_per_channel < count:
            if group_by == 'scan':
                return data[:count]
            else:
                return data[:, :count]

        return data
Esempio n. 30
0
    def write(self,
              data,
              auto_start=True,
              timeout=Q_(10.0, 'seconds'),
              group_by='scan'):
        """
        Writes multiple samples to each digital line in a task. When
        you create your write array, each sample per channel must
        contain the number of bytes returned by the
        DAQmx_Read_DigitalLines_BytesPerChan property.

	Note: If you configured timing for your task, your write is
	considered a buffered write. Buffered writes require a minimum
	buffer size of 2 samples. If you do not configure the buffer
	size using DAQmxCfgOutputBuffer, NI-DAQmx automatically
	configures the buffer when you configure sample timing. If you
	attempt to write one sample for a buffered write without
	configuring the buffer, you will receive an error.

        Parameters
        ----------

        data : array

          The samples to write to the task.

        auto_start : bool

          Specifies whether or not this function automatically starts
          the task if you do not start it.

        timeout : float

          The amount of time, in seconds, to wait for this function to
          write all the samples. The default value is 10.0 seconds. To
          specify an infinite wait, pass -1
          (DAQmx_Val_WaitInfinitely). This function returns an error
          if the timeout elapses.

          A value of 0 indicates to try once to write the submitted
          samples. If this function successfully writes all submitted
          samples, it does not return an error. Otherwise, the
          function returns a timeout error and returns the number of
          samples actually written.

        layout : {'group_by_channel', 'group_by_scan_number'}

          Specifies how the samples are arranged, either interleaved
          or noninterleaved:

            'group_by_channel' - Group by channel (non-interleaved).

            'group_by_scan_number' - Group by scan number (interleaved).
        """

        number_of_channels = self.number_of_channels()

        if np.isscalar(data):
            data = np.array([data] * number_of_channels, dtype=np.uint8)
        else:
            data = np.asarray(data, dtype=np.uint8)

        if data.ndim == 1:
            if number_of_channels == 1:
                samples_per_channel = data.shape[0]
                shape = (samples_per_channel, 1)
            else:
                samples_per_channel = data.size / number_of_channels
                shape = (samples_per_channel, number_of_channels)

            if not group_by == Constants.Val_GroupByScanNumber:
                shape = tuple(reversed(shape))

            data.reshape(shape)
        else:
            if group_by == Constants.Val_GroupByScanNumber:
                samples_per_channel = data.shape[0]
            else:
                samples_per_channel = data.shape[-1]

        err, count = self.lib.WriteDigitalLines(samples_per_channel,
                                                auto_start, timeout, group_by,
                                                data.ctypes.data,
                                                RetValue('u32'), None)

        return count