Coverage for /home/ubuntu/cv-depot/python/cv_depot/core/viewer.py: 97%
87 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 20:26 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 20:26 +0000
1from typing import Optional # noqa F401
2from typing import TYPE_CHECKING
3if TYPE_CHECKING:
4 from cv_depot.core.image import Image # noqa: F401
6import ipywidgets as ipy
7import IPython.display as ipython
8import re
10import cv_depot.api as cvd
11# ------------------------------------------------------------------------------
14class ImageViewer:
15 def __init__(self, image, size=81, gamma=1, premultiply=False):
16 # type: (Image, int, float, bool) -> None
17 '''
18 Constructs an ImageViewer widget, used for displaying Image instances.
20 Args:
21 image (Image): Image instance.
22 size (int, optional): Image size in percentage. Default: 81.
23 gamma (float, optional): Initial gamma value. Default: 1.
24 premultiply (bool, optional): Premultiply image by last channel.
25 Default: False.
27 Raises:
28 EnforceError: If image is not an Image instance.
29 '''
30 # image
31 self._image = image
32 self.size = size
33 self.gamma = gamma
34 self.premult = premultiply
36 # layer
37 self.layer = self._get_layer_options()[0]
38 self.layer_selector = ipy.Dropdown(
39 description='layer',
40 label=self.layer,
41 options=self._get_layer_options(),
42 )
43 self.layer_selector.observe(self._handle_layer_event, names='value')
45 # channel
46 self.channel = self._get_channel_options()[0]
47 self.channel_selector = ipy.Dropdown(
48 description='channel',
49 label=self.channel,
50 options=self._get_channel_options(),
51 )
52 self.channel_selector.observe(self._handle_channel_event, names='value')
54 # size slider
55 self.size_slider = ipy.IntSlider(
56 value=self.size, min=0, max=100, step=1, description='size'
57 )
58 self.size_slider.observe(self._handle_resize_event, names='value')
60 # gamma slider
61 self.gamma_slider = ipy.FloatSlider(
62 value=self.gamma, min=0, max=10, step=0.01, description='gamma'
63 )
64 self.gamma_slider.observe(self._handle_gamma_event, names='value')
66 self.premult_checkbox = ipy.Checkbox(
67 value=False, description='premultiply',
68 )
69 self.premult_checkbox.observe(self._handle_premult_event, names='value')
71 # viewer
72 self.viewer = ipy.Image(value=self._get_png(), width=f'{self.size}%')
73 self.info = ipy.HTML(value=self._get_info())
75 # widgets
76 sidebar = ipy.VBox(
77 [
78 self.info,
79 self.layer_selector,
80 self.channel_selector,
81 self.size_slider,
82 self.gamma_slider,
83 self.premult_checkbox,
84 ],
85 layout=ipy.Layout(flex_flow='column', min_width='310px')
86 )
87 self._widgets = [
88 ipy.HBox(
89 [sidebar, self.viewer],
90 layout=ipy.Layout(flex_flow='row')
91 )
92 ]
94 def show(self):
95 # type: () -> None
96 '''
97 Call ipython.display with widgets.
98 '''
99 ipython.display(*self._widgets)
101 def _get_layer_options(self):
102 # type: () -> list[str]
103 '''
104 Get list of channel layers.
106 Returns:
107 list[str]: List of channel layers.
108 '''
109 return self._image.channel_layers
111 def _get_channel_options(self):
112 # type: () -> list
113 '''
114 Get list of channel options.
116 Returns:
117 list: List of channel options.
118 '''
119 chan = self._image[:, :, self.layer].channels
120 return ['all'] + chan
122 def _get_info(self):
123 # type: () -> str
124 '''
125 Creates a HTML representation of image info.
127 Returns:
128 str: HTML.
129 '''
130 info = self._image.info
131 desc = ''
132 for key in ['width', 'height', 'num_channels', 'bit_depth']:
133 val = info[key]
134 key = re.sub(' ', ' ', f'{key:>14}')
135 desc += f'<span style="color: #7EC4CF;">{key}: </span>'
136 desc += f'<span>{val}</span><br>'
137 elem = '<p style="font-family: monospace; font-size: 13px; '
138 elem += f'background: #242424;">{desc}</p>'
139 return elem
141 def _get_png(self):
142 # type: () -> Optional[bytes]
143 '''
144 Creates a PNG representation of image data.
146 Returns:
147 str: PNG.
148 '''
149 chan = self.channel
150 if chan == 'all':
151 chan = self.layer
152 chans = self._image[:, :, chan].channels
153 if not self.premult and len(chans) > 3:
154 chans = chans[:3]
155 return cvd.ops.filter.gamma(self._image[:, :, chans], self.gamma)._repr_png()
157 def _handle_layer_event(self, event):
158 # type: (dict) -> None
159 '''
160 Handles layer selector events.
162 Args:
163 event (dict): Event.
164 '''
165 if event['type'] == 'change':
166 # layer
167 self.layer = event['new']
168 self.layer_selector.value = self.layer
170 # channel
171 options = self._get_channel_options()
172 chan = options[0]
173 self.channel = chan
174 self.channel_selector.options = options
175 self.channel_selector.label = chan
176 self.channel_selector.value = chan
178 # viewer
179 self.viewer.value = self._get_png()
181 def _handle_channel_event(self, event):
182 # type: (dict) -> None
183 '''
184 Handles channel selector events.
186 Args:
187 event (dict): Event.
188 '''
189 if event['type'] == 'change':
190 self.channel = event['new']
191 self.channel_selector.value = self.channel
192 self.viewer.value = self._get_png()
194 def _handle_resize_event(self, event):
195 # type: (dict) -> None
196 '''
197 Handles image resize events.
199 Args:
200 event (dict): Event.
201 '''
202 if event['type'] == 'change':
203 self.size = event['new']
204 self.size_slider.value = self.size
205 self.viewer.width = f'{self.size}%'
207 def _handle_gamma_event(self, event):
208 # type: (dict) -> None
209 '''
210 Handles image resize events.
212 Args:
213 event (dict): Event.
214 '''
215 if event['type'] == 'change':
216 self.gamma = event['new']
217 self.gamma_slider.value = self.gamma
218 self.viewer.value = self._get_png()
220 def _handle_premult_event(self, event):
221 # type: (dict) -> None
222 '''
223 Handles premultiply events.
225 Args:
226 event (dict): Event.
227 '''
228 if event['type'] == 'change':
229 self.premult = event['new']
230 self.premult_checkbox.value = self.premult
231 self.viewer.value = self._get_png()