File size: 9,644 Bytes
42bf28c
 
 
b151b8a
6954f98
a8b0d0d
42bf28c
 
 
6954f98
a8b0d0d
42bf28c
 
 
 
a8b0d0d
 
42bf28c
 
2f8f161
 
 
 
 
 
 
 
 
42bf28c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534b2bf
42bf28c
 
 
61e6e27
 
 
 
42bf28c
 
 
 
 
 
534b2bf
42bf28c
 
534b2bf
42bf28c
61e6e27
 
534b2bf
61e6e27
534b2bf
61e6e27
 
b5fbe5a
42bf28c
61e6e27
 
 
42bf28c
534b2bf
6954f98
534b2bf
 
6954f98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534b2bf
 
6954f98
534b2bf
 
 
42bf28c
534b2bf
42bf28c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61e6e27
42bf28c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534b2bf
42bf28c
 
534b2bf
 
42bf28c
 
534b2bf
 
 
 
 
 
 
42bf28c
 
 
 
 
534b2bf
42bf28c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534b2bf
61e6e27
 
 
 
 
42bf28c
 
 
 
 
 
 
 
 
af86f7f
42bf28c
 
 
6954f98
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
"""
Gradio web interface for CellposeAgent
"""
import os
import json
import gradio as gr
from pathlib import Path
from langfuse import get_client
from openinference.instrumentation.smolagents import SmolagentsInstrumentor
from smolagents.agents import ActionStep

from config import settings
from agents.agent import CellposeAgent
from stores import neo4j_store
from utils.prechecks import check_hf_persistent_storage


def setup_observability():
    """Initializes Langfuse and Smolagents instrumentation."""
    try:
        if os.getenv("LANGFUSE_PUBLIC_KEY") and os.getenv("LANGFUSE_SECRET_KEY"):
            get_client()
            SmolagentsInstrumentor().instrument()
            print("βœ“ Observability and instrumentation initialized.")
        else:
            print("⚠️  Langfuse keys not found. Observability disabled.")
    except Exception as e:
        print(f"⚠️  Could not initialize observability: {e}")


def initialize_app():
    """Initialize the application and verify prerequisites."""
    print("\n--- Initializing Cellpose Agent Application ---")
    
    # Setup observability
    setup_observability()
    
    # Configure LlamaIndex
    settings.configure_llama_index()

    # check for cellpose-db
    check_hf_persistent_storage(
        repo_id = "hmgill/Cellpose-DB",
        target = "cellpose_db",
        file_or_folder="folder"
    )

    # check for cellpose sam
    check_hf_persistent_storage(
        repo_id = "hmgill/Cellpose-SAM-Checkpoint",
        target = "sam_vit_h_4b8939.pth",
        file_or_folder="file"
    )

    # Verify knowledge graph is ready
    try:
        node_count, _ = neo4j_store.check_graph_status()
        if node_count == 0:
            print("\n❌ WARNING: The knowledge graph is empty.")
            print("Please run the setup script to build the knowledge graph:")
            print("\n    python setup_kg.py\n")
            return False
        print(f"βœ“ Knowledge graph is ready with {node_count} nodes.")
    except Exception as e:
        print(f"❌ ERROR: Could not connect to Neo4j: {e}")
        print("Please ensure Neo4j is running and accessible.")
        return False
    
    return True


def process_image_task(image_path: str, task_text: str, agent: CellposeAgent) -> tuple[str, str | None]:
    """
    Process a user task with the CellposeAgent.
    
    The image_path is now passed SEPARATELY to agent.run() so it gets stored
    in global context. This prevents the LLM from needing to reproduce the 
    exact path string (which it often corrupts).
    
    Args:
        image_path: Path to the uploaded image file
        task_text: User's text prompt/question
        agent: Initialized CellposeAgent instance
    
    Returns:
        tuple: (agent's text response, path to segmented image or None)
    """
    if not image_path:
        return "⚠️ Please upload an image first.", None
    
    # Build task WITHOUT embedding the full path
    # The agent instructions tell it to use image_path="" and the tool will resolve from context
    if not task_text or task_text.strip() == "":
        task = "Analyze the uploaded image and recommend optimal segmentation parameters. Then run segmentation."
    else:
        # Don't include the path in the task - it will be available via context
        task = task_text
    
    try:
        # Pass image_path SEPARATELY - this stores it in global context
        # Tools will retrieve the correct path from context instead of relying on LLM
        result = agent.run(task=task, image_path=image_path)
        get_client().flush()
        
        # Extract output image path from agent's memory (more reliable than parsing text)
        output_image_path = None
        try:
            # Search through agent's memory steps in reverse order (most recent first)
            for step in reversed(agent.agent.memory.steps):
                if isinstance(step, ActionStep) and step.observations:
                    try:
                        obs_data = json.loads(step.observations)
                        
                        # Check if this step contains segmentation output
                        if obs_data.get("status") == "success" and "output_path" in obs_data:
                            candidate_path = obs_data["output_path"]
                            
                            # Verify the file exists
                            if Path(candidate_path).exists():
                                output_image_path = candidate_path
                                print(f"βœ“ Found segmentation output: {output_image_path}")
                                break
                                
                    except (json.JSONDecodeError, Exception) as parse_error:
                        continue
            
            if output_image_path is None:
                print("ℹ️ No segmentation output found in agent memory")
                    
        except Exception as e:
            print(f"Warning: Could not extract output image from agent memory: {e}")
        
        return result, output_image_path
        
    except Exception as e:
        return f"❌ Error processing task: {str(e)}", None


def create_gradio_interface():
    """Creates and configures the Gradio interface."""
    
    # Initialize the agent once at startup
    if not initialize_app():
        raise RuntimeError("Failed to initialize application. Please check logs.")
    
    agent = CellposeAgent()
    print("βœ“ CellposeAgent initialized and ready.")
    
    with gr.Blocks(title="Cellpose-SAM Agent", theme=gr.themes.Soft()) as demo:
        gr.Markdown(
            """
            # πŸ”¬ Cellpose-SAM Segmentation Agent
            
            Upload a microscopy image and ask the AI agent to recommend optimal segmentation parameters,
            run segmentation, or answer questions about the cellpose-sam pipeline.
            """
        )
        
        with gr.Row():
            with gr.Column(scale=1):
                # Image upload
                image_input = gr.Image(
                    label="Upload Microscopy Image",
                    type="filepath",
                    height=300
                )
                
                # Task input
                task_input = gr.Textbox(
                    label="Task / Question",
                    placeholder="e.g., 'What parameters would work best?' or 'Run segmentation' (image path handled automatically)",
                    lines=3
                )
                
                # Submit button
                submit_btn = gr.Button("Run Agent", variant="primary", size="lg")
                
                # Example tasks
                gr.Markdown("### πŸ’‘ Example Tasks")
                gr.Examples(
                    examples=[
                        ["What parameters would work best for this image?"],
                        ["Analyze this image and run segmentation with optimal parameters."],
                        ["What is the flow_threshold parameter and how does it affect segmentation?"],
                        ["Run segmentation with diameter=30, flow_threshold=0.5, cellprob_threshold=0, min_size=20"],
                    ],
                    inputs=task_input,
                    label="Click to use:"
                )
            
            with gr.Column(scale=1):
                # Text output
                output = gr.Textbox(
                    label="Agent Response",
                    lines=12,
                    max_lines=20,
                    show_copy_button=True
                )
                
                # Image output for segmentation results
                output_image = gr.Image(
                    label="Segmentation Result",
                    type="filepath",
                    height=400
                )
        
        # Event handler
        submit_btn.click(
            fn=lambda img, task: process_image_task(img, task, agent),
            inputs=[image_input, task_input],
            outputs=[output, output_image]
        )
        
        gr.Markdown(
            """
            ---
            ### πŸ“š What can this agent do?
            
            - **Parameter Recommendation**: Analyzes your image and suggests optimal segmentation parameters
            - **Automated Segmentation**: Runs the full cellpose-sam pipeline with parameter refinement
            - **Visual Analysis**: Uses vision-language models to assess segmentation quality
            - **Documentation Search**: Answers questions about parameters using RAG and knowledge graphs
            - **Iterative Refinement**: Automatically adjusts parameters based on visual feedback
            
            ### πŸ” How it works
            
            1. Upload your microscopy image
            2. The agent finds similar images and recommends parameters
            3. Visually analyzes your image to validate recommendations
            4. Runs segmentation and checks quality
            5. Refines parameters if needed (up to 2 iterations)
            6. **Displays the segmented overlay image with colored cell masks**
            
            ### βš™οΈ Technical Note
            
            Image paths are handled automatically by the system - the agent doesn't need to 
            reproduce exact file paths, preventing common path corruption issues with LLMs.
            """
        )
    
    return demo


def main():
    """Launch the Gradio application."""
    demo = create_gradio_interface()
    demo.launch(server_port=7860)


if __name__ == "__main__":
    main()