2323"""
2424
2525
26+ import argparse
2627import cv2
2728import numpy as np
2829
@@ -60,7 +61,7 @@ def _set_color_to_buffer(self, coord, color):
6061 if x >= 0 and x < self .width and y >= 0 and y < self .height :
6162 self .buffer [y , x ] = self ._solid_color (color )
6263
63- def _render (self ):
64+ def update_buffer (self ) -> None :
6465 self .buffer = self .image .copy ()
6566 if self .selected_pixel [0 ] >= 0 and self .selected_pixel [1 ] >= 0 :
6667 p = self .selected_pixel
@@ -83,6 +84,8 @@ def _render(self):
8384 color = (0 , 0 , 255 ) if i == self .cur_rect_index else (255 , 0 , 0 )
8485 self .buffer = cv2 .rectangle (self .buffer , (rect [0 ], rect [1 ]), (rect [2 ], rect [3 ]), color , width )
8586
87+ def _render (self ) -> None :
88+ self .update_buffer ();
8689 cv2 .imshow (self .window_name , self .buffer )
8790 # self.fullscreen = False
8891
@@ -190,7 +193,6 @@ def _mouse_callback(self, event, x, y, flags, param):
190193 self ._render ()
191194
192195 def run (self ):
193-
194196 cv2 .imshow (self .window_name , self .buffer )
195197 cv2 .setWindowProperty (self .window_name , cv2 .WND_PROP_TOPMOST , 1 )
196198 cv2 .setMouseCallback (self .window_name , self ._mouse_callback )
@@ -243,18 +245,89 @@ def run(self):
243245 print (f"Pressed key { key } " )
244246 self ._render ()
245247
248+
249+ def parse_box (box_str : str ):
250+ """
251+ Parse a box string in the format "start_x,start_y,width,height".
252+ All values should be floats between 0.0 and 1.0 (inclusive).
253+ Returns a tuple of (start_x, start_y, width, height).
254+ """
255+ try :
256+ parts = box_str .split (',' )
257+ if len (parts ) != 4 :
258+ raise ValueError (f"Box must have exactly 4 values, got { len (parts )} " )
259+
260+ values = [float (v ) for v in parts ]
261+ for i , v in enumerate (values ):
262+ if not (0.0 <= v <= 1.0 ):
263+ raise ValueError (f"Value at position { i } ({ v } ) is not between 0.0 and 1.0" )
264+
265+ return tuple (values )
266+ except ValueError as e :
267+ raise argparse .ArgumentTypeError (f"Invalid box format '{ box_str } ': { e } " )
268+
269+
246270if __name__ == '__main__' :
247- import sys
248- assert len (sys .argv ) == 2
271+ parser = argparse .ArgumentParser (
272+ description = 'Interactive image viewer with pixel inspection and box drawing capabilities.' ,
273+ epilog = """
274+ Controls:
275+ Left click: Select pixel and show info
276+ Left drag: Draw a box
277+ Right click: Select existing box
278+ w/s/a/d: Move selected pixel
279+ i: Print info for all boxes
280+ Backspace/Delete: Delete selected box (or last box if none selected)
281+ ESC: Exit
282+
283+ Box format:
284+ Each box is specified as "start_x,start_y,width,height" where all values
285+ are floats between 0.0 and 1.0 (inclusive), representing normalized coordinates.
286+ """ ,
287+ formatter_class = argparse .RawDescriptionHelpFormatter
288+ )
249289
250- filename = sys .argv [1 ]
290+ parser .add_argument (
291+ 'image' ,
292+ help = 'Path to the image file to view'
293+ )
294+
295+ parser .add_argument (
296+ '--box' ,
297+ action = 'append' ,
298+ type = parse_box ,
299+ metavar = 'START_X,START_Y,WIDTH,HEIGHT' ,
300+ help = 'Add a box to render on the image. Format: "start_x,start_y,width,height" '
301+ 'where each value is a float between 0.0 and 1.0. Can be specified multiple times.'
302+ )
303+
304+ args = parser .parse_args ()
251305
252306 # bgr or bgra channel order
253- image = cv2 .imread (filename , cv2 .IMREAD_UNCHANGED )
254- assert image is not None
307+ image = cv2 .imread (args .image , cv2 .IMREAD_UNCHANGED )
308+ if image is None :
309+ parser .error (f"Could not load image from '{ args .image } '" )
255310
256311 height = image .shape [0 ]
257312 width = image .shape [1 ]
258- print (f"Load image from { filename } , size: { width } x { height } " )
313+ print (f"Load image from { args .image } , size: { width } x { height } " )
314+
259315 viewer = ImageViewer (image )
316+
317+ # Add boxes from command line arguments
318+ if args .box :
319+ for box in args .box :
320+ start_x , start_y , box_width , box_height = box
321+ # Convert normalized coordinates to absolute pixel coordinates
322+ abs_start_x = int (start_x * width )
323+ abs_start_y = int (start_y * height )
324+ abs_end_x = int ((start_x + box_width ) * width )
325+ abs_end_y = int ((start_y + box_height ) * height )
326+
327+ # Add to viewer's rect list in the format [start_x, start_y, end_x, end_y]
328+ viewer .rects .append ([abs_start_x , abs_start_y , abs_end_x , abs_end_y ])
329+ print (f"Added box: ({ start_x :.3f} , { start_y :.3f} , { box_width :.3f} , { box_height :.3f} ) -> "
330+ f"pixels ({ abs_start_x } , { abs_start_y } , { abs_end_x } , { abs_end_y } )" )
331+ viewer .update_buffer ()
332+
260333 viewer .run ()
0 commit comments