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

1from typing import Optional # noqa F401 

2from typing import TYPE_CHECKING 

3if TYPE_CHECKING: 

4 from cv_depot.core.image import Image # noqa: F401 

5 

6import ipywidgets as ipy 

7import IPython.display as ipython 

8import re 

9 

10import cv_depot.api as cvd 

11# ------------------------------------------------------------------------------ 

12 

13 

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. 

19 

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. 

26 

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 

35 

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') 

44 

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') 

53 

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') 

59 

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') 

65 

66 self.premult_checkbox = ipy.Checkbox( 

67 value=False, description='premultiply', 

68 ) 

69 self.premult_checkbox.observe(self._handle_premult_event, names='value') 

70 

71 # viewer 

72 self.viewer = ipy.Image(value=self._get_png(), width=f'{self.size}%') 

73 self.info = ipy.HTML(value=self._get_info()) 

74 

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 ] 

93 

94 def show(self): 

95 # type: () -> None 

96 ''' 

97 Call ipython.display with widgets. 

98 ''' 

99 ipython.display(*self._widgets) 

100 

101 def _get_layer_options(self): 

102 # type: () -> list[str] 

103 ''' 

104 Get list of channel layers. 

105 

106 Returns: 

107 list[str]: List of channel layers. 

108 ''' 

109 return self._image.channel_layers 

110 

111 def _get_channel_options(self): 

112 # type: () -> list 

113 ''' 

114 Get list of channel options. 

115 

116 Returns: 

117 list: List of channel options. 

118 ''' 

119 chan = self._image[:, :, self.layer].channels 

120 return ['all'] + chan 

121 

122 def _get_info(self): 

123 # type: () -> str 

124 ''' 

125 Creates a HTML representation of image info. 

126 

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 

140 

141 def _get_png(self): 

142 # type: () -> Optional[bytes] 

143 ''' 

144 Creates a PNG representation of image data. 

145 

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() 

156 

157 def _handle_layer_event(self, event): 

158 # type: (dict) -> None 

159 ''' 

160 Handles layer selector events. 

161 

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 

169 

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 

177 

178 # viewer 

179 self.viewer.value = self._get_png() 

180 

181 def _handle_channel_event(self, event): 

182 # type: (dict) -> None 

183 ''' 

184 Handles channel selector events. 

185 

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() 

193 

194 def _handle_resize_event(self, event): 

195 # type: (dict) -> None 

196 ''' 

197 Handles image resize events. 

198 

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}%' 

206 

207 def _handle_gamma_event(self, event): 

208 # type: (dict) -> None 

209 ''' 

210 Handles image resize events. 

211 

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() 

219 

220 def _handle_premult_event(self, event): 

221 # type: (dict) -> None 

222 ''' 

223 Handles premultiply events. 

224 

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()