コード例 #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)
コード例 #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)
コード例 #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
コード例 #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'))
コード例 #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))
コード例 #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)
コード例 #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))
コード例 #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)
コード例 #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)
コード例 #10
0
ファイル: sa201.py プロジェクト: mtsolmn/lantz-drivers
    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
コード例 #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)
コード例 #12
0
ファイル: tasks.py プロジェクト: mtsolmn/lantz-drivers
    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]
コード例 #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))
コード例 #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
コード例 #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'))
コード例 #16
0
ファイル: lantz_server.py プロジェクト: mtsolmn/lantz-drivers
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
コード例 #17
0
ファイル: dialog_units.py プロジェクト: mtsolmn/lantz-qt
    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
コード例 #18
0
ファイル: numeric.py プロジェクト: mtsolmn/lantz-qt
    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)
コード例 #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
コード例 #20
0
ファイル: fabryperot.py プロジェクト: mtsolmn/lantz-drivers
    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
コード例 #21
0
ファイル: dialog_units.py プロジェクト: mtsolmn/lantz-qt
 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)
コード例 #22
0
ファイル: v1000f.py プロジェクト: mtsolmn/lantz-drivers
 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
コード例 #23
0
ファイル: numeric.py プロジェクト: mtsolmn/lantz-qt
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
コード例 #24
0
ファイル: tasks.py プロジェクト: mtsolmn/lantz-drivers
    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
コード例 #25
0
ファイル: chart.py プロジェクト: mtsolmn/lantz-qt
    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))
コード例 #26
0
ファイル: fsm300.py プロジェクト: mtsolmn/lantz-drivers
 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
コード例 #27
0
ファイル: numeric.py プロジェクト: mtsolmn/lantz-qt
    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))
コード例 #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
コード例 #29
0
ファイル: tasks.py プロジェクト: mtsolmn/lantz-drivers
    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
コード例 #30
0
ファイル: tasks.py プロジェクト: mtsolmn/lantz-drivers
    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