|
1 | 1 | import io |
2 | | -import shutil |
3 | | -import tempfile |
4 | | -import time |
5 | 2 |
|
6 | 3 | import imageio |
7 | 4 | import matplotlib.pyplot as plt |
8 | 5 | import numpy as np |
9 | | -from matplotlib.backends.backend_agg import FigureCanvasAgg |
10 | | -from skimage.transform import resize |
11 | | - |
12 | | -from prose import Block, viz |
13 | | -from prose.visualization import corner_text |
14 | | - |
15 | | -__all__ = ["VideoPlot"] |
16 | | - |
17 | | - |
18 | | -def im_to_255(image, factor=0.25): |
19 | | - if factor != 1: |
20 | | - return ( |
21 | | - resize( |
22 | | - image.astype(float), |
23 | | - (np.array(np.shape(image)) * factor).astype(int), |
24 | | - anti_aliasing=False, |
25 | | - ) |
26 | | - * 255 |
27 | | - ).astype("uint8") |
28 | | - else: |
29 | | - data = image.copy().astype(float) |
30 | | - data = data / np.max(data) |
31 | | - data = data * 255 |
32 | | - return data.astype("uint8") |
33 | | - |
34 | | - |
35 | | -class _Video(Block): |
36 | | - """Base block to build a video""" |
37 | | - |
38 | | - def __init__(self, destination, fps=10, **kwargs): |
39 | | - super().__init__(**kwargs) |
40 | | - self.destination = destination |
41 | | - self.images = [] |
42 | | - self.fps = fps |
43 | | - self.checked_writer = False |
44 | 6 |
|
45 | | - def run(self, image): |
46 | | - if not self.checked_writer: |
47 | | - _ = imageio.get_writer(self.destination, mode="I") |
48 | | - self.checked_writer = True |
| 7 | +from prose.core.block import Block |
| 8 | +from prose.utils import z_scale |
49 | 9 |
|
50 | | - def terminate(self): |
51 | | - imageio.mimsave(self.destination, self.images, fps=self.fps) |
| 10 | +__all__ = ["VideoPlot", "Video"] |
52 | 11 |
|
53 | | - @property |
54 | | - def citations(self): |
55 | | - return super().citations + ["imageio"] |
56 | 12 |
|
| 13 | +def im_to_255(image): |
| 14 | + data = image.copy().astype(float) |
| 15 | + data = data / np.max(data) |
| 16 | + data = data * 255 |
| 17 | + return data.astype("uint8") |
57 | 18 |
|
58 | | -class RawVideo(_Video): |
| 19 | + |
| 20 | +class Video(Block): |
59 | 21 | def __init__( |
60 | | - self, destination, attribute="data", fps=10, function=None, scale=1, **kwargs |
| 22 | + self, |
| 23 | + destination, |
| 24 | + fps=10, |
| 25 | + compression=None, |
| 26 | + data_function=None, |
| 27 | + width=None, |
| 28 | + contrast=0.1, |
| 29 | + name=None, |
61 | 30 | ): |
62 | | - super().__init__(destination, fps=fps, **kwargs) |
63 | | - if function is None: |
| 31 | + """ |
| 32 | + A block to create a video from images data (using ffmpeg). |
64 | 33 |
|
65 | | - def _donothing(data): |
66 | | - return data |
| 34 | + Parameters |
| 35 | + ---------- |
| 36 | + destination : str |
| 37 | + The path to save the resulting video. |
| 38 | + fps : int, optional |
| 39 | + The frames per second of the resulting video. Default is 10. |
| 40 | + compression : int, optional |
| 41 | + The compression rate of the resulting video (-cr value of fmmpeg). |
| 42 | + Default is None. |
| 43 | + data_function : callable, optional |
| 44 | + A function to apply to each image data before adding it to the video. |
| 45 | + If none, a z scale is applied to the data with a contrast given by |
| 46 | + :code:`contrast`.Default is None. |
| 47 | + width : int, optional |
| 48 | + The width in pixels of the resulting video. |
| 49 | + Default is None (i.e. original image size). |
| 50 | + contrast : float, optional |
| 51 | + The contrast of the resulting video. Default is 0.1. |
| 52 | + Either :code:`contrast` or :code:`data_function` must be provided. |
| 53 | + name : str, optional |
| 54 | + The name of the block. Default is None. |
| 55 | +
|
| 56 | + Attributes |
| 57 | + ---------- |
| 58 | + citations : list of str |
| 59 | + The citations for the block. |
67 | 60 |
|
68 | | - function = _donothing |
| 61 | + Methods |
| 62 | + ------- |
| 63 | + run(image) |
| 64 | + Adds an image to the video. |
| 65 | + terminate() |
| 66 | + Closes the video writer. |
69 | 67 |
|
70 | | - self.function = function |
71 | | - self.scale = scale |
72 | | - self.attribute = attribute |
| 68 | + """ |
| 69 | + super().__init__(name=name) |
| 70 | + if data_function is None: |
| 71 | + |
| 72 | + def data_function(data): |
| 73 | + new_data = data.copy() |
| 74 | + new_data = z_scale(new_data, c=contrast) |
| 75 | + return new_data |
| 76 | + |
| 77 | + output = [] |
| 78 | + if compression is not None: |
| 79 | + output += ["-crf", f"{compression}"] |
| 80 | + if width is not None: |
| 81 | + output += ["-vf", f"scale={width}:-1"] |
| 82 | + self.writer = imageio.get_writer( |
| 83 | + destination, |
| 84 | + mode="I", |
| 85 | + fps=fps, |
| 86 | + output_params=output if len(output) > 0 else None, |
| 87 | + ) |
| 88 | + self.function = data_function |
73 | 89 |
|
74 | 90 | def run(self, image): |
75 | | - super().run(image) |
76 | | - data = self.function(image.__dict__[self.attribute]) |
77 | | - self.images.append(im_to_255(data, factor=self.scale)) |
| 91 | + data = self.function(image.data) |
| 92 | + self.writer.append_data(im_to_255(data)) |
78 | 93 |
|
| 94 | + def terminate(self): |
| 95 | + self.writer.close() |
| 96 | + |
| 97 | + @property |
| 98 | + def citations(self): |
| 99 | + return super().citations + ["imageio"] |
79 | 100 |
|
80 | | -class VideoPlot(_Video): |
81 | | - def __init__(self, plot_function, destination, fps=10, name=None): |
82 | | - """Make a video out of a plotting function |
| 101 | + |
| 102 | +class VideoPlot(Video): |
| 103 | + def __init__( |
| 104 | + self, |
| 105 | + plot_function, |
| 106 | + destination, |
| 107 | + fps=10, |
| 108 | + compression=None, |
| 109 | + width=None, |
| 110 | + name=None, |
| 111 | + ): |
| 112 | + """ |
| 113 | + A block to create a video from a matploltib plot (using ffmpeg). |
83 | 114 |
|
84 | 115 | Parameters |
85 | 116 | ---------- |
86 | | - plot_function : function |
87 | | - a plotting function taking an :py:class:`prose.Image` as input |
88 | | - destination : str or Path |
89 | | - destination of the video, including extension |
| 117 | + plot_function : callable |
| 118 | + A function that takes an image as input and produce a plot. |
| 119 | + destination : str |
| 120 | + The path to save the resulting video. |
90 | 121 | fps : int, optional |
91 | | - frame per seconds, by default 10 |
92 | | - antialias : bool, optional |
93 | | - whether pyplot antialias should be used, by default False |
| 122 | + The frames per second of the resulting video. Default is 10. |
| 123 | + compression : int, optional |
| 124 | + The compression rate of the resulting video (-cr value of fmmpeg). |
| 125 | + Default is None. |
| 126 | + width : int, optional |
| 127 | + The width in pixels of the resulting video. |
| 128 | + Default is None (i.e. original image size). |
| 129 | + name : str, optional |
| 130 | + The name of the block. Default is None. |
| 131 | +
|
| 132 | + Attributes |
| 133 | + ---------- |
| 134 | + citations : list of str |
| 135 | + The citations for the block. |
| 136 | +
|
| 137 | + Methods |
| 138 | + ------- |
| 139 | + run(image) |
| 140 | + Adds a plot to the video. |
| 141 | + terminate() |
| 142 | + Closes the video writer. |
| 143 | +
|
94 | 144 | """ |
95 | | - super().__init__(destination, fps=fps, name=name) |
| 145 | + super().__init__( |
| 146 | + destination, |
| 147 | + fps=fps, |
| 148 | + compression=compression, |
| 149 | + width=width, |
| 150 | + name=name, |
| 151 | + ) |
96 | 152 | self.plot_function = plot_function |
97 | | - self.destination = destination |
98 | | - self._temp = tempfile.mkdtemp() |
99 | | - self._images = [] |
100 | 153 |
|
101 | 154 | def run(self, image): |
102 | 155 | self.plot_function(image) |
103 | 156 | buf = io.BytesIO() |
104 | 157 | plt.savefig(buf) |
105 | | - self.images.append(imageio.imread(buf)) |
| 158 | + self.writer.append_data(imageio.imread(buf)) |
106 | 159 | plt.close() |
107 | 160 |
|
108 | | - def terminate(self): |
109 | | - super().terminate() |
110 | | - shutil.rmtree(self._temp) |
111 | | - |
112 | | - |
113 | | -class LivePlot(Block): |
114 | | - def __init__(self, plot_function=None, sleep=0.0, size=None, **kwargs): |
115 | | - super().__init__(**kwargs) |
116 | | - if plot_function is None: |
117 | | - plot_function = lambda im: viz.show_stars( |
118 | | - im.data, |
119 | | - im.stars_coords if hasattr(im, "stars_coords") else None, |
120 | | - size=size, |
121 | | - ) |
122 | | - |
123 | | - self.plot_function = plot_function |
124 | | - self.sleep = sleep |
125 | | - self.display = None |
126 | | - self.size = size |
127 | | - self.figure_added = False |
128 | | - |
129 | | - def run(self, image): |
130 | | - if not self.figure_added: |
131 | | - from IPython import display as disp |
132 | | - |
133 | | - self.display = disp |
134 | | - if isinstance(self.size, tuple): |
135 | | - plt.figure(figsize=self.size) |
136 | | - self.figure_added = True |
137 | | - |
138 | | - self.plot_function(image) |
139 | | - self.display.clear_output(wait=True) |
140 | | - self.display.display(plt.gcf()) |
141 | | - time.sleep(self.sleep) |
142 | | - plt.cla() |
143 | | - |
144 | 161 | def terminate(self): |
145 | 162 | plt.close() |
| 163 | + super().terminate() |
0 commit comments