Source code for tooluniverse.cellpainting_tool
"""
Cell Painting Tool
Provides access to the IDR (Image Data Resource) public API for querying
Cell Painting high-content microscopy datasets, including screens, plates,
and well-level metadata.
API: https://idr.openmicroscopy.org/api/v0/
No authentication required (public data).
"""
import requests
from typing import Dict, Any, Optional
from .base_tool import BaseTool
from .tool_registry import register_tool
IDR_BASE_URL = "https://idr.openmicroscopy.org/api/v0/m"
# Keywords used to identify Cell Painting / morphological profiling screens
_CELLPAINTING_KEYWORDS = ["paint", "jump", "phenotypic", "morphological"]
[docs]
@register_tool("CellPaintingTool")
class CellPaintingTool(BaseTool):
"""
Tool for querying Cell Painting datasets hosted on the Image Data Resource (IDR).
Provides access to:
- Available Cell Painting screens (studies)
- Plates within a screen
- Well-level metadata and image links for a plate
"""
[docs]
def __init__(self, tool_config: Dict[str, Any]):
super().__init__(tool_config)
self.parameter = tool_config.get("parameter", {})
self.required = self.parameter.get("required", [])
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute the Cell Painting IDR tool with given arguments."""
operation = arguments.get("operation")
if not operation:
return {"status": "error", "error": "Missing required parameter: operation"}
operation_handlers = {
"search_screens": self._search_screens,
"get_screen_plates": self._get_screen_plates,
"get_well_data": self._get_well_data,
}
handler = operation_handlers.get(operation)
if not handler:
return {
"status": "error",
"error": f"Unknown operation: {operation}",
"available_operations": list(operation_handlers.keys()),
}
try:
return handler(arguments)
except requests.exceptions.Timeout:
return {"status": "error", "error": "IDR API request timed out"}
except requests.exceptions.ConnectionError:
return {"status": "error", "error": "Failed to connect to IDR API"}
except Exception as e:
return {"status": "error", "error": f"Operation failed: {str(e)}"}
[docs]
def _make_request(self, path: str, params: Optional[Dict] = None) -> Dict[str, Any]:
"""Make a GET request to the IDR API."""
url = f"{IDR_BASE_URL}/{path}"
response = requests.get(url, params=params or {}, timeout=30)
if response.status_code == 200:
return {"ok": True, "data": response.json()}
else:
return {
"ok": False,
"error": f"API request failed with status {response.status_code}",
"detail": response.text[:500],
}
[docs]
def _search_screens(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""List available Cell Painting screens in IDR, optionally filtered by keyword."""
query: Optional[str] = arguments.get("query")
result = self._make_request("screens/", {"limit": 500})
if not result["ok"]:
return {"status": "error", "error": result["error"]}
screens_raw = result["data"].get("data", [])
cp_screens = []
for screen in screens_raw:
name = screen.get("Name", "")
description = screen.get("Description", "")
combined = (name + " " + description).lower()
if any(kw in combined for kw in _CELLPAINTING_KEYWORDS):
cp_screens.append(screen)
if query:
q_lower = query.lower()
cp_screens = [
s
for s in cp_screens
if q_lower
in (s.get("Name", "") + " " + s.get("Description", "")).lower()
]
output = [
{
"screen_id": screen.get("@id"),
"name": screen.get("Name", ""),
"description": screen.get("Description", ""),
"url": screen.get("url:screen", ""),
}
for screen in cp_screens
]
return {
"status": "success",
"data": output,
"num_results": len(output),
}
[docs]
def _get_screen_plates(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Get plates belonging to a given screen."""
screen_id = arguments.get("screen_id")
if screen_id is None:
return {"status": "error", "error": "Missing required parameter: screen_id"}
result = self._make_request(f"screens/{screen_id}/plates/", {"limit": 1000})
if not result["ok"]:
return {"status": "error", "error": result["error"]}
plates_raw = result["data"].get("data", [])
plates = [
{"plate_id": p.get("@id"), "name": p.get("Name", "")} for p in plates_raw
]
return {
"status": "success",
"data": {
"screen_id": screen_id,
"plates": plates,
"n_plates": len(plates),
},
}
[docs]
def _get_well_data(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Get well-level metadata and image links for a plate."""
plate_id = arguments.get("plate_id")
if plate_id is None:
return {"status": "error", "error": "Missing required parameter: plate_id"}
limit = arguments.get("limit", 20)
result = self._make_request(f"plates/{plate_id}/wells/", {"limit": limit})
if not result["ok"]:
return {"status": "error", "error": result["error"]}
wells_raw = result["data"].get("data", [])
output = []
for well in wells_raw:
well_samples = well.get("WellSamples", [])
image_url = None
if well_samples:
image_url = well_samples[0].get("Image", {}).get("url:image")
output.append(
{
"well_id": well.get("@id"),
"column": well.get("Column"),
"row": well.get("Row"),
"url": well.get("url:well", ""),
"image_url": image_url,
}
)
return {
"status": "success",
"data": output,
"num_results": len(output),
}