temapy 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- temapy/__init__.py +0 -0
- temapy/examples/__init__.py +0 -0
- temapy/examples/average_as_new_sequence.py +30 -0
- temapy/gateway.py +291 -0
- temapy/sequences.py +140 -0
- temapy-1.0.0.dist-info/METADATA +217 -0
- temapy-1.0.0.dist-info/RECORD +9 -0
- temapy-1.0.0.dist-info/WHEEL +5 -0
- temapy-1.0.0.dist-info/top_level.txt +1 -0
temapy/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test script that adds an update action to calculate the average pos of
|
|
3
|
+
every sample in two sequences.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from temapy.gateway import TemaGateway
|
|
7
|
+
from temapy.sequences import Status
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
tema_gateway = TemaGateway()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@tema_gateway.update_action(
|
|
14
|
+
input_sequences=("p1_pos", "p2_pos"), output_sequences=("avg_pos",)
|
|
15
|
+
)
|
|
16
|
+
def average_per_sample(seq_1, seq_2, seq_out):
|
|
17
|
+
"""
|
|
18
|
+
calculates the average pos of every sample in two sequences.
|
|
19
|
+
|
|
20
|
+
:param seq_1: the first sequence
|
|
21
|
+
:param seq_2: the second sequence
|
|
22
|
+
:param seq_out: sequence to put result in
|
|
23
|
+
:return:
|
|
24
|
+
"""
|
|
25
|
+
both_sequences = zip(seq_1.samples.items(), seq_2.samples.values())
|
|
26
|
+
for (time_1, sample_1), sample_2 in both_sequences:
|
|
27
|
+
if seq_1.samples[time_1].status.is_valid():
|
|
28
|
+
seq_out.samples[time_1].data[0] = (sample_1.data[0] + sample_2.data[0]) / 2
|
|
29
|
+
seq_out.samples[time_1].data[1] = (sample_1.data[1] + sample_2.data[1]) / 2
|
|
30
|
+
seq_out.samples[time_1].status = Status.CALCULATED
|
temapy/gateway.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module to set up a connection to a running Tema session.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
from py4j.clientserver import ClientServer
|
|
9
|
+
from py4j.java_gateway import DEFAULT_PORT
|
|
10
|
+
from py4j.java_gateway import JavaGateway, CallbackServerParameters, GatewayParameters
|
|
11
|
+
|
|
12
|
+
from temapy.sequences import Sequence
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TemaGateway:
|
|
16
|
+
"""
|
|
17
|
+
To make your script tema compatible, import and instantiate a
|
|
18
|
+
TemaGateway. Then add your wanted update actions (calculating
|
|
19
|
+
functions) using either the add_update_action function or the
|
|
20
|
+
update_action decorator.
|
|
21
|
+
|
|
22
|
+
Typical usage example:
|
|
23
|
+
|
|
24
|
+
# Establish connection
|
|
25
|
+
tema_gateway = TemaGateway()
|
|
26
|
+
|
|
27
|
+
# Register a calculation function
|
|
28
|
+
@tema_gateway.update_action(
|
|
29
|
+
input_sequences=("seq_1", "seq_2"),
|
|
30
|
+
output_sequences=("seq_3",)
|
|
31
|
+
)
|
|
32
|
+
def my_calculator(first_seq, second_seq, out_seq):
|
|
33
|
+
# your implementation
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
class Java:
|
|
37
|
+
"""
|
|
38
|
+
This describes to Py4J that the TemaGateway implements the Java
|
|
39
|
+
interface "temapyCallbackInterface". This allows java to call the python
|
|
40
|
+
methods defined in the interface.
|
|
41
|
+
|
|
42
|
+
Setting the auto_connect argument to false means that the connection to
|
|
43
|
+
Tema will not be setup automatically and that the self.connect method
|
|
44
|
+
needs to be called to connect to Tema. this can be used to test the
|
|
45
|
+
script without running via Tema.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
implements = ["se.imagesystems.python.temapyCallbackInterface"]
|
|
49
|
+
|
|
50
|
+
def __init__(self, auto_connect=True):
|
|
51
|
+
"""
|
|
52
|
+
Creates a new TemaGateway that handles communication with Tema and
|
|
53
|
+
allows you to add update actions to modify Tema sequences.
|
|
54
|
+
|
|
55
|
+
:param auto_connect: if the gateway should attempt to automatically
|
|
56
|
+
connect to Tema or not.
|
|
57
|
+
"""
|
|
58
|
+
self.update_actions = []
|
|
59
|
+
self.input_sequences = {}
|
|
60
|
+
self.output_sequences = {}
|
|
61
|
+
self.shutdown = threading.Event()
|
|
62
|
+
self.tema = None
|
|
63
|
+
self.connection_thread = None
|
|
64
|
+
self.port = None
|
|
65
|
+
self._parse_tema_port()
|
|
66
|
+
if auto_connect:
|
|
67
|
+
self.connect()
|
|
68
|
+
|
|
69
|
+
def connect(self):
|
|
70
|
+
"""
|
|
71
|
+
Connects temapy to a running Tema application. Should only be called if
|
|
72
|
+
the script is started via Tema.
|
|
73
|
+
|
|
74
|
+
If a connection already exist this method does nothing.
|
|
75
|
+
(see request_shutdown(self))
|
|
76
|
+
:return:
|
|
77
|
+
"""
|
|
78
|
+
if isinstance(self.tema, ClientServer):
|
|
79
|
+
return
|
|
80
|
+
self._setup_server()
|
|
81
|
+
self._notify_tema()
|
|
82
|
+
|
|
83
|
+
def _setup_server(self):
|
|
84
|
+
"""
|
|
85
|
+
Creates a client server pair used for sending and receiving commands
|
|
86
|
+
to/from Tema.
|
|
87
|
+
|
|
88
|
+
The connection will remain active until the request_shutdown() method is
|
|
89
|
+
called.
|
|
90
|
+
:return:
|
|
91
|
+
"""
|
|
92
|
+
# Start py4j Java Gateway with a callback server
|
|
93
|
+
self.tema = JavaGateway(
|
|
94
|
+
gateway_parameters=GatewayParameters(port=self.tema_port),
|
|
95
|
+
# Dynamically allocate a port for the callback server
|
|
96
|
+
callback_server_parameters=CallbackServerParameters(port=0),
|
|
97
|
+
python_server_entry_point=self,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Gets the actually allocated port for the callback server
|
|
101
|
+
self.port = self.tema.get_callback_server().get_listening_port()
|
|
102
|
+
|
|
103
|
+
def _notify_tema(self):
|
|
104
|
+
"""
|
|
105
|
+
Sends the port number of the Python server to Tema so that Tema can
|
|
106
|
+
communicate with python.
|
|
107
|
+
|
|
108
|
+
This also indicates to tema that the python process is ready to perform
|
|
109
|
+
calculations.
|
|
110
|
+
:return:
|
|
111
|
+
"""
|
|
112
|
+
self.tema.entry_point.setPythonPort(self.port)
|
|
113
|
+
|
|
114
|
+
def add_update_action(self, action, *, input_sequences=(), output_sequences=()):
|
|
115
|
+
"""
|
|
116
|
+
Adds an update action to be run when Tema calls for a recalculation.
|
|
117
|
+
|
|
118
|
+
The action should be a function or other callable that takes sequences
|
|
119
|
+
as input arguments matching the ones supplied in the input_sequences and
|
|
120
|
+
output_sequences arguments in the same order.
|
|
121
|
+
|
|
122
|
+
The action should refrain from changing the sequences specified as
|
|
123
|
+
input, as doing so would change the input for subsequent actions in the
|
|
124
|
+
same update. Additionally, changes to input sequences will not be sent
|
|
125
|
+
to Tema.
|
|
126
|
+
|
|
127
|
+
Changes made to any sequence specified as output will be copied back
|
|
128
|
+
into tema after all added update actions have run. If multiple actions
|
|
129
|
+
writes to the same output, they will operate on the same sequence and
|
|
130
|
+
might overwrite each other.
|
|
131
|
+
|
|
132
|
+
:param action: The action to be performed on the sequences.
|
|
133
|
+
:param input_sequences: The sequences to be used as input to the action
|
|
134
|
+
function.
|
|
135
|
+
:param output_sequences: The sequences to be used as output.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def do_action():
|
|
139
|
+
# Get the actual output and input sequences
|
|
140
|
+
in_sequences = []
|
|
141
|
+
for wanted_sequence_name in input_sequences:
|
|
142
|
+
try:
|
|
143
|
+
in_sequences.append(self.input_sequences[wanted_sequence_name])
|
|
144
|
+
except KeyError as e:
|
|
145
|
+
raise KeyError(
|
|
146
|
+
f"No input sequence named {wanted_sequence_name}"
|
|
147
|
+
) from e
|
|
148
|
+
out_sequences = []
|
|
149
|
+
for wanted_sequence_name in output_sequences:
|
|
150
|
+
try:
|
|
151
|
+
out_sequences.append(self.output_sequences[wanted_sequence_name])
|
|
152
|
+
except KeyError as e:
|
|
153
|
+
raise KeyError(
|
|
154
|
+
f"No output sequence named {wanted_sequence_name}"
|
|
155
|
+
) from e
|
|
156
|
+
|
|
157
|
+
# Run user action
|
|
158
|
+
action(*in_sequences, *out_sequences)
|
|
159
|
+
|
|
160
|
+
self.update_actions.append(do_action)
|
|
161
|
+
|
|
162
|
+
def update_action(self, *, input_sequences=(), output_sequences=()):
|
|
163
|
+
"""
|
|
164
|
+
Decorator used to add an update action to the given sequences.
|
|
165
|
+
|
|
166
|
+
The action should be a function or other callable that takes sequences
|
|
167
|
+
as input arguments matching the ones supplied in the input_sequences and
|
|
168
|
+
output_sequences arguments in the same order.
|
|
169
|
+
|
|
170
|
+
The action should refrain from changing the sequences specified as
|
|
171
|
+
input, as doing so would change the input for subsequent actions in the
|
|
172
|
+
same update.
|
|
173
|
+
|
|
174
|
+
Changes made to any sequence specified as output will be copied back
|
|
175
|
+
into tema after all added update actions have run. If multiple actions
|
|
176
|
+
writes to the same output, they will operate on the same sequence and
|
|
177
|
+
might overwrite each other.
|
|
178
|
+
|
|
179
|
+
:param input_sequences: The sequences to be used as input to the action
|
|
180
|
+
function.
|
|
181
|
+
:param output_sequences: The sequences to be used as output.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def decorator(action):
|
|
185
|
+
self.add_update_action(
|
|
186
|
+
action,
|
|
187
|
+
input_sequences=input_sequences,
|
|
188
|
+
output_sequences=output_sequences,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def inner(*args, **kwargs):
|
|
192
|
+
action(*args, **kwargs)
|
|
193
|
+
|
|
194
|
+
return inner
|
|
195
|
+
|
|
196
|
+
return decorator
|
|
197
|
+
|
|
198
|
+
def update(self):
|
|
199
|
+
"""
|
|
200
|
+
Performs all registered update actions.
|
|
201
|
+
"""
|
|
202
|
+
self._read_sequences_from_tema()
|
|
203
|
+
for action in self.update_actions:
|
|
204
|
+
action()
|
|
205
|
+
self._write_sequences_to_tema()
|
|
206
|
+
|
|
207
|
+
def request_shutdown(self):
|
|
208
|
+
"""
|
|
209
|
+
Requests that the python-java bridge be shutdown.
|
|
210
|
+
:return:
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def wait_for_shutdown():
|
|
214
|
+
# Wait for shutdown request
|
|
215
|
+
self.shutdown.wait()
|
|
216
|
+
self.shutdown.clear()
|
|
217
|
+
self.tema.shutdown()
|
|
218
|
+
|
|
219
|
+
self.connection_thread = threading.Thread(target=wait_for_shutdown)
|
|
220
|
+
self.connection_thread.start()
|
|
221
|
+
|
|
222
|
+
self.shutdown.set()
|
|
223
|
+
|
|
224
|
+
def _read_sequences_from_tema(self):
|
|
225
|
+
"""
|
|
226
|
+
Updates the sequences available to temapy with new input data from tema.
|
|
227
|
+
|
|
228
|
+
Runs every time tema calls for an update.
|
|
229
|
+
"""
|
|
230
|
+
# FIXME: Future improvement to utilize the updated range to minimize
|
|
231
|
+
# Java-Python data transfer
|
|
232
|
+
for (
|
|
233
|
+
tema_sequence_name,
|
|
234
|
+
tema_sequence,
|
|
235
|
+
) in self.tema.entry_point.getInputSequences().items():
|
|
236
|
+
self.input_sequences[str(tema_sequence_name)] = Sequence.of_tema_sequence(
|
|
237
|
+
tema_sequence
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
for (
|
|
241
|
+
tema_sequence_name,
|
|
242
|
+
tema_sequence,
|
|
243
|
+
) in self.tema.entry_point.getOutputSequences().items():
|
|
244
|
+
self.output_sequences[str(tema_sequence_name)] = Sequence.of_tema_sequence(
|
|
245
|
+
tema_sequence
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _write_sequences_to_tema(self):
|
|
249
|
+
"""
|
|
250
|
+
Updates tema with the values of the sequences in temapy.
|
|
251
|
+
"""
|
|
252
|
+
# FIXME: Future improvement to utilize the updated range to minimize
|
|
253
|
+
# Java-Python data transfer
|
|
254
|
+
tema = self.tema.entry_point
|
|
255
|
+
for sequence_name, tema_sequence in tema.getOutputSequences().items():
|
|
256
|
+
if sequence_name not in self.output_sequences:
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
tema_sequence_samples = tema_sequence.getSamples()
|
|
260
|
+
|
|
261
|
+
for timestamp, sample in self.output_sequences[
|
|
262
|
+
sequence_name
|
|
263
|
+
].samples.items():
|
|
264
|
+
java_double_array = self.tema.new_array(
|
|
265
|
+
self.tema.jvm.double, len(sample.data)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
for i, data_point in enumerate(sample.data):
|
|
269
|
+
java_double_array[i] = data_point
|
|
270
|
+
|
|
271
|
+
tema_sequence_samples[timestamp].setData(java_double_array)
|
|
272
|
+
tema_sequence_samples[timestamp].setStatus(sample.status.value)
|
|
273
|
+
|
|
274
|
+
def _parse_tema_port(self):
|
|
275
|
+
"""
|
|
276
|
+
Parses the --tema_port program argument if it exists and sets the
|
|
277
|
+
self.tema_port member accordingly.
|
|
278
|
+
|
|
279
|
+
If the argument is not specified python will start its server on the
|
|
280
|
+
default port (see py4j.java_gateway.DEFAULT_PORT)
|
|
281
|
+
"""
|
|
282
|
+
parser = argparse.ArgumentParser()
|
|
283
|
+
parser.add_argument(
|
|
284
|
+
"--tema_port", help="The port the java server is running on"
|
|
285
|
+
)
|
|
286
|
+
args = parser.parse_args()
|
|
287
|
+
if args.tema_port is None:
|
|
288
|
+
self.tema_port = DEFAULT_PORT
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
self.tema_port = int(args.tema_port)
|
temapy/sequences.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classes to represent TEMA sequence data in Python.
|
|
3
|
+
|
|
4
|
+
Users should not need to create their own sequences or samples but
|
|
5
|
+
simply edit those that are provided by Temapy.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Sequence:
|
|
12
|
+
"""
|
|
13
|
+
Represents a sequence as a dict mapping a frame-index to a Sample. Also
|
|
14
|
+
provides the updated_range, i.e. the range of indexes that have changed
|
|
15
|
+
since the last update, as a tuple.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, samples, updated_range):
|
|
19
|
+
self.samples = samples
|
|
20
|
+
self.updated_range = updated_range
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def of_tema_sequence(cls, tema_sequence):
|
|
24
|
+
"""
|
|
25
|
+
Factory method to create a new sequence with data copied from the given
|
|
26
|
+
sequence from Tema.
|
|
27
|
+
|
|
28
|
+
:param tema_sequence: The sequence to copy as returned from Tema
|
|
29
|
+
:return: A copy of the Tema sequence
|
|
30
|
+
"""
|
|
31
|
+
# TODO: Future improvement to utilize the updated range to
|
|
32
|
+
# minimize Java-Python data transfer
|
|
33
|
+
samples = {}
|
|
34
|
+
for timestamp, tema_sample in tema_sequence.getSamples().items():
|
|
35
|
+
samples[timestamp] = Sample.of_tema_sample(tema_sample)
|
|
36
|
+
|
|
37
|
+
updated_range = (
|
|
38
|
+
int(tema_sequence.getUpdatedRange().start()),
|
|
39
|
+
int(tema_sequence.getUpdatedRange().stop()),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return cls(samples, updated_range)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Sample:
|
|
46
|
+
"""
|
|
47
|
+
Represents a sample as a list of data points and a status.
|
|
48
|
+
|
|
49
|
+
For 2D positional data the data components are [x, y], i.e. data[0] will
|
|
50
|
+
give the x component and data[1] the y component.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, data, status):
|
|
54
|
+
self.data = data
|
|
55
|
+
# TODO: status should be represented as an enum, will be done
|
|
56
|
+
# when Java side is being implemented.
|
|
57
|
+
self.status = status
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def of_tema_sample(cls, tema_sample):
|
|
61
|
+
"""
|
|
62
|
+
Factory method to create a new sample with data copied from the given
|
|
63
|
+
sample from TEMA.
|
|
64
|
+
|
|
65
|
+
NOTE: This method is not intended to be used directly when writing
|
|
66
|
+
scripts.
|
|
67
|
+
|
|
68
|
+
:param tema_sample: The sample to copy as returned from Tema.
|
|
69
|
+
:return: A copy of the Tema sample
|
|
70
|
+
"""
|
|
71
|
+
data = [float(data_point) for data_point in tema_sample.getData()]
|
|
72
|
+
status = Status(tema_sample.getStatusAsInt())
|
|
73
|
+
return cls(list(data), status)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Status(Enum):
|
|
77
|
+
"""
|
|
78
|
+
Enum for statuses compatible with Tema.
|
|
79
|
+
|
|
80
|
+
In the normal case, any sample that is not valid (see Status.is_valid())
|
|
81
|
+
should not be used for calculation.
|
|
82
|
+
|
|
83
|
+
Any sample changed by a script should usually be given the Status CALCULATED.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
NONE = 1
|
|
87
|
+
"""
|
|
88
|
+
No status or unknown status.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
FAILED = 2
|
|
92
|
+
"""
|
|
93
|
+
Failed status. The data of the Sample failed in creation and that the data
|
|
94
|
+
might not even be readable. Is invalid and cannot be used for calculations.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
SLEEPING = 3
|
|
98
|
+
"""
|
|
99
|
+
Sleeping status. The data of the Sample is currently set to be ignored for
|
|
100
|
+
calculation. The data may be meaningful but should be considered invalid
|
|
101
|
+
and not be used for calculations.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
PREDICTED = 4
|
|
105
|
+
"""
|
|
106
|
+
Predicted status. Used by trackers for failed samples that are predicted
|
|
107
|
+
until they are either found again or declared lost. The data of the Sample
|
|
108
|
+
may be meaningful but should be considered invalid and not be used for
|
|
109
|
+
calculations.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
MANUAL = 5
|
|
113
|
+
"""
|
|
114
|
+
Manual status. The data of the Sample has been set manually and is therefore
|
|
115
|
+
considered to be valid for calculations.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
CALCULATED = 6
|
|
119
|
+
"""
|
|
120
|
+
Calculated status. The data of the Sample has been successfully calculated
|
|
121
|
+
and may be used for further calculations.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
INTERPOLATED = 7
|
|
125
|
+
"""
|
|
126
|
+
Interpolated status. Similar to CALCULATED but the data is interpolated from
|
|
127
|
+
other Samples.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def is_valid(self):
|
|
131
|
+
"""
|
|
132
|
+
Checks if the status is noe that is considered valid
|
|
133
|
+
:return: True if the Status is valid
|
|
134
|
+
"""
|
|
135
|
+
return self not in [
|
|
136
|
+
Status.NONE,
|
|
137
|
+
Status.FAILED,
|
|
138
|
+
Status.SLEEPING,
|
|
139
|
+
Status.PREDICTED,
|
|
140
|
+
]
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: temapy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python interface for scripting with TEMA
|
|
5
|
+
Author-email: Image Systems <support@imagesystems.se>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: py4j
|
|
12
|
+
|
|
13
|
+
# TemaPy
|
|
14
|
+
|
|
15
|
+
This package is designed to be used with Image Systems TEMA Platform Python
|
|
16
|
+
module to enable Python scripting functionality to TEMA.
|
|
17
|
+
|
|
18
|
+
For detail instructions on how to enable your scripts in TEMA see the
|
|
19
|
+
corresponding help pages in TEMA Connect.
|
|
20
|
+
|
|
21
|
+
## install using pip:
|
|
22
|
+
|
|
23
|
+
To develop and run scripts in TEMA you need this package installed in the
|
|
24
|
+
Python environment that you will use in TEMA.
|
|
25
|
+
|
|
26
|
+
pip install temapy
|
|
27
|
+
|
|
28
|
+
## Getting started: Create a TEMA compatible script
|
|
29
|
+
|
|
30
|
+
To create a TEMA compatible script, first import the `TemaGateway` class from
|
|
31
|
+
the `gateway` module and create a `TemaGateway`.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from temapy.gateway import TemaGateway
|
|
35
|
+
|
|
36
|
+
gateway = TemaGateway()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the script is started using TEMA the `TemaGateway` will handle the connection
|
|
40
|
+
and data transfer between TemaPy and TEMA automatically. The only thing left to
|
|
41
|
+
do is to write your calculation function, referred to as an "update action", and
|
|
42
|
+
register it using the Gateway.
|
|
43
|
+
|
|
44
|
+
Here is an example update action that takes two input sequences, and for each
|
|
45
|
+
sample calculates the average value and store it in a third output sequence.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
def average_per_sample(seq_1, seq_2, seq_out):
|
|
49
|
+
both_sequences = zip(seq_1.samples.items(), seq_2.samples.values())
|
|
50
|
+
for (time_1, sample_1), sample_2 in both_sequences:
|
|
51
|
+
if seq_1.samples[time_1].status.is_valid():
|
|
52
|
+
seq_out.samples[time_1].data[0] = (sample_1.data[0] + sample_2.data[0]) / 2
|
|
53
|
+
seq_out.samples[time_1].data[1] = (sample_1.data[1] + sample_2.data[1]) / 2
|
|
54
|
+
seq_out.samples[time_1].status = Status.CALCULATED
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Note:** The output sequence is supplied as an input to the function. This is
|
|
58
|
+
because TemaPy is not allowed to create new sequences, only change data in the
|
|
59
|
+
sequences supplied by TEMA.
|
|
60
|
+
|
|
61
|
+
There are two ways of registering your function so that TEMA will run it when
|
|
62
|
+
input sequences change, either by calling the `add_update_action` method in the
|
|
63
|
+
`TemaGateway` or by using the `update_action` decorator, also from the
|
|
64
|
+
`TemaGateway` class.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Using the add_update_action() function
|
|
68
|
+
def average_per_sample(seq_1, seq_2, seq_out):
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
gateway.add_update_action(average_per_sample,
|
|
72
|
+
input_sequences=("p1_pos", "p2_pos"),
|
|
73
|
+
output_sequences=("avg_pos",))
|
|
74
|
+
|
|
75
|
+
# Using the update_action decorator
|
|
76
|
+
@gateway.update_action(input_sequences=("p1_pos", "p2_pos"), output_sequences=("avg_pos",))
|
|
77
|
+
def average_per_sample(seq_1, seq_2, seq_out):
|
|
78
|
+
...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
In both cases the `input_sequences` and `output_sequences` parameter specify
|
|
82
|
+
what TEMA sequences the update action will operate on. The strings in those
|
|
83
|
+
parameters should correspond with the variable names given to the sequences in
|
|
84
|
+
the Python Script Setup pane in TEMA. The sequences from TEMA will then
|
|
85
|
+
automatically map to the input parameters in your update action in order, first
|
|
86
|
+
input sequences and then output sequences. In this example this means that the
|
|
87
|
+
sequence named "p1_pos" in TEMA is mapped to the `seq_1` parameter in the
|
|
88
|
+
function, "p2_pos" is mapped to `seq_2` and "avg_pos" is mapped to `out_seq`.
|
|
89
|
+
|
|
90
|
+
Now your script is ready to be used in TEMA, for more information about how to
|
|
91
|
+
enable your script in TEMA, see the corresponding help pages in TEMA Connect.
|
|
92
|
+
|
|
93
|
+
For more details on how to work with TemaPy, continue reading below.
|
|
94
|
+
Additionally, more example scripts can be found in the `temapy.examples`
|
|
95
|
+
package.
|
|
96
|
+
|
|
97
|
+
## The gateway
|
|
98
|
+
|
|
99
|
+
The `TemaGateway` handles the connection to TEMA and allows you to access and
|
|
100
|
+
manipulate data sequences specified using the TEMA Python Scripting Setup. The
|
|
101
|
+
connection is handled mostly automatically as soon as an instance of
|
|
102
|
+
`TemaGateway` is created.
|
|
103
|
+
|
|
104
|
+
### Update actions
|
|
105
|
+
|
|
106
|
+
The update actions are your calculation functions that are run each time the
|
|
107
|
+
input sequences specified in TEMA are updated. This means for example that each
|
|
108
|
+
update action will run again for each tracked frame as long as the script is
|
|
109
|
+
active.
|
|
110
|
+
|
|
111
|
+
While it is not possible to run multiple Python scripts simultaneously in TEMA,
|
|
112
|
+
it is possible to add multiple update actions to the same script. Whenever TEMA
|
|
113
|
+
calls for the script to update, all update actions will be executed in the order
|
|
114
|
+
they were added. Any changes made to sequences in an update action will be
|
|
115
|
+
carried over to any following update actions which means that it is possible to
|
|
116
|
+
chain actions together.
|
|
117
|
+
|
|
118
|
+
The input and output sequences used by your update actions are copies of the
|
|
119
|
+
data in TEMA.
|
|
120
|
+
|
|
121
|
+
The input sequences are sequences that already exists in TEMA and are added from
|
|
122
|
+
a list of available sequences in TEMA. While it is possible to change an input
|
|
123
|
+
sequence in your update actions, this is not recommended, as data in input
|
|
124
|
+
sequences will **not** be returned to TEMA and changing input sequences might
|
|
125
|
+
cause the scripts sequences to diverge from the sequences supplied by TEMA.
|
|
126
|
+
|
|
127
|
+
The output sequences are created by the TEMA Scripting Module for your TemaPy
|
|
128
|
+
script to edit. The output sequences will be initialized with invalid
|
|
129
|
+
placeholder values for each sample and it is up to your update actions to fill
|
|
130
|
+
them with new values, see the sections about sequences and samples for
|
|
131
|
+
instructions on how to work with sequences.
|
|
132
|
+
|
|
133
|
+
It is important to note that the separation between output and input sequences
|
|
134
|
+
does not necessarily reflect their use in your update functions. Instead it is
|
|
135
|
+
the distinction between the sequences that already exists in TEMA (input) and
|
|
136
|
+
those that are created by the scripting module (output).
|
|
137
|
+
|
|
138
|
+
## TEMA data
|
|
139
|
+
|
|
140
|
+
The data available from TEMA is in the form of sequences. In TemaPy these are
|
|
141
|
+
represented by the classes found in the `temapy.sequences` module.
|
|
142
|
+
|
|
143
|
+
The following sections describe how the data is structured and how to work with
|
|
144
|
+
it.
|
|
145
|
+
|
|
146
|
+
### Sequences
|
|
147
|
+
|
|
148
|
+
The `Sequence` is the data structure that your update actions will operate on.
|
|
149
|
+
A sequence represents a set of measurements for each frame in the original image
|
|
150
|
+
sequence in TEMA. For each frame there is a `Sample` that contains numerical
|
|
151
|
+
data from that frame of the image sequence.
|
|
152
|
+
|
|
153
|
+
#### updated range
|
|
154
|
+
|
|
155
|
+
Each sequence also has a `updated_range` which is a tuple of two integers. These
|
|
156
|
+
integers represent the first (inclusive) and last (exclusive) frame of the
|
|
157
|
+
sequence that has changed since the update actions were last run. This range can
|
|
158
|
+
be used to avoid unnecessary recalculations and slowdown during tracking in TEMA
|
|
159
|
+
with an active script. It is also good to adjust the `updated_range` of the
|
|
160
|
+
output sequences to no wider than the range of values that have been changed.
|
|
161
|
+
Otherwise, it will be assumed that all frames have been updated and the sequence
|
|
162
|
+
will be copied to tema in its entirety which might cause significant slow-down
|
|
163
|
+
during tracking.
|
|
164
|
+
|
|
165
|
+
#### Samples
|
|
166
|
+
|
|
167
|
+
The `samples` attribute is a dictionary that maps a frame number, to a `Sample`.
|
|
168
|
+
The `Sample` contains the data of that specific frame of the image sequence used
|
|
169
|
+
in TEMA. As this is a standard Python dictionary, it supports all standard dict
|
|
170
|
+
operations in Python.
|
|
171
|
+
|
|
172
|
+
### Sample
|
|
173
|
+
|
|
174
|
+
The `Sample` class contains data for a specific frame of the image sequence,
|
|
175
|
+
|
|
176
|
+
#### Data
|
|
177
|
+
|
|
178
|
+
The data of each
|
|
179
|
+
`Sample` is a tuple of numerical value, each value representing a
|
|
180
|
+
certain component of the data. What components are available depends on the type
|
|
181
|
+
of sequence. In a 2D position sequence, for example, each `Sample` contains two
|
|
182
|
+
data components, the x position and the y position (x, y).
|
|
183
|
+
|
|
184
|
+
#### Status
|
|
185
|
+
|
|
186
|
+
The `Status` of the `Sample` describe the nature of the `Sample` and if it
|
|
187
|
+
should be used for calculations. Usually it is enough to use the
|
|
188
|
+
`Status.is_valid()` method do determine if the `Sample` should be used or not.
|
|
189
|
+
For more fine grade control, each `Status` is described below.
|
|
190
|
+
|
|
191
|
+
**NONE:** No or unknown `Status`.
|
|
192
|
+
|
|
193
|
+
**FAILED:**
|
|
194
|
+
The data of the `Sample` failed in creation and that the data might not even be
|
|
195
|
+
readable, will cause undefined behaviour if used and might cause the script to
|
|
196
|
+
fail. Is invalid and cannot be used for calculations.
|
|
197
|
+
|
|
198
|
+
**SLEEPING:**
|
|
199
|
+
The data of the `Sample` is currently set to be ignored for calculation. The
|
|
200
|
+
data may be meaningful but should be considered invalid and not be used for
|
|
201
|
+
calculations.
|
|
202
|
+
|
|
203
|
+
**PREDICTED:**
|
|
204
|
+
Used by trackers for failed Samples that are predicted until they are either
|
|
205
|
+
found again or declared lost. The data of the `Sample` may be meaningful but
|
|
206
|
+
should be considered invalid and not be used for calculations.
|
|
207
|
+
|
|
208
|
+
**MANUAL:**
|
|
209
|
+
The data of the `Sample` has been set manually and is therefore considered to be
|
|
210
|
+
valid for calculations.
|
|
211
|
+
|
|
212
|
+
**CALCULATED:**
|
|
213
|
+
The data of the `Sample` has been successfully calculated and
|
|
214
|
+
may be used for further calculations.
|
|
215
|
+
|
|
216
|
+
**INTERPOLATED:**
|
|
217
|
+
Similar to `CALCULATED` but the data is interpolated from other Samples.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
temapy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
temapy/gateway.py,sha256=NmwYyd6j2sBKUMlBfpaqqzewGTbNegcnDmT2GEpB-ak,10648
|
|
3
|
+
temapy/sequences.py,sha256=baSkENB9TuieU5OXGmklI3ewhPNjOHi9bJSKtfkt1y4,4292
|
|
4
|
+
temapy/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
temapy/examples/average_as_new_sequence.py,sha256=CjmFEkeWnwLbQl8PMt1-0Lkewi-3iFG2XTliygghuLw,1034
|
|
6
|
+
temapy-1.0.0.dist-info/METADATA,sha256=9SX0wzkaNWlO8RqkWSVXWeir5O7krIX-K0yfafgNu2s,9279
|
|
7
|
+
temapy-1.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
8
|
+
temapy-1.0.0.dist-info/top_level.txt,sha256=vGCOwZT-j1DnKtCnzs1ytwrK6I-fks-nss1_9NB6Juk,7
|
|
9
|
+
temapy-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
temapy
|