## A Python library for Trgen Device
Project description
trgenpy 🐍🧠
A Python library for Trgen Device
The full documentation is available here.
Index
- 📚 Getting started
- 🧠 E-Prime integration
- 🚀 How To build
- 💪 Examples
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]
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() # now I can use this client for any purpose.
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 NS0sendMarker(128)→ activates only NS6sendMarker(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:
markerNSmarkerSAmarkerGPIO
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
BNCOBNCI
- 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.createTrgenPort(TrgenPin.NS3)
passing as only argument the enum from TrgenPin corresponding to the real used pin.
NOTE ABOUT mtml
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.getImplementation()
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}")
see the "Concatenate Instructions"" section for further informations.
Custom Instruction Set
The TrgenPort object has a memory property.
memory has to be filled with a list of 2^mtml slots, default cardinality is 32.
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 Admissible
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
Each TrgenPortcan be programmed with a bunch of instructions.
the function set_instruction() can be use to store instructuons into memory slots.
Instructions can be added in memory slots using the static function set_instruction(i, x), that adds a specific instruction (x) in the desired memory index (i).
Index position is important since the program counter of TRGen Device execute each TrgenPort instructions from 0 to 2^mtml.
def set_instruction(self, index, instruction):
# index: 0-32 value
# instruction: instruction_code
Remember, the set_instruction(index,instruction) function does not program the TrgenPort, it just stores the instructon, that will be written using writeTrgenMemory().
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() |
Write Instruction on TRGenPort
Once the desired instruction set has be setted, the memory can be written using the function writeTrgenMemory(trgenport=TrgenPin.BNCO) to a specific TrgenPort by its TrgenPin.
After this, the program can be executed invoking the static method start()
The following script use the whole instruction set in order to program a single TrgenPort
Examples
client = TrgenClient()
bnco = client.createTrgenPort(TrgenPin.BNCO)
impl = client.getImplementation()
# helper function used to clear the current TrgenPort memory
# it program the desired TrgenPort with a blank program
# made of one end() and all not_admissible() instructions
client.resetTrgenPort(bnco) # it is explained in the "Helpers" paragraph
bnco.set_instruction(0, active_for_us(5))
bnco.set_instruction(1, unactive_for_us(3))
bnco.set_instruction(2, repeat(0,2))
bnco.set_instruction(3, wait_pe(TrgenPin.NS4))
bnco.set_instruction(4, unactive_for_us(3))
bnco.set_instruction(5, wait_nd(TrgenPin.NS0))
bnco.set_instruction(6, end()) # end is mandatory!
for i in range(7,impl.mtml-1): # from 7 to 31!
bnco.set_instruction(i, not_admissible()) # this also is mandatory
# write the program
# every instruction in bnco.memory
# is flashed into TRGen Device
client.writeTrgenMemory(bnco)
# execute the TRGen Device
client.start()
writeTrgenMemory() write only specific TrgenPort. This means that many custom programs can be written
bnco = client.createTrgenPort(TrgenPin.BNCO)
ns7 = client.createTrgenPort(TrgenPin.NS7)
# you can also use this helper in order to reset All TrgenPort
resetAllTrgenPorts
bnco.set_instruction(0, active_for_us(5))
bnco.set_instruction(1, unactive_for_us(3))
bnco.set_instruction(2, end(3))
for i in range(3,31):
bnco.set_instruction(i, not_admissible())
ns7.set_instruction(0, wait_pe(TrgenPin.BNCO))
ns7.set_instruction(1, active_for_us(5))
ns7.set_instruction(2, unactive_for_us(3))
ns7.set_instruction(3, end(3))
for i in range(4,31):
ns7.set_instruction(i, not_admissible())
client.writeTrgenMemory(ns7)
client.writeTrgenMemory(bnco)
# execute the TRGen Device
client.start()
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.
Marker arguments
Same parametrization of standard sendMarkers:
- markerNS: for NeuroScan ports
- markerSA: for Synamps ports
- markerGPIO: for GPIO ports
# 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()
NOTE
It's not allowed to send GPIO markers when the inputPin is GPIO
# THIS WILL GENERATE ERRORS
client.callbackMarker(ne=True, markerGPIO=13,inputPin=TrgenPin.GPIO2)
# Like:
# ❌ GPIO cannot be used as both input and output simultaneously
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:
- Default trigger using
programDefaultTrigger()
This sets a predefined impulse of duration (default 20µs).
tr = client.createTrgenPort(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
- Advanced programming by manually setting instructions with
set_instruction()
tr = client.createTrgenPort(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.writeTrgenMemory(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 written at least one TrgenPort program into its 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.getImplementation()
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()
Helpers
Here's some utility functions used to simplify the TRGen Device interaction
Reset TrgenPort
In order to reset a specific TrgenPort program, the following static functions can be used:
resetTrgenPort(trgenport)resetAllTrgenPorts()
bnco = TrgenPort(TrgenPin.BNCO)
client.resetTrgenPort(bnco) # reset just bnco
# it basically does
# bnco.set_instruction(0, end())
# for i in range(1, 31):
# bnco.set_instruction(i,not_admissible())
client.resetAllTrgenPorts() # reset all TrgenPorts
Set Default Duration
In this and this section is explained how to send a default trigger to BNCO or multiple TrgenPorts
Default duration is 20µs, but it can be changed using setDefaultDuration(duration_us) like this:
client.setDefaultDuration(100)
client.sendTriggerBNCO() # now this default trigger lasts 100µs
Get Default Duration
To get standard new duration use getDefaultDuration() like this:
new_duration = client.getDefaultDuration() # returns an int value
Get Implementation
The TRGenDevice has its own "settings" stored into the impl object defined by class TrgenImplementation.
impl is a TrgenClient property and can be reached invoking the static function getImplementation() like this:
client = TrgenClient()
impl = client.getImplementation()
TrgenImplementation contains informations about:
-
TrgenPort cardinality
- Standard
mtml
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_bnco_up():
# check if the device is online
if client.is_available():
print("Trgen is connected")
# build a trigger
bnco = client.createTrgenPort(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.writeTrgenMemory(bnco)
# start the sequence
client.start()
# stop the sequence
client.stop()
set_bnco_up()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trgenpy-1.0.6.tar.gz.
File metadata
- Download URL: trgenpy-1.0.6.tar.gz
- Upload date:
- Size: 38.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14de8476ec318b12ee685c51d588763c5aef0995c152062c1818a6ab44b34c65
|
|
| MD5 |
59da158ce11143368aadd597c0147a8f
|
|
| BLAKE2b-256 |
fc55f8318446cf031602f7837cabccee8394c92cbcca99bc775d6ae7b6870e82
|
File details
Details for the file trgenpy-1.0.6-py3-none-any.whl.
File metadata
- Download URL: trgenpy-1.0.6-py3-none-any.whl
- Upload date:
- Size: 33.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
826cf067e0e01a08b91bea41228abca7f7704a9a25cc53572c0faa05f912f52c
|
|
| MD5 |
7d48cc4307c7e9ee616160e858ea9feb
|
|
| BLAKE2b-256 |
3abe32a03d2aec933aa9f98b60d6958197995cd4632317c1261a0ef51baee9ab
|