temapy 1.0.0__tar.gz → 1.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: temapy
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Python interface for scripting with TEMA
5
5
  Author-email: Image Systems <support@imagesystems.se>
6
6
  License-Expression: Apache-2.0
@@ -25,6 +25,11 @@ Python environment that you will use in TEMA.
25
25
 
26
26
  pip install temapy
27
27
 
28
+ To upgrade an existing installation to a newer temapy (required when a TEMA
29
+ release asks for a minimum version):
30
+
31
+ pip install --upgrade temapy
32
+
28
33
  ## Getting started: Create a TEMA compatible script
29
34
 
30
35
  To create a TEMA compatible script, first import the `TemaGateway` class from
@@ -147,8 +152,8 @@ it.
147
152
 
148
153
  The `Sequence` is the data structure that your update actions will operate on.
149
154
  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.
155
+ sequence in TEMA. For each frame there is a sample that contains data from that
156
+ frame of the image sequence.
152
157
 
153
158
  #### updated range
154
159
 
@@ -164,54 +169,107 @@ during tracking.
164
169
 
165
170
  #### Samples
166
171
 
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.
172
+ The `samples` attribute is a dictionary that maps a frame number to a sample.
173
+ As this is a standard Python dictionary, it supports all standard dict
174
+ operations in Python. The concrete sample type depends on the sequence:
175
+ `NumericSample` for numeric data, `OutlineSample` for outlines, and
176
+ `LineSample` for 2D lines.
171
177
 
172
178
  ### Sample
173
179
 
174
- The `Sample` class contains data for a specific frame of the image sequence,
180
+ `Sample` is the base class shared by all Temapy samples. Every sample has a
181
+ `status`. Scripts normally work with one of the concrete subclasses below.
182
+
183
+ ### NumericSample
184
+
185
+ The `NumericSample` class contains numeric data for a specific frame of the
186
+ image sequence.
175
187
 
176
188
  #### Data
177
189
 
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).
190
+ The data of each `NumericSample` is a list of numerical values, each value
191
+ representing a certain component of the data. What components are available
192
+ depends on the type of sequence. In a 2D position sequence, for example, each
193
+ `NumericSample` contains two data components, the x position and the y position
194
+ (`[x, y]`). A Length scalar uses a single component in `data[0]`.
195
+
196
+ #### Outline samples
197
+
198
+ An outline (chain code) input sequence contains `OutlineSample` objects instead
199
+ of `NumericSample` objects. Each outline sample provides:
200
+
201
+ - `points`: a list of `(x, y)` tuples describing the contour. Lens correction
202
+ and coordinate transformation have already been applied by TEMA.
203
+ - `start`: the raw `(x, y)` starting point of the chain code.
204
+ - `links`: the raw chain links as a string containing `U`, `D`, `L`, and `R`.
205
+ - `status`: the sample status, used in the same way as for numeric samples.
206
+
207
+ Outline sequences are currently input-only. Scripts can derive values from an
208
+ outline and write those values to numeric output sequences. See
209
+ `temapy.examples.outline_width` for an example.
210
+
211
+ #### 2D line outputs
212
+
213
+ A 2D line output uses a `LineSample`. Its `start` and `end` attributes are
214
+ coordinate lists describing the line's two endpoints (currently `[x, y]`):
215
+
216
+ ```python
217
+ line_sample.start = [x1, y1]
218
+ line_sample.end = [x2, y2]
219
+ line_sample.status = Status.CALCULATED
220
+ ```
221
+
222
+ Choose **2D line** as the output physical type in TEMA. The line can be added
223
+ to the Image Diagram as a distance overlay.
224
+
225
+ Output sequences share a world feature named by the prefix before the first
226
+ ``_`` (for example ``point_position`` and ``point_speed`` both map to feature
227
+ ``point``). 2D line arrays and 2D distances use a line feature; other types use
228
+ a point feature.
229
+
230
+ To show the length in meters on a line overlay (and in Time Table / 2D Diagram),
231
+ add a second output such as ``line_distance`` next to ``line`` with physical
232
+ type **2D distance**, and write the offset components:
233
+
234
+ ```python
235
+ line_distance.samples[frame].data[0] = end[0] - start[0]
236
+ line_distance.samples[frame].data[1] = end[1] - start[1]
237
+ line_distance.samples[frame].status = Status.CALCULATED
238
+ ```
239
+
240
+ See `temapy.examples.two_points_to_line`.
183
241
 
184
242
  #### Status
185
243
 
186
- The `Status` of the `Sample` describe the nature of the `Sample` and if it
244
+ The `Status` of a sample describes the nature of the sample and if it
187
245
  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.
246
+ `Status.is_valid()` method to determine if the sample should be used or not.
189
247
  For more fine grade control, each `Status` is described below.
190
248
 
191
249
  **NONE:** No or unknown `Status`.
192
250
 
193
251
  **FAILED:**
194
- The data of the `Sample` failed in creation and that the data might not even be
252
+ The data of the sample failed in creation and that the data might not even be
195
253
  readable, will cause undefined behaviour if used and might cause the script to
196
254
  fail. Is invalid and cannot be used for calculations.
197
255
 
198
256
  **SLEEPING:**
199
- The data of the `Sample` is currently set to be ignored for calculation. The
257
+ The data of the sample is currently set to be ignored for calculation. The
200
258
  data may be meaningful but should be considered invalid and not be used for
201
259
  calculations.
202
260
 
203
261
  **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
262
+ Used by trackers for failed samples that are predicted until they are either
263
+ found again or declared lost. The data of the sample may be meaningful but
206
264
  should be considered invalid and not be used for calculations.
207
265
 
208
266
  **MANUAL:**
209
- The data of the `Sample` has been set manually and is therefore considered to be
267
+ The data of the sample has been set manually and is therefore considered to be
210
268
  valid for calculations.
211
269
 
212
270
  **CALCULATED:**
213
- The data of the `Sample` has been successfully calculated and
271
+ The data of the sample has been successfully calculated and
214
272
  may be used for further calculations.
215
273
 
216
274
  **INTERPOLATED:**
217
- Similar to `CALCULATED` but the data is interpolated from other Samples.
275
+ Similar to `CALCULATED` but the data is interpolated from other samples.
@@ -13,6 +13,11 @@ Python environment that you will use in TEMA.
13
13
 
14
14
  pip install temapy
15
15
 
16
+ To upgrade an existing installation to a newer temapy (required when a TEMA
17
+ release asks for a minimum version):
18
+
19
+ pip install --upgrade temapy
20
+
16
21
  ## Getting started: Create a TEMA compatible script
17
22
 
18
23
  To create a TEMA compatible script, first import the `TemaGateway` class from
@@ -135,8 +140,8 @@ it.
135
140
 
136
141
  The `Sequence` is the data structure that your update actions will operate on.
137
142
  A sequence represents a set of measurements for each frame in the original image
138
- sequence in TEMA. For each frame there is a `Sample` that contains numerical
139
- data from that frame of the image sequence.
143
+ sequence in TEMA. For each frame there is a sample that contains data from that
144
+ frame of the image sequence.
140
145
 
141
146
  #### updated range
142
147
 
@@ -152,54 +157,107 @@ during tracking.
152
157
 
153
158
  #### Samples
154
159
 
155
- The `samples` attribute is a dictionary that maps a frame number, to a `Sample`.
156
- The `Sample` contains the data of that specific frame of the image sequence used
157
- in TEMA. As this is a standard Python dictionary, it supports all standard dict
158
- operations in Python.
160
+ The `samples` attribute is a dictionary that maps a frame number to a sample.
161
+ As this is a standard Python dictionary, it supports all standard dict
162
+ operations in Python. The concrete sample type depends on the sequence:
163
+ `NumericSample` for numeric data, `OutlineSample` for outlines, and
164
+ `LineSample` for 2D lines.
159
165
 
160
166
  ### Sample
161
167
 
162
- The `Sample` class contains data for a specific frame of the image sequence,
168
+ `Sample` is the base class shared by all Temapy samples. Every sample has a
169
+ `status`. Scripts normally work with one of the concrete subclasses below.
170
+
171
+ ### NumericSample
172
+
173
+ The `NumericSample` class contains numeric data for a specific frame of the
174
+ image sequence.
163
175
 
164
176
  #### Data
165
177
 
166
- The data of each
167
- `Sample` is a tuple of numerical value, each value representing a
168
- certain component of the data. What components are available depends on the type
169
- of sequence. In a 2D position sequence, for example, each `Sample` contains two
170
- data components, the x position and the y position (x, y).
178
+ The data of each `NumericSample` is a list of numerical values, each value
179
+ representing a certain component of the data. What components are available
180
+ depends on the type of sequence. In a 2D position sequence, for example, each
181
+ `NumericSample` contains two data components, the x position and the y position
182
+ (`[x, y]`). A Length scalar uses a single component in `data[0]`.
183
+
184
+ #### Outline samples
185
+
186
+ An outline (chain code) input sequence contains `OutlineSample` objects instead
187
+ of `NumericSample` objects. Each outline sample provides:
188
+
189
+ - `points`: a list of `(x, y)` tuples describing the contour. Lens correction
190
+ and coordinate transformation have already been applied by TEMA.
191
+ - `start`: the raw `(x, y)` starting point of the chain code.
192
+ - `links`: the raw chain links as a string containing `U`, `D`, `L`, and `R`.
193
+ - `status`: the sample status, used in the same way as for numeric samples.
194
+
195
+ Outline sequences are currently input-only. Scripts can derive values from an
196
+ outline and write those values to numeric output sequences. See
197
+ `temapy.examples.outline_width` for an example.
198
+
199
+ #### 2D line outputs
200
+
201
+ A 2D line output uses a `LineSample`. Its `start` and `end` attributes are
202
+ coordinate lists describing the line's two endpoints (currently `[x, y]`):
203
+
204
+ ```python
205
+ line_sample.start = [x1, y1]
206
+ line_sample.end = [x2, y2]
207
+ line_sample.status = Status.CALCULATED
208
+ ```
209
+
210
+ Choose **2D line** as the output physical type in TEMA. The line can be added
211
+ to the Image Diagram as a distance overlay.
212
+
213
+ Output sequences share a world feature named by the prefix before the first
214
+ ``_`` (for example ``point_position`` and ``point_speed`` both map to feature
215
+ ``point``). 2D line arrays and 2D distances use a line feature; other types use
216
+ a point feature.
217
+
218
+ To show the length in meters on a line overlay (and in Time Table / 2D Diagram),
219
+ add a second output such as ``line_distance`` next to ``line`` with physical
220
+ type **2D distance**, and write the offset components:
221
+
222
+ ```python
223
+ line_distance.samples[frame].data[0] = end[0] - start[0]
224
+ line_distance.samples[frame].data[1] = end[1] - start[1]
225
+ line_distance.samples[frame].status = Status.CALCULATED
226
+ ```
227
+
228
+ See `temapy.examples.two_points_to_line`.
171
229
 
172
230
  #### Status
173
231
 
174
- The `Status` of the `Sample` describe the nature of the `Sample` and if it
232
+ The `Status` of a sample describes the nature of the sample and if it
175
233
  should be used for calculations. Usually it is enough to use the
176
- `Status.is_valid()` method do determine if the `Sample` should be used or not.
234
+ `Status.is_valid()` method to determine if the sample should be used or not.
177
235
  For more fine grade control, each `Status` is described below.
178
236
 
179
237
  **NONE:** No or unknown `Status`.
180
238
 
181
239
  **FAILED:**
182
- The data of the `Sample` failed in creation and that the data might not even be
240
+ The data of the sample failed in creation and that the data might not even be
183
241
  readable, will cause undefined behaviour if used and might cause the script to
184
242
  fail. Is invalid and cannot be used for calculations.
185
243
 
186
244
  **SLEEPING:**
187
- The data of the `Sample` is currently set to be ignored for calculation. The
245
+ The data of the sample is currently set to be ignored for calculation. The
188
246
  data may be meaningful but should be considered invalid and not be used for
189
247
  calculations.
190
248
 
191
249
  **PREDICTED:**
192
- Used by trackers for failed Samples that are predicted until they are either
193
- found again or declared lost. The data of the `Sample` may be meaningful but
250
+ Used by trackers for failed samples that are predicted until they are either
251
+ found again or declared lost. The data of the sample may be meaningful but
194
252
  should be considered invalid and not be used for calculations.
195
253
 
196
254
  **MANUAL:**
197
- The data of the `Sample` has been set manually and is therefore considered to be
255
+ The data of the sample has been set manually and is therefore considered to be
198
256
  valid for calculations.
199
257
 
200
258
  **CALCULATED:**
201
- The data of the `Sample` has been successfully calculated and
259
+ The data of the sample has been successfully calculated and
202
260
  may be used for further calculations.
203
261
 
204
262
  **INTERPOLATED:**
205
- Similar to `CALCULATED` but the data is interpolated from other Samples.
263
+ Similar to `CALCULATED` but the data is interpolated from other samples.
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "temapy"
10
- version = "1.0.0"
10
+ version = "1.2.0"
11
11
  authors = [
12
12
  { name = "Image Systems", email = "support@imagesystems.se" }
13
13
  ]
@@ -46,6 +46,7 @@ env_list = [
46
46
  "lint",
47
47
  # FIXME: Disabled to not prevent merging. We should probably do type-checking in the future
48
48
  # "type",
49
+ "3.14",
49
50
  "3.13",
50
51
  "3.12",
51
52
  "3.11",
@@ -67,7 +68,7 @@ commands = [
67
68
  description = "run linters"
68
69
  deps = ["pylint"]
69
70
  commands = [
70
- ["pylint", { replace = "posargs", default = ["src", "scripts"], extend = true }, "--disable=fixme,too-few-public-methods, too-many-instance-attributes", ],
71
+ ["pylint", { replace = "posargs", default = ["src"], extend = true }, "--disable=fixme,too-few-public-methods, too-many-instance-attributes", ],
71
72
  ]
72
73
 
73
74
 
@@ -75,5 +76,5 @@ commands = [
75
76
  description = "run type checks"
76
77
  deps = ["mypy"]
77
78
  commands = [
78
- ["mypy", { replace = "posargs", default = ["src", "scripts"], extend = true }],
79
+ ["mypy", { replace = "posargs", default = ["src"], extend = true }],
79
80
  ]
@@ -0,0 +1,25 @@
1
+ """
2
+ Calculates the axis-aligned width of an outline for every valid sample.
3
+ """
4
+
5
+ from temapy.gateway import TemaGateway
6
+ from temapy.sequences import Status
7
+
8
+
9
+ tema_gateway = TemaGateway()
10
+
11
+
12
+ @tema_gateway.update_action(
13
+ input_sequences=("outline",), output_sequences=("width",)
14
+ )
15
+ def calculate_outline_width(outline, width):
16
+ """
17
+ Writes the difference between the largest and smallest x coordinate.
18
+ """
19
+ for frame, sample in outline.samples.items():
20
+ if not sample.status.is_valid() or not sample.points:
21
+ continue
22
+
23
+ x_coordinates = [x for x, _ in sample.points]
24
+ width.samples[frame].data[0] = max(x_coordinates) - min(x_coordinates)
25
+ width.samples[frame].status = Status.CALCULATED
@@ -0,0 +1,32 @@
1
+ """
2
+ Builds a 2D line from two point sequences, plus a 2D distance sibling for the overlay.
3
+ """
4
+
5
+ from temapy.gateway import TemaGateway
6
+ from temapy.sequences import Status
7
+
8
+
9
+ tema_gateway = TemaGateway()
10
+
11
+
12
+ @tema_gateway.update_action(
13
+ input_sequences=("p1", "p2"), output_sequences=("line", "line_distance")
14
+ )
15
+ def make_line(p1, p2, line, line_distance):
16
+ """
17
+ Writes a two-point line and its 2D distance for every frame where both points are valid.
18
+
19
+ Name the distance output with the same prefix as the line (here ``line_distance`` next to
20
+ ``line``) and set its physical type to **2D distance** so both share the same line feature.
21
+ """
22
+ for frame, sample_p1 in p1.samples.items():
23
+ sample_p2 = p2.samples[frame]
24
+ if sample_p1.status.is_valid() and sample_p2.status.is_valid():
25
+ start = [sample_p1.data[0], sample_p1.data[1]]
26
+ end = [sample_p2.data[0], sample_p2.data[1]]
27
+ line.samples[frame].start = start
28
+ line.samples[frame].end = end
29
+ line.samples[frame].status = Status.CALCULATED
30
+ line_distance.samples[frame].data[0] = end[0] - start[0]
31
+ line_distance.samples[frame].data[1] = end[1] - start[1]
32
+ line_distance.samples[frame].status = Status.CALCULATED
@@ -89,9 +89,10 @@ class TemaGateway:
89
89
  called.
90
90
  :return:
91
91
  """
92
- # Start py4j Java Gateway with a callback server
92
+ # Start py4j Java Gateway with a callback server.
93
93
  self.tema = JavaGateway(
94
- gateway_parameters=GatewayParameters(port=self.tema_port),
94
+ gateway_parameters=GatewayParameters(port=self.tema_port,
95
+ auto_convert=True),
95
96
  # Dynamically allocate a port for the callback server
96
97
  callback_server_parameters=CallbackServerParameters(port=0),
97
98
  python_server_entry_point=self,
@@ -261,16 +262,10 @@ class TemaGateway:
261
262
  for timestamp, sample in self.output_sequences[
262
263
  sequence_name
263
264
  ].samples.items():
264
- java_double_array = self.tema.new_array(
265
- self.tema.jvm.double, len(sample.data)
265
+ sample.write_to_tema_sample(
266
+ tema_sequence_samples[timestamp]
266
267
  )
267
268
 
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
269
  def _parse_tema_port(self):
275
270
  """
276
271
  Parses the --tema_port program argument if it exists and sets the
@@ -0,0 +1,282 @@
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 abc import ABC, abstractmethod
9
+ from array import array
10
+ from enum import Enum
11
+ import sys
12
+
13
+ from py4j.protocol import Py4JError
14
+
15
+
16
+ class Sequence:
17
+ """
18
+ Represents a sequence as a dict mapping a frame-index to a Sample. Also
19
+ provides the updated_range, i.e. the range of indexes that have changed
20
+ since the last update, as a tuple.
21
+ """
22
+
23
+ def __init__(self, samples, updated_range):
24
+ self.samples = samples
25
+ self.updated_range = updated_range
26
+
27
+ @classmethod
28
+ def of_tema_sequence(cls, tema_sequence):
29
+ """
30
+ Factory method to create a new sequence with data copied from the given
31
+ sequence from Tema.
32
+
33
+ :param tema_sequence: The sequence to copy as returned from Tema
34
+ :return: A copy of the Tema sequence
35
+ """
36
+ # TODO: Future improvement to utilize the updated range to
37
+ # minimize Java-Python data transfer
38
+ try:
39
+ sample_type = str(tema_sequence.getSampleType())
40
+ except Py4JError:
41
+ # Older TEMA versions only expose numeric samples.
42
+ sample_type = "NUMERIC"
43
+ sample_factory = _SAMPLE_FACTORIES.get(sample_type, NumericSample)
44
+ samples = {}
45
+ for timestamp, tema_sample in tema_sequence.getSamples().items():
46
+ samples[timestamp] = sample_factory.of_tema_sample(tema_sample)
47
+
48
+ updated_range = (
49
+ int(tema_sequence.getUpdatedRange().start()),
50
+ int(tema_sequence.getUpdatedRange().stop()),
51
+ )
52
+
53
+ return cls(samples, updated_range)
54
+
55
+
56
+ class Sample(ABC):
57
+ """
58
+ Base class for all Temapy samples.
59
+
60
+ Concrete sample types share a ``status`` and type-specific data attributes.
61
+ """
62
+
63
+ def __init__(self, status):
64
+ self.status = status
65
+
66
+ @classmethod
67
+ @abstractmethod
68
+ def of_tema_sample(cls, tema_sample):
69
+ """
70
+ Factory method to create a new sample with data copied from the given
71
+ sample from TEMA.
72
+
73
+ NOTE: This method is not intended to be used directly when writing
74
+ scripts.
75
+
76
+ :param tema_sample: The sample to copy as returned from Tema.
77
+ :return: A copy of the Tema sample
78
+ """
79
+
80
+ @abstractmethod
81
+ def write_to_tema_sample(self, tema_sample):
82
+ """
83
+ Copies this sample to its corresponding TEMA sample.
84
+
85
+ NOTE: This method is used internally by the gateway.
86
+
87
+ :param tema_sample: The TEMA sample to write to.
88
+ """
89
+
90
+
91
+ class NumericSample(Sample):
92
+ """
93
+ Represents a numeric sample as a list of data components and a status.
94
+
95
+ For 2D positional data the data components are [x, y], i.e. data[0] will
96
+ give the x component and data[1] the y component. Scalars such as Length
97
+ use a single component in data[0].
98
+ """
99
+
100
+ def __init__(self, data, status):
101
+ super().__init__(status)
102
+ self.data = data
103
+
104
+ @classmethod
105
+ def of_tema_sample(cls, tema_sample):
106
+ """
107
+ Factory method to create a new sample with data copied from the given
108
+ sample from TEMA.
109
+
110
+ NOTE: This method is not intended to be used directly when writing
111
+ scripts.
112
+
113
+ :param tema_sample: The sample to copy as returned from Tema.
114
+ :return: A copy of the Tema sample
115
+ """
116
+ data = [float(data_point) for data_point in tema_sample.getData()]
117
+ status = Status(tema_sample.getStatusAsInt())
118
+ return cls(list(data), status)
119
+
120
+ def write_to_tema_sample(self, tema_sample):
121
+ """
122
+ Copies this numeric sample to its corresponding TEMA sample.
123
+
124
+ NOTE: This method is used internally by the gateway.
125
+ """
126
+ # auto_convert turns this list into a Java List for setData
127
+ tema_sample.setData(list(self.data))
128
+ tema_sample.setStatus(self.status.value)
129
+
130
+
131
+ class LineSample(Sample):
132
+ """
133
+ Represents a two-point line sample.
134
+
135
+ ``start`` and ``end`` are coordinate lists, currently ``[x, y]`` for 2D
136
+ lines in TEMA. A line always has exactly these two endpoints.
137
+ """
138
+
139
+ def __init__(self, start, end, status):
140
+ super().__init__(status)
141
+ self.start = start
142
+ self.end = end
143
+
144
+ @classmethod
145
+ def of_tema_sample(cls, tema_sample):
146
+ """
147
+ Factory method to copy a line sample returned from TEMA.
148
+
149
+ NOTE: This method is not intended to be used directly when writing
150
+ scripts.
151
+ """
152
+ start = [float(tema_sample.getStartX()), float(tema_sample.getStartY())]
153
+ end = [float(tema_sample.getEndX()), float(tema_sample.getEndY())]
154
+ status = Status(tema_sample.getStatusAsInt())
155
+ return cls(start, end, status)
156
+
157
+ def write_to_tema_sample(self, tema_sample):
158
+ """
159
+ Copies this line sample to its corresponding TEMA sample.
160
+
161
+ NOTE: This method is used internally by the gateway.
162
+ """
163
+ tema_sample.setStartX(self.start[0])
164
+ tema_sample.setStartY(self.start[1])
165
+ tema_sample.setEndX(self.end[0])
166
+ tema_sample.setEndY(self.end[1])
167
+ tema_sample.setStatus(self.status.value)
168
+
169
+
170
+ class OutlineSample(Sample):
171
+ """
172
+ Represents an outline sample.
173
+
174
+ ``points`` contains the lens-corrected and coordinate-transformed contour
175
+ as ``(x, y)`` tuples. ``start`` and ``links`` contain the raw chain code.
176
+ """
177
+
178
+ def __init__(self, points, start, links, status):
179
+ super().__init__(status)
180
+ self.points = points
181
+ self.start = start
182
+ self.links = links
183
+
184
+ @classmethod
185
+ def of_tema_sample(cls, tema_sample):
186
+ """
187
+ Factory method to copy an outline sample returned from TEMA.
188
+
189
+ NOTE: This method is not intended to be used directly when writing
190
+ scripts.
191
+ """
192
+ values = array("d")
193
+ values.frombytes(tema_sample.getPoints())
194
+ if sys.byteorder == "little":
195
+ values.byteswap()
196
+
197
+ points = [(values[i], values[i+1]) for i in range(0, len(values), 2)]
198
+ start = (float(tema_sample.getStartX()), float(tema_sample.getStartY()))
199
+ links = str(tema_sample.getLinks())
200
+ status = Status(tema_sample.getStatusAsInt())
201
+ return cls(points, start, links, status)
202
+
203
+ def write_to_tema_sample(self, tema_sample):
204
+ """
205
+ Outline samples are not written back to TEMA.
206
+
207
+ NOTE: This method is used internally by the gateway.
208
+ """
209
+
210
+
211
+ _SAMPLE_FACTORIES = {
212
+ "NUMERIC": NumericSample,
213
+ "CHAIN_CODE": OutlineSample,
214
+ "LINE": LineSample,
215
+ }
216
+
217
+
218
+ class Status(Enum):
219
+ """
220
+ Enum for statuses compatible with Tema.
221
+
222
+ In the normal case, any sample that is not valid (see Status.is_valid())
223
+ should not be used for calculation.
224
+
225
+ Any sample changed by a script should usually be given the Status CALCULATED.
226
+ """
227
+
228
+ NONE = 1
229
+ """
230
+ No status or unknown status.
231
+ """
232
+
233
+ FAILED = 2
234
+ """
235
+ Failed status. The data of the Sample failed in creation and that the data
236
+ might not even be readable. Is invalid and cannot be used for calculations.
237
+ """
238
+
239
+ SLEEPING = 3
240
+ """
241
+ Sleeping status. The data of the Sample is currently set to be ignored for
242
+ calculation. The data may be meaningful but should be considered invalid
243
+ and not be used for calculations.
244
+ """
245
+
246
+ PREDICTED = 4
247
+ """
248
+ Predicted status. Used by trackers for failed samples that are predicted
249
+ until they are either found again or declared lost. The data of the Sample
250
+ may be meaningful but should be considered invalid and not be used for
251
+ calculations.
252
+ """
253
+
254
+ MANUAL = 5
255
+ """
256
+ Manual status. The data of the Sample has been set manually and is therefore
257
+ considered to be valid for calculations.
258
+ """
259
+
260
+ CALCULATED = 6
261
+ """
262
+ Calculated status. The data of the Sample has been successfully calculated
263
+ and may be used for further calculations.
264
+ """
265
+
266
+ INTERPOLATED = 7
267
+ """
268
+ Interpolated status. Similar to CALCULATED but the data is interpolated from
269
+ other Samples.
270
+ """
271
+
272
+ def is_valid(self):
273
+ """
274
+ Checks if the status is noe that is considered valid
275
+ :return: True if the Status is valid
276
+ """
277
+ return self not in [
278
+ Status.NONE,
279
+ Status.FAILED,
280
+ Status.SLEEPING,
281
+ Status.PREDICTED,
282
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: temapy
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Python interface for scripting with TEMA
5
5
  Author-email: Image Systems <support@imagesystems.se>
6
6
  License-Expression: Apache-2.0
@@ -25,6 +25,11 @@ Python environment that you will use in TEMA.
25
25
 
26
26
  pip install temapy
27
27
 
28
+ To upgrade an existing installation to a newer temapy (required when a TEMA
29
+ release asks for a minimum version):
30
+
31
+ pip install --upgrade temapy
32
+
28
33
  ## Getting started: Create a TEMA compatible script
29
34
 
30
35
  To create a TEMA compatible script, first import the `TemaGateway` class from
@@ -147,8 +152,8 @@ it.
147
152
 
148
153
  The `Sequence` is the data structure that your update actions will operate on.
149
154
  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.
155
+ sequence in TEMA. For each frame there is a sample that contains data from that
156
+ frame of the image sequence.
152
157
 
153
158
  #### updated range
154
159
 
@@ -164,54 +169,107 @@ during tracking.
164
169
 
165
170
  #### Samples
166
171
 
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.
172
+ The `samples` attribute is a dictionary that maps a frame number to a sample.
173
+ As this is a standard Python dictionary, it supports all standard dict
174
+ operations in Python. The concrete sample type depends on the sequence:
175
+ `NumericSample` for numeric data, `OutlineSample` for outlines, and
176
+ `LineSample` for 2D lines.
171
177
 
172
178
  ### Sample
173
179
 
174
- The `Sample` class contains data for a specific frame of the image sequence,
180
+ `Sample` is the base class shared by all Temapy samples. Every sample has a
181
+ `status`. Scripts normally work with one of the concrete subclasses below.
182
+
183
+ ### NumericSample
184
+
185
+ The `NumericSample` class contains numeric data for a specific frame of the
186
+ image sequence.
175
187
 
176
188
  #### Data
177
189
 
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).
190
+ The data of each `NumericSample` is a list of numerical values, each value
191
+ representing a certain component of the data. What components are available
192
+ depends on the type of sequence. In a 2D position sequence, for example, each
193
+ `NumericSample` contains two data components, the x position and the y position
194
+ (`[x, y]`). A Length scalar uses a single component in `data[0]`.
195
+
196
+ #### Outline samples
197
+
198
+ An outline (chain code) input sequence contains `OutlineSample` objects instead
199
+ of `NumericSample` objects. Each outline sample provides:
200
+
201
+ - `points`: a list of `(x, y)` tuples describing the contour. Lens correction
202
+ and coordinate transformation have already been applied by TEMA.
203
+ - `start`: the raw `(x, y)` starting point of the chain code.
204
+ - `links`: the raw chain links as a string containing `U`, `D`, `L`, and `R`.
205
+ - `status`: the sample status, used in the same way as for numeric samples.
206
+
207
+ Outline sequences are currently input-only. Scripts can derive values from an
208
+ outline and write those values to numeric output sequences. See
209
+ `temapy.examples.outline_width` for an example.
210
+
211
+ #### 2D line outputs
212
+
213
+ A 2D line output uses a `LineSample`. Its `start` and `end` attributes are
214
+ coordinate lists describing the line's two endpoints (currently `[x, y]`):
215
+
216
+ ```python
217
+ line_sample.start = [x1, y1]
218
+ line_sample.end = [x2, y2]
219
+ line_sample.status = Status.CALCULATED
220
+ ```
221
+
222
+ Choose **2D line** as the output physical type in TEMA. The line can be added
223
+ to the Image Diagram as a distance overlay.
224
+
225
+ Output sequences share a world feature named by the prefix before the first
226
+ ``_`` (for example ``point_position`` and ``point_speed`` both map to feature
227
+ ``point``). 2D line arrays and 2D distances use a line feature; other types use
228
+ a point feature.
229
+
230
+ To show the length in meters on a line overlay (and in Time Table / 2D Diagram),
231
+ add a second output such as ``line_distance`` next to ``line`` with physical
232
+ type **2D distance**, and write the offset components:
233
+
234
+ ```python
235
+ line_distance.samples[frame].data[0] = end[0] - start[0]
236
+ line_distance.samples[frame].data[1] = end[1] - start[1]
237
+ line_distance.samples[frame].status = Status.CALCULATED
238
+ ```
239
+
240
+ See `temapy.examples.two_points_to_line`.
183
241
 
184
242
  #### Status
185
243
 
186
- The `Status` of the `Sample` describe the nature of the `Sample` and if it
244
+ The `Status` of a sample describes the nature of the sample and if it
187
245
  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.
246
+ `Status.is_valid()` method to determine if the sample should be used or not.
189
247
  For more fine grade control, each `Status` is described below.
190
248
 
191
249
  **NONE:** No or unknown `Status`.
192
250
 
193
251
  **FAILED:**
194
- The data of the `Sample` failed in creation and that the data might not even be
252
+ The data of the sample failed in creation and that the data might not even be
195
253
  readable, will cause undefined behaviour if used and might cause the script to
196
254
  fail. Is invalid and cannot be used for calculations.
197
255
 
198
256
  **SLEEPING:**
199
- The data of the `Sample` is currently set to be ignored for calculation. The
257
+ The data of the sample is currently set to be ignored for calculation. The
200
258
  data may be meaningful but should be considered invalid and not be used for
201
259
  calculations.
202
260
 
203
261
  **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
262
+ Used by trackers for failed samples that are predicted until they are either
263
+ found again or declared lost. The data of the sample may be meaningful but
206
264
  should be considered invalid and not be used for calculations.
207
265
 
208
266
  **MANUAL:**
209
- The data of the `Sample` has been set manually and is therefore considered to be
267
+ The data of the sample has been set manually and is therefore considered to be
210
268
  valid for calculations.
211
269
 
212
270
  **CALCULATED:**
213
- The data of the `Sample` has been successfully calculated and
271
+ The data of the sample has been successfully calculated and
214
272
  may be used for further calculations.
215
273
 
216
274
  **INTERPOLATED:**
217
- Similar to `CALCULATED` but the data is interpolated from other Samples.
275
+ Similar to `CALCULATED` but the data is interpolated from other samples.
@@ -9,4 +9,6 @@ src/temapy.egg-info/dependency_links.txt
9
9
  src/temapy.egg-info/requires.txt
10
10
  src/temapy.egg-info/top_level.txt
11
11
  src/temapy/examples/__init__.py
12
- src/temapy/examples/average_as_new_sequence.py
12
+ src/temapy/examples/average_as_new_sequence.py
13
+ src/temapy/examples/outline_width.py
14
+ src/temapy/examples/two_points_to_line.py
@@ -1,140 +0,0 @@
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
- ]
File without changes
File without changes