hmgill commited on
Commit
633a827
Β·
verified Β·
1 Parent(s): 4cf382c

Update tools/segmentation.py

Browse files
Files changed (1) hide show
  1. tools/segmentation.py +110 -15
tools/segmentation.py CHANGED
@@ -1,9 +1,13 @@
1
  """
2
  Segmentation tools for cellpose-sam pipeline with proper smolagents VLM integration.
 
 
 
3
  """
4
  import base64
5
  import json
6
  import re
 
7
  from typing import Any, Dict, TYPE_CHECKING
8
  import numpy as np
9
  import cv2
@@ -26,6 +30,52 @@ from config import settings
26
  langfuse = get_client()
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # --- Global State and Caching ---
30
  _image_cache: Dict[str, tuple[str, str]] = {}
31
  _cellpose_model = None
@@ -50,12 +100,14 @@ def get_sam_predictor():
50
  _sam_predictor = SamPredictor(sam)
51
  return _sam_predictor
52
 
 
53
  def _get_cached_image(image_path: str) -> tuple[str, str] | None:
54
  """Helper to retrieve an image from the cache."""
55
  if image_path in _image_cache:
56
  return _image_cache[image_path]
57
  return None
58
 
 
59
  def _load_and_cache_image(image_path: str) -> tuple[str, str]:
60
  """Helper to load, encode, and cache an image."""
61
  image_base64, media_type = resize_and_encode_image(image_path)
@@ -94,26 +146,35 @@ def parse_parameters_from_text(param_text: str) -> dict:
94
 
95
 
96
  @tool
97
- def get_segmentation_parameters(image_path: str, agent: Any = None) -> str:
98
  """
99
  Finds the best cellpose-sam segmentation parameters for an image using vector similarity.
100
  The image will be visible to the VLM for visual analysis.
101
 
 
 
 
102
  Args:
103
- image_path (str): Path to the image file to segment.
104
  agent (Any, optional): The agent instance, passed automatically by smol-agents.
105
 
106
  Returns:
107
  str: JSON string containing recommended parameters and analysis context
108
- (NO base64 to avoid GPU OOM)
109
  """
110
  print(f"\n--- TOOL CALLED: get_segmentation_parameters for '{image_path}' ---")
111
 
 
 
 
 
 
 
 
 
112
  try:
113
  # Load and cache image (for internal use)
114
  image_base64, media_type = _get_cached_image(image_path) or _load_and_cache_image(image_path)
115
 
116
-
117
  except Exception as e:
118
  print(f"Warning: Could not read/resize image: {e}")
119
  return json.dumps({"error": f"Could not read image: {e}"})
@@ -204,7 +265,7 @@ def get_segmentation_parameters(image_path: str, agent: Any = None) -> str:
204
  f"- min_size: {params['min_size']}\n\n"
205
  f"Image stats: {image_shape[0]}x{image_shape[1]} pixels, "
206
  f"mean intensity {stats['mean_intensity']:.1f}\n\n"
207
- f"To run segmentation, use: run_cellpose_sam(image_path='{image_path}', "
208
  f"diameter={params['diameter']}, flow_threshold={params['flow_threshold']}, "
209
  f"cellprob_threshold={params['cellprob_threshold']}, min_size={params['min_size']})"
210
  }
@@ -217,7 +278,7 @@ def get_segmentation_parameters(image_path: str, agent: Any = None) -> str:
217
 
218
  @tool
219
  def run_cellpose_sam(
220
- image_path: str,
221
  diameter: int = None,
222
  flow_threshold: float = None,
223
  cellprob_threshold: float = None,
@@ -230,8 +291,11 @@ def run_cellpose_sam(
230
  Runs cellpose-sam segmentation pipeline on an image with specified parameters.
231
  Returns results WITHOUT base64 images to prevent GPU memory issues.
232
 
 
 
 
233
  Args:
234
- image_path (str): Path to the image file to segment
235
  diameter (int): Expected diameter of cells in pixels
236
  flow_threshold (float): Flow error threshold (range: 0-1)
237
  cellprob_threshold (float): Cell probability threshold (range: -6 to 6)
@@ -245,6 +309,14 @@ def run_cellpose_sam(
245
  """
246
  print(f"\n--- TOOL CALLED: run_cellpose_sam for '{image_path}' ---")
247
 
 
 
 
 
 
 
 
 
248
  try:
249
  # Load and cache input image
250
  input_image_base64, input_media_type = _get_cached_image(image_path) or _load_and_cache_image(image_path)
@@ -337,6 +409,10 @@ def run_cellpose_sam(
337
  # Save output
338
  cv2.imwrite(output_path, cv2.cvtColor(colored_overlay.astype(np.uint8), cv2.COLOR_RGB2BGR))
339
 
 
 
 
 
340
  # Load and cache output image
341
  output_image_base64, output_media_type = _load_and_cache_image(output_path)
342
 
@@ -394,9 +470,9 @@ def run_cellpose_sam(
394
 
395
  @tool
396
  def refine_cellpose_sam_segmentation(
397
- original_image_path: str,
398
- segmentation_output_path: str,
399
- current_parameters: dict,
400
  agent: Any = None,
401
  ) -> str:
402
  """
@@ -406,6 +482,8 @@ def refine_cellpose_sam_segmentation(
406
  Use this tool after run_cellpose_sam to check segmentation quality. The tool attaches
407
  both images to the current step so you can visually compare them.
408
 
 
 
409
  Before calling, consider using search_knowledge_graph or hybrid_search to refresh
410
  your understanding of how cellpose parameters affect segmentation.
411
 
@@ -416,8 +494,8 @@ def refine_cellpose_sam_segmentation(
416
  - Too many false positives: increase cellprob_threshold or min_size
417
 
418
  Args:
419
- original_image_path: Path to the original input image
420
- segmentation_output_path: Path to the segmented overlay image
421
  current_parameters: Dict with current diameter, flow_threshold, cellprob_threshold, min_size
422
  agent: The agent instance (passed automatically)
423
 
@@ -425,10 +503,27 @@ def refine_cellpose_sam_segmentation(
425
  str: JSON with guidance for VLM analysis (NO base64 images)
426
  """
427
  print(f"\n--- TOOL CALLED: refine_cellpose_sam_segmentation ---")
428
- print(f"Original image: {original_image_path}")
429
- print(f"Segmented image: {segmentation_output_path}")
430
  print(f"Current parameters: {current_parameters}")
431
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  try:
433
  # Load both images (for cache)
434
  original_b64, original_type = _get_cached_image(original_image_path) or _load_and_cache_image(original_image_path)
@@ -529,4 +624,4 @@ def refine_cellpose_sam_segmentation(
529
  "error": str(e),
530
  "message": "Could not load images for refinement. Check that both file paths are valid."
531
  }
532
- return json.dumps(error_result, indent=2)
 
1
  """
2
  Segmentation tools for cellpose-sam pipeline with proper smolagents VLM integration.
3
+
4
+ Key change: Tools now resolve image paths from global context when the provided path
5
+ is invalid or empty, preventing LLM path corruption issues.
6
  """
7
  import base64
8
  import json
9
  import re
10
+ from pathlib import Path
11
  from typing import Any, Dict, TYPE_CHECKING
12
  import numpy as np
13
  import cv2
 
30
  langfuse = get_client()
31
 
32
 
33
+ # =============================================================================
34
+ # PATH RESOLUTION HELPER
35
+ # =============================================================================
36
+ def resolve_image_path(provided_path: str, context_type: str = "image") -> str:
37
+ """
38
+ Resolve the actual image path, falling back to global context if needed.
39
+
40
+ This function handles the case where the LLM corrupts the file path by:
41
+ 1. Checking if the provided path exists
42
+ 2. If not, retrieving the correct path from global context
43
+
44
+ Args:
45
+ provided_path: The path provided by the LLM (may be corrupted)
46
+ context_type: Either "image" for input or "output" for segmentation result
47
+
48
+ Returns:
49
+ The resolved, valid path
50
+
51
+ Raises:
52
+ FileNotFoundError: If no valid path can be resolved
53
+ """
54
+ # Import here to avoid circular imports
55
+ from agents.agent import get_current_image_path, get_current_output_path
56
+
57
+ # First, check if the provided path is valid
58
+ if provided_path and Path(provided_path).exists():
59
+ print(f"[Path Resolution] Using provided path: {provided_path}")
60
+ return provided_path
61
+
62
+ # Path is invalid - try to get from context
63
+ if context_type == "image":
64
+ context_path = get_current_image_path()
65
+ else:
66
+ context_path = get_current_output_path()
67
+
68
+ if context_path and Path(context_path).exists():
69
+ print(f"[Path Resolution] Provided path invalid, using context: {context_path}")
70
+ print(f"[Path Resolution] (LLM provided: '{provided_path}')")
71
+ return context_path
72
+
73
+ # Neither worked
74
+ error_msg = f"Could not resolve {context_type} path. Provided: '{provided_path}', Context: '{context_path}'"
75
+ print(f"[Path Resolution] ERROR: {error_msg}")
76
+ raise FileNotFoundError(error_msg)
77
+
78
+
79
  # --- Global State and Caching ---
80
  _image_cache: Dict[str, tuple[str, str]] = {}
81
  _cellpose_model = None
 
100
  _sam_predictor = SamPredictor(sam)
101
  return _sam_predictor
102
 
103
+
104
  def _get_cached_image(image_path: str) -> tuple[str, str] | None:
105
  """Helper to retrieve an image from the cache."""
106
  if image_path in _image_cache:
107
  return _image_cache[image_path]
108
  return None
109
 
110
+
111
  def _load_and_cache_image(image_path: str) -> tuple[str, str]:
112
  """Helper to load, encode, and cache an image."""
113
  image_base64, media_type = resize_and_encode_image(image_path)
 
146
 
147
 
148
  @tool
149
+ def get_segmentation_parameters(image_path: str = "", agent: Any = None) -> str:
150
  """
151
  Finds the best cellpose-sam segmentation parameters for an image using vector similarity.
152
  The image will be visible to the VLM for visual analysis.
153
 
154
+ NOTE: If image_path is empty or invalid, the tool will automatically use the
155
+ current image from the system context.
156
+
157
  Args:
158
+ image_path (str): Path to the image file (optional - uses context if invalid).
159
  agent (Any, optional): The agent instance, passed automatically by smol-agents.
160
 
161
  Returns:
162
  str: JSON string containing recommended parameters and analysis context
 
163
  """
164
  print(f"\n--- TOOL CALLED: get_segmentation_parameters for '{image_path}' ---")
165
 
166
+ # Resolve the actual image path
167
+ try:
168
+ actual_path = resolve_image_path(image_path, context_type="image")
169
+ except FileNotFoundError as e:
170
+ return json.dumps({"error": str(e)})
171
+
172
+ image_path = actual_path # Use resolved path from here on
173
+
174
  try:
175
  # Load and cache image (for internal use)
176
  image_base64, media_type = _get_cached_image(image_path) or _load_and_cache_image(image_path)
177
 
 
178
  except Exception as e:
179
  print(f"Warning: Could not read/resize image: {e}")
180
  return json.dumps({"error": f"Could not read image: {e}"})
 
265
  f"- min_size: {params['min_size']}\n\n"
266
  f"Image stats: {image_shape[0]}x{image_shape[1]} pixels, "
267
  f"mean intensity {stats['mean_intensity']:.1f}\n\n"
268
+ f"To run segmentation, use: run_cellpose_sam(image_path='', "
269
  f"diameter={params['diameter']}, flow_threshold={params['flow_threshold']}, "
270
  f"cellprob_threshold={params['cellprob_threshold']}, min_size={params['min_size']})"
271
  }
 
278
 
279
  @tool
280
  def run_cellpose_sam(
281
+ image_path: str = "",
282
  diameter: int = None,
283
  flow_threshold: float = None,
284
  cellprob_threshold: float = None,
 
291
  Runs cellpose-sam segmentation pipeline on an image with specified parameters.
292
  Returns results WITHOUT base64 images to prevent GPU memory issues.
293
 
294
+ NOTE: If image_path is empty or invalid, the tool will automatically use the
295
+ current image from the system context.
296
+
297
  Args:
298
+ image_path (str): Path to the image file (optional - uses context if invalid)
299
  diameter (int): Expected diameter of cells in pixels
300
  flow_threshold (float): Flow error threshold (range: 0-1)
301
  cellprob_threshold (float): Cell probability threshold (range: -6 to 6)
 
309
  """
310
  print(f"\n--- TOOL CALLED: run_cellpose_sam for '{image_path}' ---")
311
 
312
+ # Resolve the actual image path
313
+ try:
314
+ actual_path = resolve_image_path(image_path, context_type="image")
315
+ except FileNotFoundError as e:
316
+ return json.dumps({"error": str(e)})
317
+
318
+ image_path = actual_path # Use resolved path from here on
319
+
320
  try:
321
  # Load and cache input image
322
  input_image_base64, input_media_type = _get_cached_image(image_path) or _load_and_cache_image(image_path)
 
409
  # Save output
410
  cv2.imwrite(output_path, cv2.cvtColor(colored_overlay.astype(np.uint8), cv2.COLOR_RGB2BGR))
411
 
412
+ # Store output path in context for later tools
413
+ from agents.agent import set_current_output_path
414
+ set_current_output_path(output_path)
415
+
416
  # Load and cache output image
417
  output_image_base64, output_media_type = _load_and_cache_image(output_path)
418
 
 
470
 
471
  @tool
472
  def refine_cellpose_sam_segmentation(
473
+ original_image_path: str = "",
474
+ segmentation_output_path: str = "",
475
+ current_parameters: dict = None,
476
  agent: Any = None,
477
  ) -> str:
478
  """
 
482
  Use this tool after run_cellpose_sam to check segmentation quality. The tool attaches
483
  both images to the current step so you can visually compare them.
484
 
485
+ NOTE: If paths are empty or invalid, the tool will automatically use paths from context.
486
+
487
  Before calling, consider using search_knowledge_graph or hybrid_search to refresh
488
  your understanding of how cellpose parameters affect segmentation.
489
 
 
494
  - Too many false positives: increase cellprob_threshold or min_size
495
 
496
  Args:
497
+ original_image_path: Path to the original input image (optional - uses context)
498
+ segmentation_output_path: Path to the segmented overlay image (optional - uses context)
499
  current_parameters: Dict with current diameter, flow_threshold, cellprob_threshold, min_size
500
  agent: The agent instance (passed automatically)
501
 
 
503
  str: JSON with guidance for VLM analysis (NO base64 images)
504
  """
505
  print(f"\n--- TOOL CALLED: refine_cellpose_sam_segmentation ---")
506
+ print(f"Original image (provided): {original_image_path}")
507
+ print(f"Segmented image (provided): {segmentation_output_path}")
508
  print(f"Current parameters: {current_parameters}")
509
 
510
+ # Resolve paths from context if needed
511
+ try:
512
+ actual_original = resolve_image_path(original_image_path, context_type="image")
513
+ except FileNotFoundError as e:
514
+ return json.dumps({"error": f"Could not resolve original image: {e}"})
515
+
516
+ try:
517
+ actual_segmented = resolve_image_path(segmentation_output_path, context_type="output")
518
+ except FileNotFoundError as e:
519
+ return json.dumps({"error": f"Could not resolve segmented image: {e}"})
520
+
521
+ original_image_path = actual_original
522
+ segmentation_output_path = actual_segmented
523
+
524
+ print(f"Resolved original: {original_image_path}")
525
+ print(f"Resolved segmented: {segmentation_output_path}")
526
+
527
  try:
528
  # Load both images (for cache)
529
  original_b64, original_type = _get_cached_image(original_image_path) or _load_and_cache_image(original_image_path)
 
624
  "error": str(e),
625
  "message": "Could not load images for refinement. Check that both file paths are valid."
626
  }
627
+ return json.dumps(error_result, indent=2)