Skip to main content

## A Python library for Trgen Device

Project description

trgenpy 🐍🧠

A Python library for Trgen Device

PyPI version Python Version License

The full documentation is available here.


Index


Getting started

How to install

To install this package use is required Python >= 3.9

pip install trgenpy

Architecture

As explained in this Mermaid diagram, it is possible to program n (0-25) TrgenPorts to the TrgenClient

graph TD
    A[TrgenClient]
    A --> Impl[TrgenImplementation]
    A --> B0[TrgenPort 0]
    A --> B1[TrgenPort 1]
    A --> BN[TrgenPort n]
    B0 --> P0[TrgenPin]
    B1 --> P1[TrgenPin]
    BN --> PN[TrgenPin]
    B0 --> M0[Memory]
    B1 --> M1[Memory]
    BN --> MN[Memory]
    M0 --> I0[Instruction 0]
    M0 --> I1[Instruction 1]
    M0 --> I2[Instruction n]
    M1 --> J0[Instruction 0]
    M1 --> J1[Instruction 1]
    M1 --> J2[Instruction n]
    MN --> K0[Instruction 0]
    MN --> K1[Instruction 1]
    MN --> K2[Instruction n]

The client variable can be used to launch each command inside TRGen device

client = TrgenClient() # now I can use this client for any purpose.

Each TrgenPortcan be programmed with a bunch of instructions. We can use the function set_instruction() in many memory slots. Finally, we can use set_trgen_memory to a specific TrgenPort and execute the TrgenClient with client.start()

NOTE

Each TrgenPort can support a list of N instructions, where N = 2^MTML (Max TrgenPort Memory Length). the Memory Length is a "magic number" hardcoded inside the triggerbox firmware. In the current versions is 5

You can access to the mtml value by

# Add this in try block to manage errors
try:
    impl = client.get_implementation()
    mtml = impml.mtml
except InvalidAckError as e:
    print(f"⚠️ ACK sbagliato: {e}")
except AckFormatError as e:
    print(f"⚠️ ACK malformato: {e}")
except TimeoutError as e:
    print(f"⏱️ Timeout: {e}")

Import the library

# you can import 
# library like this:
import trgenpy as tp
client = tp.TrgenClient()

# or like this:
from from trgenpy import TrgenClient,TrgenPin,active_for_us,unactive_for_us,end,wait_ne,wait_pe
client = TrgenClient()

Client

The TrgenClient object stores all the information about the socket connection between the user PC and the TrGEN Device.
The TrgenClient object may be istantiated like this:

client = TrgenClient() 

Once the client object is created, connection between user PC and TrGEN Device is achieved with:

client.connect()

It is also possible to check the availability of the device (useful to check if properly connected)

isAavailable = client.is_available() # true / false

Sending Default Single Trigger to BNCO

To send a defaul trigger signal (positive square with lasting 20µs) the sendTriggerBNCO() function can be used:

client.sendTriggerBNCO()

Custom Trigger(s)

To send trigger to a customa single or multiple custom triggers at the same time (i.e., with nanosecond time resolution), the sendCustomTrigger() function can be used :

client.sendCustomTrigger([TrgenPin.NS0,TrgenPin.GPIO0])

In this example, sendCustomTrigger has one array as argument. The array represent the list of the desired PINs (PIN 0 of the parallel port and PIN 0 of the GPIO, TrgenPin.NS0 and TrgenPin.GPIO0, respectively) to send simultaneous triggers. This function can also be used to test whether signals on PINs are properly functioning.

Sending Markers

To send a marker to bioamplifiers equipped with parallel ports used for triggering and register events, the function sendMarker() function can be used:

client.sendMarker(13)

sendMarker(n) will send the appropriate triggers to generate the desired marker n on the electrophysiological signal from ALL the equipped output ports (Parallel port, GPIO) on the TRGen device.

Pin Binary Mapping

Both sendMarker() and sendCustomTrigger() use a binary mapping system where each pin corresponds to a specific bit position. This allows for efficient encoding of multiple pin states in a single value:

Pin Binary Position Decimal Value Binary Representation
NS0 Bit 0 (2^0) 1 00000001
NS1 Bit 1 (2^1) 2 00000010
NS2 Bit 2 (2^2) 4 00000100
NS3 Bit 3 (2^3) 8 00001000
NS4 Bit 4 (2^4) 16 00010000
NS5 Bit 5 (2^5) 32 00100000
NS6 Bit 6 (2^6) 64 01000000
NS7 Bit 7 (2^7) 128 10000000

This suggests the pins might be mapped differently than the sequential naming suggests. Please verify the complete mapping to ensure accuracy.

Examples based on your observation:

  • sendMarker(1) → activates only NS0
  • sendMarker(128) → activates only NS6
  • sendMarker(129) → activates NS0 + NS6 (binary: 10000001)

This same mapping applies to Synamps (SA0-SA7) and GPIO (GPIO0-GPIO7) pins when using their respective marker parameters.

If the marker number sent does not match with the one observed on the physiological signal, this is may due to an inverted mapping of the bioamplifier’s PINOUT. To fix the issue, also it is possible to invert the bit order, just flag the LSB argument (True by default)

client.sendMarker(13,LSB=False)

To send different markers simultaneously (with nanosecond temporal precision) n different output ports, the following arguments may be used:

  • markerNS
  • markerSA
  • markerGPIO

like this:

client.sendMarker(markerNS=8,markerSA=2,markerGPIO=15)

In this way, a marker value of 8 will be generated from the parallel port, 2 on the second parallel port and 15 from the GPIO port.

Programming Custom Trigger Signals

trgenpy offers some advanced functions and features for users need to customize the output signal's behaviour.

TrgenPin List

Each Pin on the TRGen Device has an unique ID. This is the classification of each Port grouped by connector Type

  • Neuroscan IDs The Neuroscan pinout (only for used pins) goes from 0 to 7
    • [NS0,NS1,NS2,NS3,NS4,NS5,NS6,NS7]
  • Synamps IDs The Neuroscan pinout (only for used pins) goes from 0 to 7
    • [SA0,SA1,SA2,SA3,SA4,SA5,SA6,SA7]
  • BNC I/O IDs
    • BNCO
    • BNCI
  • GPIO IDs The GPIO pinout goes from 0 to 7
    • [GPIO0,GPIO01,GPIO02,GPIO03,GPIO04,GPIO05,GPIO06,GPIO07]

The definition of a specific port is done by calling an enumeration through the TrgenPin class by doing TrgenPin.$PIN_ID, like this:

# defining pin
neuroscan_third_pin = TrgenPin.NS3

TrgenPort

The TrgenPort object defines a single trigger behaviours through its id. This is the list of supported TrgenPin values for the TRGrgen:

Instantiate the TrgenPort object with:

neuroScan = TrgenClient.create_trgen(TrgenPin.NS3)

passing as only argument the enum from TrgenPin corresponding to the real used pin.

Custom Instruction Set

The TrgenPort object has a memory property, a list that can contain 32 slot. Each slot can contain a single istruction that will be executed in order from the first to the last.

The supported instruction set for TRGen Device is:

  • Unactive For N µs

    The TrgenPort stays unactive for N µs

  • Active For N µs

    The TrgenPort stays active for N µs

  • Wait Positive Edge

    The TrgenPort waits until there is a positive edge

  • Wait Negative Edge

    The TrgenPort waits until there is a negative edge

  • Repeat from N, for X times

    The TrgenPort repeat the instruction sequence from the index X to the current one, for X times

  • End

    The TrgenPort end its behaviour

  • Not Ammissible

    This instruction is ignored, but it is necessary to fill the empty instruction list. NOTE: if the space is empty, it will be filled automatically by trgenpy

Concatenate Instructions

The memory list can be build by adding some istructions to it.

The static function set_instruction(i, x) can be used to add a specific instruction (x) in the desired program sequence position (i)

def set_instruction(self, index, instruction):
# index: 0-32 value
# instruction: instruction_code

Instruction Helpers

Since for TRGen Device all instruction are "bitmap chunks" this library offers some helper functions that do the bitmap encoding. They can be used to define easily any instruction:

Instruction 1° Param 2° Param Description
unactive_for_us(us) µ seconds duration Set the unactivation time for µ seconds
active_for_us(us) µ seconds duration Set the activation time for µ seconds
wait_pe(tr) TrgenPin Wait the positive edge for a specific TrgenPin
wait_ne(tr) TrgenPin Wait the negative edge for a specific TrgenPin
repeat(addr,time) Instruction address Time of repeat Set the activation time for µ seconds
end() End the behaviour
not_admissible() Empty instruction, it has to be placed after the [end()

Example

Here, and example of custom trigger on the previously defined PIN 4 of the parallel port, which will be active for 5 microseconds (HIGH status -1-) and unactive for 3 microseconds (LOW status -0-).

# call the function directly on the same TrgenPort object
neuroScan.set_instruction(0, active_for_us(5))
neuroScan.set_instruction(1, unactive_for_us(3))
# ...

Event Listening

These are functions that helps on programming simple and customized behaviour on a simple trigger event on a specified inputPin.

Listen for Default Simple to BNCO

Configures the BNC output to automatically respond to signals received on a custom inputPin

from trgenpy import TrgenClient

client = TrgenClient()
client.connect()

# Respond to positive edge (default)
# default input inputPin is BNCI
# send default Trigger to BNCO
client.callbackSendTriggerBNCO()

# Program a behaviour that send default trigger when BNCI (default) is on negative edge
client.callbackSendTriggerBNCO(ne=True)

# Program a behaviour that send default trigger when NS6 is on negative edge
client.callbackSendTriggerBNCO(ne=True,inputPin=TrgenPin.NS6)

# Program a behaviour that send default trigger when GPIO2 is on positive edge (default True)
# it automatically set the global GPIO direction to "input" 
client.callbackSendTriggerBNCO(inputPin=TrgenPin.GPIO2)

# Remember to call .start(), because this is just Port programming
# Start listening
client.start()

Listen for Markers

Configures the GPIO connector (in "input direction" mode) to respond to signals received on a specific GPIO pin.

# Program a behaviour that send default trigger when GPIO2 is on negative edge
# Also set the Global GPIO direction to "input"
client.callbackMarker(ne=True, markerNS=50,inputPin=TrgenPin.GPIO2)

# Start listening
client.start()

Listen for Custom Triggers

Configures a custom output pin to respond to a specific inputPin of type TrgenPin

# Program a default trigger to Neuroscan 7 and Synamps 8 pins on a negative front edge from the BNCI port
client.callbackCustomTrigger(ne=True, trgenPinList=[TrgenPin.NS7,TrgenPin.SA8], inputPin=TrgenPin.BNCI))

# Start listening
client.start()

# Advanced configuration: use custom instruction set
# Configuration with custom instructions
instruction_set = [
    wait_pe(TrgenPin.BNCI),      # Wait for positive edge on BNCI
    active_for_us(50),           # Active for 50µs
    unactive_for_us(10),         # Inactive for 10µs
    active_for_us(30),           # Active for 30µs
    unactive_for_us(20),         # Inactive for 20µs
    active_for_us(20),           # Active for 20µs
    unactive_for_us(30),         # Inactive for 30µs
    end()                        # End program
]

# Program a custom trigger on GPIO8 and BNCO on a positive edge front (default when not specified) with a custom instruction set
client.callbackCustomTrigger(trgenPinList=[TrgenPin.GPIO8,TrgenPin.BNCO], inputPin=TrgenPin.BNCI,customInstructions=instruction_set)

# Start listening
client.start()

NOTE

Programming custom trigger does not include the automatic start. It's always recommended to invoke the start() function once your custom istruction set is written


Native Commands

TRGen devices can receive some commands, the full instruction set includes:

  • Program TrgenPort
  • Start TrgenPort
  • Set GPIO I/O Direction
  • Request Implementation parameters
  • Request TrgenPort Status
  • Set the TrgenPort level
  • Get the TrgenPort Level
  • Get GPIO I/O Direction
  • Stop the TrgenPort

Program TrgenPort

You can program a trigger behaviour in two ways:

  1. Default trigger using programDefaultTrigger()
    This sets a predefined impulse of duration (default 20µs).
tr = client.create_trgen(TrgenPin.GPIO0)
client.programDefaultTrigger(tr, us=50)  # impulse of 50µs

In this way, all the successive triggers sent from GPIO0 PIN will have a duration of 50 microseconds

  1. Advanced programming by manually setting instructions with set_instruction()
tr = client.create_trgen(TrgenPin.NS1)
tr.set_instruction(0, active_for_us(10))
tr.set_instruction(1, unactive_for_us(5))
tr.set_instruction(2, repeat(0, 3))
tr.set_instruction(3, end())
client.set_trgen_memory(tr)

Start Trigger

After programming triggers, you can start them with:

client.start()

This command makes the Trgen execute all programmed triggers.
Remember: you must have previously sent at least one trigger memory.

Set GPIO I/O Direction

You can use this helper to build the complete or partial pinout map to set.

# we choose to set just GPIO0 -> High and the GPIO1 -> Low
mask = DirectionConfig().out(TrgenPin.GPIO0).in(TrgenPin.GPIO1).build()
# once you have it, you can use it as argument in the set_level function
client.set_gpio_direction(mask)

Request Implementation

You can get information about the hardware configuration (number of channels, memory length, etc.) with:

impl = client.get_implementation()
print(impl.memory_length)  # max number of instructions per trigger

Request TrgenPort Status

To check current status of triggers (active/inactive state):

status = client.get_status()
print(status)

This returns an integer bitmask where each bit represents the state of a trigger pin.

Set the Trigger level

It is possible to change the polarity (active-high or active-low) for any TrgenPort: Use the helper funcion set_levellike this in order to build the complete or partial pinout map to set.

# we choose to set just GPIO0 -> High and the GPIO1 -> Low
mask = LevelConfig().high(TrgenPin.GPIO0, TrgenPin.BNCO).low(TrgenPin.GPIO1).build()
# once you have it, you can use it as argument in the set_level function
client.set_level(mask)

Get the Trigger Level

To check trigger polarity:

level = client.get_level()
print(bin(level))

Get the GPIO I/O Direction

To retrieve the current GPIO configuration:

gpio_state = client.get_gpio_direction()
print(bin(gpio_state))

Stop the TrgenPort

To stop the TRGen device use the stop() function

gpio_state = client.stop()

E-Prime integration

trgenpy offers a wrapper version to integrate its behaviour inside the E-Prime software via Python COM-visible.

Install pywin32

pip install pywin32

Register the COM wrapper

python trgen_com.py --register

So that you can use in E-Prime like this

Dim tb
Set tb = CreateObject("Trgen.COM")

If tb.IsAvailable() Then
    tb.Connect
    tb.SendTrigger
    tb.SendMarker 13
Else
    MsgBox "Trgen not available!"
End If

E-Prime example

See the examples directory for the complete examples

How to build

trgenpy uses setuptools to have a much clear and minimal build process.

Step 1 - install twine

pip install --upgrade build twine

Step 2 - build

python -m build

Step 3 - use it anywhere!

In the same directory (pwd) of this project, run: pip install .

Deploy

twine upload --repository testpypi dist/*

Examples

These are some example extracted from the example directory in this repo.

Default Single Trigger (ONLY BNCO)

from trgenpy import TrgenClient,TrgenPin,active_for_us,unactive_for_us,end,wait_ne,wait_pe

# create the Client for the Trgen
client = TrgenClient() # eventually TrgenClient(ip="192.168.123.2")

client.connect()
client.sendTriggerBNCO()

Custom Trigger

from trgenpy import TrgenClient,TrgenPin,active_for_us,unactive_for_us,end,wait_ne,wait_pe

# create the Client for the Trgen
client = TrgenClient() # eventually TrgenClient(ip="192.168.123.2")

client.connect()
client.sendCustomTrigger([TrgenPin.NS0,TrgenPin.GPIO0])

Custom Trigger

from trgenpy import TrgenClient,TrgenPin,active_for_us,unactive_for_us,end,wait_ne,wait_pe
# create the Client for the Trgen
client = TrgenClient() # eventually TrgenClient(ip="192.168.123.2")

client.connect()

def set_tmso_up():
    # check if the device is online
    if client.is_available():
        print("Trgen is connected")

        # build a trigger
        bnco = client.create_trgen(TrgenPin.BNCO)
        bnco.set_instruction(0, active_for_us(5))
        bnco.set_instruction(1, unactive_for_us(3))
        bnco.set_instruction(2, end())
        # send the trigger
        client.set_trgen_memory(bnco)

        # start the sequence
        client.start()

        # stop the sequence
        client.stop()
        
set_tmso_up()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

trgenpy-1.0.5.tar.gz (36.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

trgenpy-1.0.5-py3-none-any.whl (32.4 kB view details)

Uploaded Python 3

File details

Details for the file trgenpy-1.0.5.tar.gz.

File metadata

  • Download URL: trgenpy-1.0.5.tar.gz
  • Upload date:
  • Size: 36.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for trgenpy-1.0.5.tar.gz
Algorithm Hash digest
SHA256 0bb2e5852add44d4f18fffa22b775478977d8c20d9c0933ad6947b88e7adcade
MD5 2e5902156c4ce03866b62007255d1e79
BLAKE2b-256 fba7d3ddbd9dd4f9a5f0785a30b420a4cacb955553d50ecaeac8794258bad5a5

See more details on using hashes here.

File details

Details for the file trgenpy-1.0.5-py3-none-any.whl.

File metadata

  • Download URL: trgenpy-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 32.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for trgenpy-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 146cd84e0b52ee813980148a3b29f0456456b2c8861ebfaf43efdfc011bcbe1d
MD5 72afb3dd937a4af05f5656ad3a775352
BLAKE2b-256 6c70bbcd3bf02330b7a457de393a6027fa49e4035764b3c9c55682baa9936165

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page