Source code for stg._wrapper.downloadnet

# -*- coding: utf-8 -*-
from typing import List
from stg._wrapper.dll import (
    System,
    CURRENT,
    VOLTAGE,
    available,
    select,
    DeviceInfo,
    StreamingInterface,
    bitmap,
    STGX,
)


[docs]class STG4000(STGX): """ This class implements the interface to download, start and stop stimulation. At this point, you should pay attention to two specific details. First, indexing starts at 0. That means, the first channel is channel 0. Second, there is a difference between channels and triggers for the STG. Triggers are mapped to channels according to a channelmap. That means a single trigger can start stimulation of a whole set of channels. During initialization of the STG, we give this a sensible default. That means, all triggers are mapped to the respective channels following diagonal identity, i.e. trigger 0 maps to channel 0. Use :meth:`~STG4000.diagonalize_triggermap` to repeat this normalization. Example ------- .. code-block:: python import time from stg.api import STG4000 stg = STG4000() stg.download(0,[1,-1, 0], [0.1, 0.1, 49.8]) while True: time.sleep(0.5) a.trigger() stg.start_stimulation([0]) .. note:: * Indexing starts at zero * Differentiate triggers and channels .. warning:: Please note, that in download mode, the Python object and the STG do not share states. More specifically, once a program is downloaded to the STG, it stays downloaded, even if you delete the object, instantiate a new one or reboot the PC. You can enforce a clean state by rebooting the STG or explicitly downloading and thereby overwriting the channels after instantiation. This behavior was kept for three reasons. First, this is also the behavior of the MCS GUI, and therefore less non-intuitive if you come from this direction. Second, it clearly shows that in download mode, PC and STG are not coupled, i.e. once downloaded, you can trigger without any USB connection. Third, clearing the downloaded programs during instantiation (or deletion of the object) would prevent the user from common use cases like recovering the STG after a restart of the kernel, deletion of the object or unplugging the USB cable. """
[docs] def stop_stimulation(self, triggerIndex: List[int] = []): """stops all trigger inputs or a selection based on a list args ---- triggerIndex:List[int] defaults to [], which stops stimulation at all channels. Give it a list of integers to start a specific subset of triggers, e.g. [0,1]. """ if triggerIndex == []: triggerIndex = [c for c in range(self.channel_count)] with self.interface() as interface: interface.SendStop(System.UInt32(bitmap(triggerIndex)))
[docs] def start_stimulation(self, triggerIndex: List[int] = []): """starts all trigger inputs or a selection based on a list args ---- triggerIndex:List[int] defaults to [], which starts stimulation at all channels. Give it a list of integers to start a specific subset of triggers, e.g. [0,1]. """ if triggerIndex == []: triggerIndex = [c for c in range(self.channel_count)] with self.interface() as interface: interface.SendStart(System.UInt32(bitmap(triggerIndex)))
[docs] def set_mode(self, channel_index: List[int] = [], mode: str = "current") -> int: """set a single or all channels to voltage or current mode args ---- channel_index: list defaults to [], which sets the mode at all channels. Give it a list of integers to set the mode only for a specific subset of channels, e.g. [0,1]. mode: str ("current", "voltage") defaults to current .. warning:: Because we primarily use current-mode, voltage mode is relatively untested. Additionally, i so far have not tested the behavior when different channels are in different modes. Be safe, and just set all channels to current-mode with :code:`stg.set_mode("current")` """ if mode == "current": self._set_current_mode(channel_index) return CURRENT elif mode == "voltage": self._set_voltage_mode(channel_index) return VOLTAGE else: # pragma no cover raise ValueError( f"Unknow mode {mode}. select either 'current' or ' 'voltage'" )
[docs] def diagonalize_triggermap(self): """Give each trigger a sensible channel Use this function to normalize the mapping of trigger to channel to a diagonal identity, i.e. trigger 0 maps to channel 0, so on. +----------+---+---+---+---+---+---+---+---+ | Trigger | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +----------+---+---+---+---+---+---+---+---+ | Channel | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +----------+---+---+---+---+---+---+---+---+ """ channelmap = [] syncoutmap = [] repeat = [] for chan_idx in range(0, self.channel_count): repeat.append(1) # every trigger only once syncoutmap.append(1 << chan_idx) # diagonal triggerout channelmap.append(1 << chan_idx) # diagonal triggerin with self.interface() as interface: interface.SetupTrigger(0, channelmap, syncoutmap, repeat)
[docs] def download( self, channel_index: int = 0, amplitudes_in_mA: List[float,] = [0], durations_in_ms: List[float,] = [0], mode="current", ): """Download a stimulation signal .. Warning:: Any previous data sent to that channel is erased. Other channels stay untouched. The signal is compressed as amplitudes and their respective durations args ---- channel_index: int The index of the channel for which to download the signal. Indexing starts at 0 amplitudes_in_mA: List[float] a list of amplitudes in mA/mV delivered for the corresponding duration durations_in_ms: List[float] a list of durations in ms determing how long each corresponding amplitude is delivered mode: str defaults to current Example ------- .. code-block:: python stg.download(channel_index = 0, amplitudes_in_mA = [1, -1, 0], durations_in_ms = [.1, .1, .488]) """ if len(amplitudes_in_mA) != len(durations_in_ms): raise ValueError("Every amplitude needs a duration and vice versa!") amplitudes = [System.Int32(a * 1000_000) for a in amplitudes_in_mA] durations = [System.UInt64(s * 1000) for s in durations_in_ms] MODE = self.set_mode([channel_index], mode) with self.interface() as interface: interface.PrepareAndSendData( System.UInt32(channel_index), amplitudes, durations, MODE )