monochrome 2025.1.17__py3-none-macosx_10_9_universal2.whl
Sign up to get free protection for your applications and to get access to all the features.
- monochrome/__init__.py +23 -0
- monochrome/__main__.py +6 -0
- monochrome/_version.py +16 -0
- monochrome/data/Monochrome.app/Contents/Info.plist +73 -0
- monochrome/data/Monochrome.app/Contents/MacOS/Monochrome +0 -0
- monochrome/data/Monochrome.app/Contents/Resources/Monochrome.icns +0 -0
- monochrome/data/Monochrome.app/Contents/Resources/npy-logo.icns +0 -0
- monochrome/fbs/Array3DataChunkf.py +90 -0
- monochrome/fbs/Array3DataChunku16.py +90 -0
- monochrome/fbs/Array3DataChunku8.py +90 -0
- monochrome/fbs/Array3Meta.py +271 -0
- monochrome/fbs/Array3MetaFlow.py +121 -0
- monochrome/fbs/ArrayDataType.py +9 -0
- monochrome/fbs/BitRange.py +17 -0
- monochrome/fbs/CloseVideo.py +52 -0
- monochrome/fbs/Color.py +47 -0
- monochrome/fbs/ColorMap.py +15 -0
- monochrome/fbs/Data.py +17 -0
- monochrome/fbs/DictEntry.py +65 -0
- monochrome/fbs/Filepaths.py +70 -0
- monochrome/fbs/OpacityFunction.py +15 -0
- monochrome/fbs/PointsVideo.py +173 -0
- monochrome/fbs/Quit.py +38 -0
- monochrome/fbs/Root.py +68 -0
- monochrome/fbs/VideoExport.py +143 -0
- monochrome/fbs/VideoExportFormat.py +8 -0
- monochrome/fbs/__init__.py +0 -0
- monochrome/ipc.py +624 -0
- monochrome-2025.1.17.dist-info/METADATA +166 -0
- monochrome-2025.1.17.dist-info/RECORD +33 -0
- monochrome-2025.1.17.dist-info/WHEEL +5 -0
- monochrome-2025.1.17.dist-info/entry_points.txt +3 -0
- monochrome-2025.1.17.dist-info/licenses/LICENSE.md +19 -0
monochrome/ipc.py
ADDED
@@ -0,0 +1,624 @@
|
|
1
|
+
import os
|
2
|
+
import socket
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
import time
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Dict, List, Optional, Text, Union
|
8
|
+
|
9
|
+
import flatbuffers
|
10
|
+
import numpy as np
|
11
|
+
|
12
|
+
from .fbs import (Array3DataChunkf, Array3DataChunku8, Array3DataChunku16,
|
13
|
+
Array3Meta, Array3MetaFlow, CloseVideo, Filepaths,
|
14
|
+
PointsVideo, Root, VideoExport)
|
15
|
+
from .fbs.ArrayDataType import ArrayDataType
|
16
|
+
from .fbs.BitRange import BitRange
|
17
|
+
from .fbs.Color import CreateColor
|
18
|
+
from .fbs.ColorMap import ColorMap
|
19
|
+
from .fbs.Data import Data
|
20
|
+
from .fbs.VideoExportFormat import VideoExportFormat
|
21
|
+
from .fbs.DictEntry import (DictEntryAddKey, DictEntryAddVal, DictEntryEnd,
|
22
|
+
DictEntryStart)
|
23
|
+
from .fbs.OpacityFunction import OpacityFunction
|
24
|
+
|
25
|
+
if sys.platform == 'win32':
|
26
|
+
MONOCHROME_BIN_PATH = Path(__file__).parent / 'data' / 'bin' / 'Monochrome.exe'
|
27
|
+
elif sys.platform == 'darwin':
|
28
|
+
MONOCHROME_BIN_PATH = Path(__file__).parent / 'data' / 'Monochrome.app'
|
29
|
+
else:
|
30
|
+
MONOCHROME_BIN_PATH = Path(__file__).parent / 'data' / 'bin' / 'Monochrome'
|
31
|
+
|
32
|
+
USE_TCP = sys.platform in ['win32', 'cygwin']
|
33
|
+
TCP_IP, TCP_PORT = '127.0.0.1', 4864
|
34
|
+
# OSX doesn't support abstract UNIX domain sockets
|
35
|
+
ABSTRACT_DOMAIN_SOCKET_SUPPORTED = sys.platform != 'darwin'
|
36
|
+
if sys.platform != 'win32':
|
37
|
+
SOCK_PATH = f'\0Monochrome{os.getuid()}' if ABSTRACT_DOMAIN_SOCKET_SUPPORTED else f'/tmp/Monochrome{os.getuid()}.s'
|
38
|
+
else:
|
39
|
+
SOCK_PATH = None
|
40
|
+
MAX_BUFFER_SIZE = 16352
|
41
|
+
|
42
|
+
|
43
|
+
def start_monochrome(speed: Optional[float] = None,
|
44
|
+
display_fps: Optional[int] = None,
|
45
|
+
scale: Optional[float] = None,
|
46
|
+
fliph: bool = False,
|
47
|
+
flipv: bool = False,
|
48
|
+
**kwargs):
|
49
|
+
"""
|
50
|
+
Start bundled Monochrome executable with the given settings.
|
51
|
+
"""
|
52
|
+
if sys.platform != 'darwin':
|
53
|
+
args = [str(MONOCHROME_BIN_PATH)]
|
54
|
+
else:
|
55
|
+
args = ['open', '-a', str(MONOCHROME_BIN_PATH), '--args']
|
56
|
+
if speed:
|
57
|
+
args.append('--speed')
|
58
|
+
args.append(str(speed))
|
59
|
+
if display_fps:
|
60
|
+
args.append('--display_fps')
|
61
|
+
args.append(str(display_fps))
|
62
|
+
if scale:
|
63
|
+
args.append('--scale')
|
64
|
+
args.append(str(scale))
|
65
|
+
if fliph:
|
66
|
+
args.append('--fliph')
|
67
|
+
if flipv:
|
68
|
+
args.append('--flipv')
|
69
|
+
for key, val in kwargs.items():
|
70
|
+
args.append(f'--{key}')
|
71
|
+
args.append(str(val))
|
72
|
+
subprocess.Popen(args, start_new_session=True)
|
73
|
+
|
74
|
+
|
75
|
+
def console_entrypoint():
|
76
|
+
if sys.platform != 'darwin':
|
77
|
+
args = [str(MONOCHROME_BIN_PATH)]
|
78
|
+
else:
|
79
|
+
args = ['open', '-a', str(MONOCHROME_BIN_PATH), '--args']
|
80
|
+
args.extend(sys.argv[1:])
|
81
|
+
subprocess.Popen(args).wait()
|
82
|
+
|
83
|
+
|
84
|
+
def _create_socket():
|
85
|
+
if USE_TCP:
|
86
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
87
|
+
s.connect((TCP_IP, TCP_PORT))
|
88
|
+
else:
|
89
|
+
if not ABSTRACT_DOMAIN_SOCKET_SUPPORTED and not Path(SOCK_PATH).exists():
|
90
|
+
raise ConnectionRefusedError("No socket found, please start Monochrome")
|
91
|
+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
92
|
+
s.connect(SOCK_PATH)
|
93
|
+
return s
|
94
|
+
|
95
|
+
|
96
|
+
def create_socket():
|
97
|
+
s = None
|
98
|
+
try:
|
99
|
+
s = _create_socket()
|
100
|
+
except ConnectionRefusedError:
|
101
|
+
if not USE_TCP and not ABSTRACT_DOMAIN_SOCKET_SUPPORTED:
|
102
|
+
Path(SOCK_PATH).unlink(missing_ok=True)
|
103
|
+
start_monochrome()
|
104
|
+
waiting = True
|
105
|
+
timeout = time.time() + 5
|
106
|
+
while waiting and time.time() < timeout:
|
107
|
+
try:
|
108
|
+
s = _create_socket()
|
109
|
+
waiting = False
|
110
|
+
except ConnectionRefusedError:
|
111
|
+
pass
|
112
|
+
if waiting:
|
113
|
+
raise ConnectionRefusedError("Could not connect to Monochrome")
|
114
|
+
return s
|
115
|
+
|
116
|
+
|
117
|
+
def get_color(builder, color):
|
118
|
+
if color is None:
|
119
|
+
return None
|
120
|
+
|
121
|
+
try:
|
122
|
+
import matplotlib.colors as mcolors
|
123
|
+
color = mcolors.to_rgba(color)
|
124
|
+
except ImportError:
|
125
|
+
if isinstance(color, str):
|
126
|
+
print("ERROR: unable to import matplotlib, please install it")
|
127
|
+
color = (0.0, 0.0, 0.0, 1.0)
|
128
|
+
return CreateColor(builder, color)
|
129
|
+
|
130
|
+
|
131
|
+
def build_root(builder, data_type, data):
|
132
|
+
Root.Start(builder)
|
133
|
+
Root.AddDataType(builder, data_type)
|
134
|
+
Root.AddData(builder, data)
|
135
|
+
root = Root.End(builder)
|
136
|
+
return root
|
137
|
+
|
138
|
+
|
139
|
+
def create_filepaths_msg(paths):
|
140
|
+
builder = flatbuffers.Builder(512)
|
141
|
+
paths_fb = [builder.CreateString(s) for s in paths]
|
142
|
+
Filepaths.StartFileVector(builder, len(paths_fb))
|
143
|
+
for p in paths_fb:
|
144
|
+
builder.PrependSOffsetTRelative(p)
|
145
|
+
vec = builder.EndVector()
|
146
|
+
Filepaths.Start(builder)
|
147
|
+
Filepaths.AddFile(builder, vec)
|
148
|
+
fp = Filepaths.End(builder)
|
149
|
+
root = build_root(builder, Data.Filepaths, fp)
|
150
|
+
builder.FinishSizePrefixed(root)
|
151
|
+
buf = builder.Output()
|
152
|
+
return buf
|
153
|
+
|
154
|
+
|
155
|
+
def create_pointsvideo_msg(points_py, name, parent_name=None, color=None, point_size=None):
|
156
|
+
builder = flatbuffers.Builder(1024)
|
157
|
+
name_fb = builder.CreateString(name)
|
158
|
+
parent_fb = builder.CreateString(parent_name) if parent_name else None
|
159
|
+
|
160
|
+
flat = []
|
161
|
+
indexes = []
|
162
|
+
for frame in points_py:
|
163
|
+
for point in frame:
|
164
|
+
flat.append(point[0])
|
165
|
+
flat.append(point[1])
|
166
|
+
indexes.append(len(flat))
|
167
|
+
flat = np.array(flat, dtype=np.float32)
|
168
|
+
indexs = np.array(indexes, dtype=np.uint32)
|
169
|
+
flat_fb = builder.CreateNumpyVector(flat)
|
170
|
+
indexes_fb = builder.CreateNumpyVector(indexs)
|
171
|
+
|
172
|
+
PointsVideo.Start(builder)
|
173
|
+
PointsVideo.AddName(builder, name_fb)
|
174
|
+
if parent_fb:
|
175
|
+
PointsVideo.AddParentName(builder, parent_fb)
|
176
|
+
if color:
|
177
|
+
PointsVideo.AddColor(builder, get_color(builder, color))
|
178
|
+
if point_size:
|
179
|
+
PointsVideo.AddPointSize(builder, point_size)
|
180
|
+
PointsVideo.AddPointsData(builder, flat_fb)
|
181
|
+
PointsVideo.AddTimeIdxs(builder, indexes_fb)
|
182
|
+
fp = PointsVideo.End(builder)
|
183
|
+
root = build_root(builder, Data.PointsVideo, fp)
|
184
|
+
builder.FinishSizePrefixed(root)
|
185
|
+
buf = builder.Output()
|
186
|
+
return buf
|
187
|
+
|
188
|
+
def create_array3meta_msg(type: ArrayDataType, name, shape, duration=0., fps=0., date="", comment="",
|
189
|
+
bitrange=BitRange.AUTODETECT, cmap=ColorMap.DEFAULT, parent_name=None, opacity=None,
|
190
|
+
metadata=None, vmin=None, vmax=None):
|
191
|
+
builder = flatbuffers.Builder(1024)
|
192
|
+
name_fb = builder.CreateString(name)
|
193
|
+
parent_fb = builder.CreateString(parent_name) if parent_name is not None else None
|
194
|
+
date_fb = builder.CreateString(date)
|
195
|
+
comment_fb = builder.CreateString(comment)
|
196
|
+
if metadata:
|
197
|
+
metadata = [(builder.CreateString(key), builder.CreateString(val)) for key, val in metadata.items()]
|
198
|
+
metaData_fbs = []
|
199
|
+
for key, val in metadata:
|
200
|
+
DictEntryStart(builder)
|
201
|
+
DictEntryAddKey(builder, key)
|
202
|
+
DictEntryAddVal(builder, val)
|
203
|
+
metaData_fbs.append(DictEntryEnd(builder))
|
204
|
+
Array3Meta.Array3MetaStartMetadataVector(builder, len(metadata))
|
205
|
+
for e in metaData_fbs:
|
206
|
+
builder.PrependUOffsetTRelative(e)
|
207
|
+
metadata = builder.EndVector()
|
208
|
+
Array3Meta.Start(builder)
|
209
|
+
Array3Meta.AddType(builder, type)
|
210
|
+
Array3Meta.AddNx(builder, shape[2])
|
211
|
+
Array3Meta.AddNy(builder, shape[1])
|
212
|
+
Array3Meta.AddNt(builder, shape[0])
|
213
|
+
Array3Meta.AddBitrange(builder, bitrange)
|
214
|
+
Array3Meta.AddCmap(builder, cmap)
|
215
|
+
if vmin is not None:
|
216
|
+
Array3Meta.AddVmin(builder, vmin)
|
217
|
+
if vmax is not None:
|
218
|
+
Array3Meta.AddVmax(builder, vmax)
|
219
|
+
if opacity is not None:
|
220
|
+
Array3Meta.AddOpacity(builder, opacity)
|
221
|
+
Array3Meta.AddName(builder, name_fb)
|
222
|
+
if parent_fb:
|
223
|
+
Array3Meta.AddParentName(builder, parent_fb)
|
224
|
+
Array3Meta.AddDuration(builder, duration)
|
225
|
+
Array3Meta.AddFps(builder, fps)
|
226
|
+
Array3Meta.AddDate(builder, date_fb)
|
227
|
+
Array3Meta.AddComment(builder, comment_fb)
|
228
|
+
if metadata:
|
229
|
+
Array3Meta.AddMetadata(builder, metadata)
|
230
|
+
d = Array3Meta.End(builder)
|
231
|
+
|
232
|
+
root = build_root(builder, Data.Array3Meta, d)
|
233
|
+
builder.FinishSizePrefixed(root)
|
234
|
+
buf = builder.Output()
|
235
|
+
return buf
|
236
|
+
|
237
|
+
|
238
|
+
def create_array3metaflow_msg(shape, parent_name=None, name="", color=None):
|
239
|
+
if parent_name is None:
|
240
|
+
parent_name = ""
|
241
|
+
builder = flatbuffers.Builder(1024)
|
242
|
+
name_fb = builder.CreateString(name)
|
243
|
+
parent_fb = builder.CreateString(parent_name)
|
244
|
+
Array3MetaFlow.Start(builder)
|
245
|
+
Array3MetaFlow.AddNx(builder, shape[2])
|
246
|
+
Array3MetaFlow.AddNy(builder, shape[1])
|
247
|
+
Array3MetaFlow.AddNt(builder, shape[0])
|
248
|
+
Array3MetaFlow.AddName(builder, name_fb)
|
249
|
+
Array3MetaFlow.AddParentName(builder, parent_fb)
|
250
|
+
if color:
|
251
|
+
Array3MetaFlow.AddColor(builder, get_color(builder, color))
|
252
|
+
d = Array3MetaFlow.End(builder)
|
253
|
+
|
254
|
+
root = build_root(builder, Data.Array3MetaFlow, d)
|
255
|
+
builder.FinishSizePrefixed(root)
|
256
|
+
buf = builder.Output()
|
257
|
+
return buf
|
258
|
+
|
259
|
+
|
260
|
+
def create_array3dataf_msg(array, idx=0):
|
261
|
+
builder = flatbuffers.Builder(65536)
|
262
|
+
data = builder.CreateNumpyVector(array)
|
263
|
+
Array3DataChunkf.Start(builder)
|
264
|
+
Array3DataChunkf.AddStartidx(builder, idx)
|
265
|
+
Array3DataChunkf.AddData(builder, data)
|
266
|
+
d = Array3DataChunkf.End(builder)
|
267
|
+
|
268
|
+
root = build_root(builder, Data.Array3DataChunkf, d)
|
269
|
+
builder.FinishSizePrefixed(root)
|
270
|
+
buf = builder.Output()
|
271
|
+
return buf
|
272
|
+
|
273
|
+
|
274
|
+
def create_array3datau8_msg(array, idx=0):
|
275
|
+
builder = flatbuffers.Builder(65536)
|
276
|
+
data = builder.CreateNumpyVector(array)
|
277
|
+
Array3DataChunku8.Start(builder)
|
278
|
+
Array3DataChunku8.AddStartidx(builder, idx)
|
279
|
+
Array3DataChunku8.AddData(builder, data)
|
280
|
+
d = Array3DataChunku8.End(builder)
|
281
|
+
|
282
|
+
root = build_root(builder, Data.Array3DataChunku8, d)
|
283
|
+
builder.FinishSizePrefixed(root)
|
284
|
+
buf = builder.Output()
|
285
|
+
return buf
|
286
|
+
|
287
|
+
|
288
|
+
def create_array3datau16_msg(array, idx=0):
|
289
|
+
builder = flatbuffers.Builder(65536)
|
290
|
+
data = builder.CreateNumpyVector(array)
|
291
|
+
Array3DataChunku16.Start(builder)
|
292
|
+
Array3DataChunku16.AddStartidx(builder, idx)
|
293
|
+
Array3DataChunku16.AddData(builder, data)
|
294
|
+
d = Array3DataChunku16.End(builder)
|
295
|
+
|
296
|
+
root = build_root(builder, Data.Array3DataChunku16, d)
|
297
|
+
builder.FinishSizePrefixed(root)
|
298
|
+
buf = builder.Output()
|
299
|
+
return buf
|
300
|
+
|
301
|
+
|
302
|
+
def show_file(filepath: Union[Text, Path]):
|
303
|
+
"""
|
304
|
+
Load a file in Monochrome.
|
305
|
+
|
306
|
+
Parameters
|
307
|
+
----------
|
308
|
+
filepath : str or Path
|
309
|
+
Path to the file to be loaded
|
310
|
+
"""
|
311
|
+
show_files([filepath])
|
312
|
+
|
313
|
+
|
314
|
+
def show_files(paths: List[Union[Text, Path]]):
|
315
|
+
"""
|
316
|
+
Load multiple files in Monochrome.
|
317
|
+
|
318
|
+
Parameters
|
319
|
+
----------
|
320
|
+
paths : List[str or Path]
|
321
|
+
List of paths to the files to be loaded
|
322
|
+
"""
|
323
|
+
paths = [Path(path) for path in paths]
|
324
|
+
if not all([path.exists() for path in paths]):
|
325
|
+
raise FileNotFoundError(f"One of more files of {paths} do not exist")
|
326
|
+
paths = [str(path.absolute()) for path in paths]
|
327
|
+
s = create_socket()
|
328
|
+
buf = create_filepaths_msg(paths)
|
329
|
+
s.sendall(buf)
|
330
|
+
|
331
|
+
|
332
|
+
def show_points(points, name: Text = "", parent: Optional[Text] = None, color=None,
|
333
|
+
point_size: Optional[float] = None):
|
334
|
+
"""
|
335
|
+
Show a list of points for each frame in Monochrome.
|
336
|
+
|
337
|
+
Parameters
|
338
|
+
----------
|
339
|
+
points : List[List[Tuple[float, float]]]
|
340
|
+
A list of list of points (x, y). The outer list elements are the frames, the inner list is the list of points for a specific frame.
|
341
|
+
name : str
|
342
|
+
Optional description
|
343
|
+
parent : str
|
344
|
+
Name of the video onto which the points will be displayed. If none is given the last loaded video will be used.
|
345
|
+
color : str or tuple
|
346
|
+
Matplotlib color (either string like 'black' or rgba tuple)
|
347
|
+
point_size : float
|
348
|
+
Size of points in image pixels
|
349
|
+
"""
|
350
|
+
|
351
|
+
name = str(name)
|
352
|
+
s = create_socket()
|
353
|
+
buf = create_pointsvideo_msg(points, name, parent, color, point_size)
|
354
|
+
s.sendall(buf)
|
355
|
+
|
356
|
+
|
357
|
+
def show_image(array: np.ndarray,
|
358
|
+
name: Text = "",
|
359
|
+
cmap: Union[ColorMap, Text] = ColorMap.DEFAULT,
|
360
|
+
vmin: Optional[float] = None,
|
361
|
+
vmax: Optional[float] = None,
|
362
|
+
bitrange: Union[BitRange, Text] = BitRange.AUTODETECT,
|
363
|
+
parent: Optional[Text] = None,
|
364
|
+
opacity: Optional[OpacityFunction] = None,
|
365
|
+
comment: Text = "",
|
366
|
+
metadata: Optional[Dict] = None):
|
367
|
+
"""
|
368
|
+
Show an image in Monochrome.
|
369
|
+
|
370
|
+
Alias for :func:`show_video`.
|
371
|
+
"""
|
372
|
+
return show_video(array, name=name, cmap=cmap, vmin=vmin, vmax=vmax, bitrange=bitrange, parent=parent, opacity=opacity, comment=comment, metadata=metadata)
|
373
|
+
|
374
|
+
|
375
|
+
def show_video(array: np.ndarray,
|
376
|
+
name: Text = "",
|
377
|
+
cmap: Union[ColorMap, Text] = ColorMap.DEFAULT,
|
378
|
+
vmin: Optional[float] = None,
|
379
|
+
vmax: Optional[float] = None,
|
380
|
+
bitrange: Union[BitRange, Text] = BitRange.AUTODETECT,
|
381
|
+
parent: Optional[Text] = None,
|
382
|
+
opacity: Optional[OpacityFunction] = None,
|
383
|
+
comment: Text = "",
|
384
|
+
metadata: Optional[Dict] = None):
|
385
|
+
"""
|
386
|
+
Play a video or open a image in Monochrome.
|
387
|
+
Arrays of dtype np.float, np.uint8, and np.uint16 are natively supported by Monochrome.
|
388
|
+
Arrays with other dtypes will be converted to np.float
|
389
|
+
|
390
|
+
Parameters
|
391
|
+
----------
|
392
|
+
array : np.ndarray
|
393
|
+
The video to be displayed. The array should have the shape (T, H, W) or (H, W).
|
394
|
+
name : str
|
395
|
+
Name of the video
|
396
|
+
cmap : str or ColorMap
|
397
|
+
Colormap for the video. One of 'default' (autodetect), 'gray', 'hsv', 'blackbody', 'viridis', 'PRGn', 'PRGn_pos', 'PRGn_neg', 'RdBu'.
|
398
|
+
vmin : float
|
399
|
+
Minimum value for the colormap. Default is None.
|
400
|
+
vmax : float
|
401
|
+
Maximum value for the colormap. Default is None.
|
402
|
+
bitrange : str or BitRange
|
403
|
+
Valuerange for the video. One of 'autodetect', 'MinMax' 'uint8', 'uint10', 'uint12', 'uint16', 'float' (for [0,1]), 'diff' (for [-1, 1]), 'phase' (for [0, 2*pi]), or 'phase_diff (for [-pi, pi])'. Default is 'autodetect'.
|
404
|
+
parent : str
|
405
|
+
Name of the parent video
|
406
|
+
opacity : OpacityFunction
|
407
|
+
Opacity function for alpha blending if video is a layer. One of 'linear', 'linear_r', 'centered', 1.0, 0.75, 0.5, 0.25, or 0.0. Default is `opacity=1.0`.
|
408
|
+
comment : str
|
409
|
+
Comment to be displayed
|
410
|
+
metadata : dict
|
411
|
+
Additional metadata to be displayed
|
412
|
+
"""
|
413
|
+
|
414
|
+
array = np.squeeze(array)
|
415
|
+
if array.ndim == 2:
|
416
|
+
# assume that it is a 2D image
|
417
|
+
array = np.expand_dims(array, 0)
|
418
|
+
elif array.ndim != 3:
|
419
|
+
raise ValueError("array is not two- or three-dimensional")
|
420
|
+
|
421
|
+
if array.dtype == np.float32:
|
422
|
+
dtype = ArrayDataType.FLOAT
|
423
|
+
elif array.dtype == np.uint8:
|
424
|
+
dtype = ArrayDataType.UINT8
|
425
|
+
elif array.dtype == np.uint16:
|
426
|
+
dtype = ArrayDataType.UINT16
|
427
|
+
else:
|
428
|
+
if np.iscomplexobj(array):
|
429
|
+
raise ValueError("Complex arrays not supported")
|
430
|
+
else:
|
431
|
+
array = array.astype(np.float32)
|
432
|
+
dtype = ArrayDataType.FLOAT
|
433
|
+
|
434
|
+
name = str(name)
|
435
|
+
|
436
|
+
if isinstance(cmap, str):
|
437
|
+
cmap = getattr(ColorMap, cmap.upper())
|
438
|
+
if isinstance(bitrange, str):
|
439
|
+
bitrange = getattr(BitRange, bitrange.upper())
|
440
|
+
if isinstance(opacity, str):
|
441
|
+
try:
|
442
|
+
opacity = float(opacity)
|
443
|
+
except ValueError:
|
444
|
+
pass
|
445
|
+
if isinstance(opacity, (int, float)):
|
446
|
+
if opacity == 1:
|
447
|
+
opacity = OpacityFunction.FIXED_100
|
448
|
+
elif opacity == 0.75:
|
449
|
+
opacity = OpacityFunction.FIXED_75
|
450
|
+
elif opacity == 0.5:
|
451
|
+
opacity = OpacityFunction.FIXED_50
|
452
|
+
elif opacity == 0.25:
|
453
|
+
opacity = OpacityFunction.FIXED_25
|
454
|
+
elif opacity == 0:
|
455
|
+
opacity = OpacityFunction.FIXED_0
|
456
|
+
else:
|
457
|
+
raise ValueError("Invalid opacity value")
|
458
|
+
if isinstance(opacity, str):
|
459
|
+
opacity = getattr(OpacityFunction, opacity.upper())
|
460
|
+
|
461
|
+
s = create_socket()
|
462
|
+
buf = create_array3meta_msg(dtype, name, array.shape, comment=comment, bitrange=bitrange, cmap=cmap,
|
463
|
+
parent_name=parent, opacity=opacity, metadata=metadata, vmin=vmin, vmax=vmax)
|
464
|
+
s.sendall(buf)
|
465
|
+
|
466
|
+
flat = array.flatten()
|
467
|
+
length = flat.size
|
468
|
+
max_size = MAX_BUFFER_SIZE
|
469
|
+
for idx in range(0, length, max_size):
|
470
|
+
end = length if idx + max_size > length else idx + max_size
|
471
|
+
if array.dtype == np.float32:
|
472
|
+
buf = create_array3dataf_msg(flat[idx:end], idx)
|
473
|
+
elif array.dtype == np.uint8:
|
474
|
+
buf = create_array3datau8_msg(flat[idx:end], idx)
|
475
|
+
elif array.dtype == np.uint16:
|
476
|
+
buf = create_array3datau16_msg(flat[idx:end], idx)
|
477
|
+
else:
|
478
|
+
raise NotImplementedError("Unkown dtype")
|
479
|
+
s.sendall(buf)
|
480
|
+
|
481
|
+
|
482
|
+
def show_layer(array: np.ndarray, name: Text = "", parent: Optional[Text] = None, opacity: Optional[OpacityFunction] = None, **kwargs):
|
483
|
+
"""
|
484
|
+
Add a layer to the parent video in Monochrome.
|
485
|
+
|
486
|
+
Parameters
|
487
|
+
----------
|
488
|
+
array : np.ndarray
|
489
|
+
The layer to be displayed. The array should have the shape (T, H, W) or (H, W).
|
490
|
+
name : str
|
491
|
+
Name of the layer
|
492
|
+
parent : str
|
493
|
+
Name of the parent video, if None the last loaded video will be used
|
494
|
+
opacity : OpacityFunction
|
495
|
+
Opacity function for alpha blending. One of 'linear', 'linear_r', 'centered', 1.0, 0.75, 0.5, 0.25, or 0.0. Default is `opacity=1.0`.
|
496
|
+
kwargs : dict
|
497
|
+
Additional arguments to be passed to :func:`show_video`
|
498
|
+
"""
|
499
|
+
if parent is None:
|
500
|
+
parent = ""
|
501
|
+
show_video(array, name=name, parent=parent, opacity=opacity, **kwargs)
|
502
|
+
|
503
|
+
|
504
|
+
def show_flow(flow_uv: np.ndarray, name: Text = "", parent: Optional[Text] = None, color=None):
|
505
|
+
"""
|
506
|
+
Visualize optical flow in Monochrome.
|
507
|
+
|
508
|
+
Parameters
|
509
|
+
----------
|
510
|
+
flow_uv : np.ndarray
|
511
|
+
Optical flow field of shape (T, H, W, 2)
|
512
|
+
name : str
|
513
|
+
Name of the flow
|
514
|
+
parent : str
|
515
|
+
Name of the parent video, if None the last loaded video will be used
|
516
|
+
color : str or tuple
|
517
|
+
Matplotlib color (either string like 'black' or rgba tuple)
|
518
|
+
"""
|
519
|
+
if flow_uv.ndim != 4:
|
520
|
+
raise ValueError("array is not four-dimensional")
|
521
|
+
if flow_uv.dtype != np.float32:
|
522
|
+
raise ValueError("array is not floating type")
|
523
|
+
if flow_uv.shape[3] != 2:
|
524
|
+
raise ValueError("flow should be of shape [T, H, W, 2]")
|
525
|
+
name = str(name)
|
526
|
+
|
527
|
+
s = create_socket()
|
528
|
+
shape = (flow_uv.shape[0] * 2, flow_uv.shape[1], flow_uv.shape[2])
|
529
|
+
buf = create_array3metaflow_msg(shape, parent, name, color)
|
530
|
+
s.sendall(buf)
|
531
|
+
|
532
|
+
flat = flow_uv.flatten()
|
533
|
+
length = flat.size
|
534
|
+
max_size = MAX_BUFFER_SIZE
|
535
|
+
for idx in range(0, length, max_size):
|
536
|
+
end = length if idx + max_size > length else idx + max_size
|
537
|
+
buf = create_array3dataf_msg(flat[idx:end], idx)
|
538
|
+
s.sendall(buf)
|
539
|
+
|
540
|
+
|
541
|
+
def show(array_or_path: Union[str, Path, np.ndarray], *args, **kwargs):
|
542
|
+
"""Autodetect the type of the input and show it in Monochrome."""
|
543
|
+
if isinstance(array_or_path, np.ndarray):
|
544
|
+
if array_or_path.ndim == 4 and array_or_path.shape[3] == 2:
|
545
|
+
return show_flow(array_or_path, *args, **kwargs)
|
546
|
+
else:
|
547
|
+
return show_video(array_or_path, *args, **kwargs)
|
548
|
+
elif isinstance(array_or_path, str) or isinstance(array_or_path, Path):
|
549
|
+
return show_file(array_or_path)
|
550
|
+
else:
|
551
|
+
raise ValueError("array_or_path has to be numpy array or string")
|
552
|
+
|
553
|
+
def export_video(filepath, name="", fps=30, t_start=0, t_end=-1, description="", close_after_completion=False):
|
554
|
+
"""Export a video displayed in Monochrome to a .mp4 file.
|
555
|
+
|
556
|
+
NOTE: Monochrome exports the video as rendered in the window, i.e. the video will have the same resolution as
|
557
|
+
the video window and all the layers/points/... will be merged into a single video.
|
558
|
+
|
559
|
+
Parameters
|
560
|
+
----------
|
561
|
+
filepath : str
|
562
|
+
Path to the output .mp4 file
|
563
|
+
name : str
|
564
|
+
Name of the video to be exported
|
565
|
+
fps : int
|
566
|
+
Frames per second of the output video
|
567
|
+
t_start : int
|
568
|
+
Start frame of the output video
|
569
|
+
t_end : int
|
570
|
+
End frame of the output video, -1 for the last frame
|
571
|
+
description : str
|
572
|
+
Description of the video to embed in the .mp4 file
|
573
|
+
close_after_completion : bool
|
574
|
+
Close the video in Monochrome after the export is completed
|
575
|
+
"""
|
576
|
+
s = create_socket()
|
577
|
+
builder = flatbuffers.Builder(512)
|
578
|
+
name_fb = builder.CreateString(name)
|
579
|
+
filepath_fb = builder.CreateString(str(Path(filepath).absolute()))
|
580
|
+
description_fb = builder.CreateString(description)
|
581
|
+
|
582
|
+
VideoExport.Start(builder)
|
583
|
+
VideoExport.AddRecording(builder, name_fb)
|
584
|
+
VideoExport.AddFilepath(builder, filepath_fb)
|
585
|
+
VideoExport.AddDescription(builder, description_fb)
|
586
|
+
VideoExport.AddFormat(builder, VideoExportFormat.FFMPEG)
|
587
|
+
VideoExport.AddFps(builder, fps)
|
588
|
+
VideoExport.AddTStart(builder, t_start)
|
589
|
+
VideoExport.AddTEnd(builder, t_end)
|
590
|
+
VideoExport.AddCloseAfterCompletion(builder, close_after_completion)
|
591
|
+
fp = VideoExport.End(builder)
|
592
|
+
root = build_root(builder, Data.VideoExport, fp)
|
593
|
+
builder.FinishSizePrefixed(root)
|
594
|
+
buf = builder.Output()
|
595
|
+
s.sendall(buf)
|
596
|
+
|
597
|
+
def close_video(name=""):
|
598
|
+
"""Close a video in Monochrome.
|
599
|
+
|
600
|
+
Parameters
|
601
|
+
----------
|
602
|
+
name : str
|
603
|
+
Name of the video to be closed. If empty, the last loaded video will be closed.
|
604
|
+
"""
|
605
|
+
s = create_socket()
|
606
|
+
builder = flatbuffers.Builder(512)
|
607
|
+
name_fb = builder.CreateString(name)
|
608
|
+
|
609
|
+
CloseVideo.Start(builder)
|
610
|
+
CloseVideo.AddName(builder, name_fb)
|
611
|
+
fp = CloseVideo.End(builder)
|
612
|
+
root = build_root(builder, Data.CloseVideo, fp)
|
613
|
+
builder.FinishSizePrefixed(root)
|
614
|
+
buf = builder.Output()
|
615
|
+
s.sendall(buf)
|
616
|
+
|
617
|
+
def quit():
|
618
|
+
"""Quit Monochrome, terminating the process."""
|
619
|
+
s = create_socket()
|
620
|
+
builder = flatbuffers.Builder(512)
|
621
|
+
root = build_root(builder, Data.Quit, 0)
|
622
|
+
builder.FinishSizePrefixed(root)
|
623
|
+
buf = builder.Output()
|
624
|
+
s.sendall(buf)
|