monochrome 2025.1.17__py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Sign up to get free protection for your applications and to get access to all the features.
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)