1212from inference .core .workflows .execution_engine .entities .base import WorkflowImageData
1313from inference .core .workflows .execution_engine .entities .types import (
1414 IMAGE_KIND ,
15- INTEGER_KIND ,
1615 INSTANCE_SEGMENTATION_PREDICTION_KIND ,
16+ INTEGER_KIND ,
1717 KEYPOINT_DETECTION_PREDICTION_KIND ,
1818 OBJECT_DETECTION_PREDICTION_KIND ,
1919 STRING_KIND ,
2222from inference .core .workflows .prototypes .block import BlockResult , WorkflowBlockManifest
2323
2424TYPE : str = "roboflow_core/icon_visualization@v1"
25- SHORT_DESCRIPTION = (
26- "Draw icons on an image either at specific static coordinates or dynamically based on detections."
27- )
25+ SHORT_DESCRIPTION = "Draw icons on an image either at specific static coordinates or dynamically based on detections."
2826LONG_DESCRIPTION = """
2927The `IconVisualization` block draws icons on an image using Supervision's `sv.IconAnnotator`.
3028It supports two modes:
31291. **Static Mode**: Position an icon at a fixed location (e.g., for watermarks)
32302. **Dynamic Mode**: Position icons based on detection coordinates
3331"""
3432
33+
3534class IconManifest (VisualizationManifest ):
3635 type : Literal [f"{ TYPE } " , "IconVisualization" ]
3736 model_config = ConfigDict (
@@ -173,17 +172,14 @@ class IconManifest(VisualizationManifest):
173172 def validate_mode_parameters (self ) -> "IconManifest" :
174173 if self .mode == "dynamic" :
175174 if self .predictions is None :
176- raise ValueError (
177- "The 'predictions' field is required for dynamic mode"
178- )
175+ raise ValueError ("The 'predictions' field is required for dynamic mode" )
179176 return self
180177
181178 @classmethod
182179 def get_execution_engine_compatibility (cls ) -> Optional [str ]:
183180 return ">=1.3.0,<2.0.0"
184181
185182
186-
187183class IconVisualizationBlockV1 (VisualizationBlock ):
188184 def __init__ (self , * args , ** kwargs ):
189185 super ().__init__ (* args , ** kwargs )
@@ -224,28 +220,39 @@ def run(
224220 ) -> BlockResult :
225221 annotated_image = image .numpy_image .copy () if copy_image else image .numpy_image
226222 icon_np = icon .numpy_image .copy ()
227-
223+
224+ import os
228225 import tempfile
226+
229227 import cv2
230- import os
231-
228+
232229 # WorkflowImageData loses alpha channels when loading images.
233230 # Try to recover them from the original source.
234231 if icon_np .shape [2 ] == 3 :
235232 # Try reloading from file with IMREAD_UNCHANGED
236- if hasattr (icon , '_image_reference' ) and icon ._image_reference and \
237- not icon ._image_reference .startswith ('http' ):
233+ if (
234+ hasattr (icon , "_image_reference" )
235+ and icon ._image_reference
236+ and not icon ._image_reference .startswith ("http" )
237+ ):
238238 try :
239- icon_with_alpha = cv2 .imread (icon ._image_reference , cv2 .IMREAD_UNCHANGED )
239+ icon_with_alpha = cv2 .imread (
240+ icon ._image_reference , cv2 .IMREAD_UNCHANGED
241+ )
240242 if icon_with_alpha is not None and icon_with_alpha .shape [2 ] == 4 :
241243 icon_np = icon_with_alpha
242244 except :
243245 pass
244-
246+
245247 # Try decoding base64 with alpha preserved
246- if icon_np .shape [2 ] == 3 and hasattr (icon , '_base64_image' ) and icon ._base64_image :
248+ if (
249+ icon_np .shape [2 ] == 3
250+ and hasattr (icon , "_base64_image" )
251+ and icon ._base64_image
252+ ):
247253 try :
248254 import base64
255+
249256 image_bytes = base64 .b64decode (icon ._base64_image )
250257 nparr = np .frombuffer (image_bytes , np .uint8 )
251258 decoded = cv2 .imdecode (nparr , cv2 .IMREAD_UNCHANGED )
@@ -256,30 +263,40 @@ def run(
256263 icon_np = decoded
257264 except :
258265 pass
259-
266+
260267 with tempfile .NamedTemporaryFile (suffix = ".png" , delete = False ) as f :
261268 # Ensure proper format for IconAnnotator
262269 if len (icon_np .shape ) == 2 :
263270 icon_np = cv2 .cvtColor (icon_np , cv2 .COLOR_GRAY2BGR )
264- alpha = np .ones ((icon_np .shape [0 ], icon_np .shape [1 ], 1 ), dtype = icon_np .dtype ) * 255
271+ alpha = (
272+ np .ones (
273+ (icon_np .shape [0 ], icon_np .shape [1 ], 1 ), dtype = icon_np .dtype
274+ )
275+ * 255
276+ )
265277 icon_np = np .concatenate ([icon_np , alpha ], axis = 2 )
266278 elif icon_np .shape [2 ] == 3 :
267- alpha = np .ones ((icon_np .shape [0 ], icon_np .shape [1 ], 1 ), dtype = icon_np .dtype ) * 255
279+ alpha = (
280+ np .ones (
281+ (icon_np .shape [0 ], icon_np .shape [1 ], 1 ), dtype = icon_np .dtype
282+ )
283+ * 255
284+ )
268285 icon_np = np .concatenate ([icon_np , alpha ], axis = 2 )
269-
286+
270287 cv2 .imwrite (f .name , icon_np )
271288 icon_path = f .name
272-
289+
273290 try :
274291 if mode == "static" :
275292 img_height , img_width = annotated_image .shape [:2 ]
276-
293+
277294 # Handle negative positioning (from right/bottom edges)
278295 if x_position < 0 :
279296 actual_x = img_width + x_position - icon_width
280297 else :
281298 actual_x = x_position
282-
299+
283300 if y_position < 0 :
284301 actual_y = img_height + y_position - icon_height
285302 else :
@@ -288,48 +305,45 @@ def run(
288305 # IconAnnotator expects a detection, so create one at the desired position
289306 center_x = actual_x + icon_width // 2
290307 center_y = actual_y + icon_height // 2
291-
308+
292309 static_detections = sv .Detections (
293- xyxy = np .array ([[
294- center_x - 1 ,
295- center_y - 1 ,
296- center_x + 1 ,
297- center_y + 1
298- ]], dtype = np .float64 ),
310+ xyxy = np .array (
311+ [[center_x - 1 , center_y - 1 , center_x + 1 , center_y + 1 ]],
312+ dtype = np .float64 ,
313+ ),
299314 class_id = np .array ([0 ]),
300315 confidence = np .array ([1.0 ]),
301316 )
302-
317+
303318 annotator = sv .IconAnnotator (
304319 icon_resolution_wh = (icon_width , icon_height ),
305320 icon_position = sv .Position .CENTER ,
306321 )
307-
322+
308323 annotated_image = annotator .annotate (
309324 scene = annotated_image ,
310325 detections = static_detections ,
311- icon_path = icon_path
326+ icon_path = icon_path ,
312327 )
313-
328+
314329 elif mode == "dynamic" and predictions is not None and len (predictions ) > 0 :
315330 annotator = self .getAnnotator (
316331 icon_width = icon_width ,
317332 icon_height = icon_height ,
318333 position = position ,
319334 )
320-
335+
321336 if annotator is not None :
322337 annotated_image = annotator .annotate (
323338 scene = annotated_image ,
324339 detections = predictions ,
325- icon_path = icon_path
340+ icon_path = icon_path ,
326341 )
327342 finally :
328343 os .unlink (icon_path )
329-
344+
330345 return {
331346 OUTPUT_IMAGE_KEY : WorkflowImageData .copy_and_replace (
332- origin_image_data = image ,
333- numpy_image = annotated_image
347+ origin_image_data = image , numpy_image = annotated_image
334348 )
335349 }
0 commit comments