agentsclimarketplace

Dicom web query

Skill aizech/clinical-skills/.bob/skills/dicom-web-query

A collection of AI agent skills focused on medical imaging and healthcare workflows. Built for radiologists, healthcare IT professionals, and researchers who want AI coding agents to help with imaging workflows, clinical documentation, AI integration, and medical research. Works with Claude Code, Codex, Cursor, Windsurf, and many other agents.

Install
npx -y skills add aizech/clinical-skills --skill dicom-web-query

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Query and retrieve DICOM objects via DICOMweb REST API. Also use when the user needs to work with DICOMweb servers, retrieve imaging studies via REST, or perform web-based DICOM operations. For traditional DICOM queries, see pacs-workflow.

SKILL.md

10.3 KB, as published. Nobody here has run it

DICOMweb Query

You are a DICOMweb expert. Your role is to help users interact with DICOMweb-enabled servers for imaging data retrieval.

DICOMweb Overview

RESTful DICOM Services

ServiceMethodDescription
QIDO-RSGETQuery DICOM images (Query-based ID Retrieve)
WADO-RSGETRetrieve DICOM objects (Web Access to DICOM)
STOW-RSPOSTStore DICOM objects (Store Over the Web)
WADO-URIGETRetrieve via URI (legacy)

Base URL Structure

https://pacs.example.com/dicomweb

QIDO-RS (Query)

Study Search

import requests

BASE_URL = "https://pacs.example.com/dicomweb"

def qido_studies(filters=None, include_fields=None):
    """
    Query for studies using QIDO-RS.
    
    Args:
        filters: Dict of DICOM tags to filter
        include_fields: Specific tags to return
    """
    params = {}
    
    if filters:
        for key, value in filters.items():
            params[f"includefield={key}"] = value
    
    response = requests.get(
        f"{BASE_URL}/studies",
        params=params
    )
    
    return response.json()

Common Query Parameters

ParameterDICOM TagDescription
00100010PatientNamePatient name
00100020PatientIDPatient ID
00080020StudyDateStudy date (YYYYMMDD)
00080030StudyTimeStudy time
00080050AccessionNumberAccession number
00080060ModalityImaging modality
00081030StudyDescriptionStudy description
00200010StudyInstanceUIDStudy UID

Query by Patient

def find_studies_by_patient(patient_id):
    """Find all studies for a patient."""
    params = {
        "PatientID": patient_id,
        "includefield": "00080020,00080030,00080060"
    }
    
    response = requests.get(f"{BASE_URL}/studies", params=params)
    return response.json()

Query by Date Range

def find_studies_by_date(start_date, end_date, modality=None):
    """Find studies within date range."""
    params = {
        "StudyDate": f"{start_date}-{end_date}"
    }
    
    if modality:
        params["Modality"] = modality
    
    response = requests.get(f"{BASE_URL}/studies", params=params)
    return response.json()

Query by Modality

def find_ct_studies(limit=100):
    """Find CT studies."""
    params = {
        "Modality": "CT",
        "limit": limit
    }
    
    response = requests.get(f"{BASE_URL}/studies", params=params)
    return response.json()

WADO-RS (Retrieve)

Retrieve Study

def retrieve_study(study_uid, format="application/dicom+json"):
    """
    Retrieve study metadata.
    
    Args:
        study_uid: Study Instance UID
        format: Response format
    """
    headers = {"Accept": format}
    
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}",
        headers=headers
    )
    
    return response.json()

Retrieve as DICOM (ZIP)

def download_study_dicom(study_uid, output_path=None):
    """Download complete study as DICOM ZIP."""
    headers = {"Accept": "application/zip"}
    
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}/archive",
        headers=headers,
        stream=True
    )
    
    if output_path:
        with open(output_path, "wb") as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)
    
    return response.content

Retrieve Series

def retrieve_series(study_uid, series_uid):
    """Retrieve specific series."""
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}/series/{series_uid}",
        headers={"Accept": "application/dicom+json"}
    )
    return response.json()

Retrieve Single Instance

def retrieve_instance(study_uid, series_uid, instance_uid):
    """Retrieve single DICOM instance metadata."""
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}",
        headers={"Accept": "application/dicom+json"}
    )
    return response.json()

Retrieve Pixel Data

def retrieve_image_pixels(study_uid, series_uid, instance_uid, frame=1):
    """
    Retrieve image pixel data.
    
    Args:
        study_uid: Study Instance UID
        series_uid: Series Instance UID
        instance_uid: SOP Instance UID
        frame: Frame number (1-indexed for multi-frame)
    """
    url = f"{BASE_URL}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/frames/{frame}"
    
    response = requests.get(
        url,
        headers={"Accept": "image/jpeg"}
    )
    
    return response.content  # JPEG image data

WADO-RS Metadata

Study Metadata

def get_study_metadata(study_uid):
    """Get complete study metadata."""
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}/metadata",
        headers={"Accept": "application/dicom+json"}
    )
    return response.json()

Series Metadata

def get_series_metadata(study_uid, series_uid):
    """Get series metadata."""
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}/series/{series_uid}/metadata",
        headers={"Accept": "application/dicom+json"}
    )
    return response.json()

Instance Metadata

def get_instance_metadata(study_uid, series_uid, instance_uid):
    """Get single instance metadata."""
    response = requests.get(
        f"{BASE_URL}/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}/metadata",
        headers={"Accept": "application/dicom+json"}
    )
    return response.json()

Thumbnail Retrieval

def get_thumbnail(study_uid, series_uid=None):
    """
    Retrieve study or series thumbnail.
    
    Args:
        study_uid: Study Instance UID
        series_uid: Optional series UID
    """
    if series_uid:
        url = f"{BASE_URL}/studies/{study_uid}/series/{series_uid}/thumbnail"
    else:
        # Get first series thumbnail
        url = f"{BASE_URL}/studies/{study_uid}/thumbnail"
    
    response = requests.get(
        url,
        headers={"Accept": "image/jpeg"}
    )
    
    return response.content

STOW-RS (Store)

Store DICOM File

def store_dicom(file_path, study_uid=None):
    """
    Store DICOM file to server.
    
    Args:
        file_path: Path to DICOM file
        study_uid: Optional existing study UID to add to
    """
    with open(file_path, "rb") as f:
        data = f.read()
    
    headers = {
        "Content-Type": "application/dicom",
        "Accept": "application/dicom+json"
    }
    
    response = requests.post(
        f"{BASE_URL}/studies{'/' + study_uid if study_uid else ''}/instances",
        headers=headers,
        data=data
    )
    
    return response.json()

Pagination

Limit Results

def qido_with_pagination(filters, limit=100, offset=0):
    """Query with pagination."""
    params = {
        "limit": limit,
        "offset": offset,
        **filters
    }
    
    response = requests.get(f"{BASE_URL}/studies", params=params)
    
    # Check for more results
    total = response.headers.get("X-Total-Count", "unknown")
    
    return {
        "results": response.json(),
        "total": total,
        "has_more": (offset + limit) < int(total) if total.isdigit() else True
    }

Common Patterns

Find and Download Study

def find_and_download(patient_id, output_dir):
    """Find patient's latest study and download."""
    # Find studies
    studies = find_studies_by_patient(patient_id)
    
    if not studies:
        return None
    
    # Get most recent
    latest = studies[0]
    study_uid = latest["0020000D"]["Value"][0]
    
    # Download
    download_path = f"{output_dir}/{study_uid}.zip"
    download_study_dicom(study_uid, download_path)
    
    return download_path

Bulk Retrieve by Date

def download_studies_by_date(start_date, end_date, modality, output_dir):
    """Download all studies for date range."""
    studies = find_studies_by_date(start_date, end_date, modality)
    
    downloaded = []
    for study in studies:
        study_uid = study["0020000D"]["Value"][0]
        try:
            path = f"{output_dir}/{study_uid}.zip"
            download_study_dicom(study_uid, path)
            downloaded.append(path)
        except Exception as e:
            print(f"Failed to download {study_uid}: {e}")
    
    return downloaded

Error Handling

ErrorCauseSolution
404 Not FoundStudy/series doesn't existVerify UID
401 UnauthorizedAuth requiredAdd credentials
403 ForbiddenInsufficient permissionsCheck user roles
500 Server ErrorServer issueRetry later

Authentication

Basic Auth

from requests.auth import HTTPBasicAuth

def authenticated_request(url, auth):
    """Make authenticated request."""
    response = requests.get(
        url,
        auth=HTTPBasicAuth(auth["username"], auth["password"])
    )
    return response

Bearer Token

def token_auth_request(url, token):
    """Make request with bearer token."""
    headers = {"Authorization": f"Bearer {token}"}
    response = requests.get(url, headers=headers)
    return response

Related Skills

  • pacs-workflow: For PACS-specific operations
  • filesystem-imaging: For local file handling
  • radiology-context: For configuration

Examples

Example 1: Query and Download CT Study

# Find CT studies from last week
studies = find_studies_by_date("20260325", "20260403", "CT")

# Download first result
if studies:
    study_uid = studies[0]["0020000D"]["Value"][0]
    download_study_dicom(study_uid, "ct_study.zip")

Example 2: Get Study Metadata

metadata = get_study_metadata("1.2.840.12345.67890")
for item in metadata:
    print(f"{item['0020000D']['vr']}: {item['00080018']['Value']}")

Example 3: Retrieve Image for Viewing

# Get thumbnail
thumb = get_thumbnail("1.2.840.12345.67890")

# Save as JPEG
with open("thumbnail.jpg", "wb") as f:
    f.write(thumb)

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.