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