Skip to main content

Definition and tools for Open Imaging Finding Models

Project description

findingmodel Package

Contains library code for managing FindingModel objects.

Look in the demo notebook.

CLI

$ python -m findingmodel
Usage: python -m findingmodel [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  config           Show the currently active configuration.
  fm-to-markdown   Convert finding model JSON file to Markdown format.
  make-info        Generate description/synonyms and more...
  make-stub-model  Generate a simple finding model object (presence and...
  markdown-to-fm   Convert markdown file to finding model format.

Models

FindingModelBase

Basics of a finding model, including name, description, and attributes.

Properties:

  • name: The name of the finding.
  • description: A brief description of the finding. Optional.
  • synonyms: Alternative names or abbreviations for the finding. Optional.
  • tags: Keywords or categories associated with the finding. Optional.
  • attributes: A collection of attributes objects associated with the finding.

Methods:

  • as_markdown(): Generates a markdown representation of the finding model.

FindingModelFull

Uses FindingModelBase, but adds contains more detailed metadata:

  • Requiring IDs on models and attributes (with enumerated codes for values on choice attributes)
  • Allows index codes on multiple levels (model, attribute, value)
  • Allows contributors (people and organization)

FindingInfo

Information on a finding, including description and synonyms, can add detailed description and citations.

Properties:

  • name: The name of the finding.
  • synonyms: Alternative names or abbreviations for the finding. Optional.
  • description: A brief description of the finding. Optional.
  • detail: A more detailed description of the finding. Optional.
  • citations: A list of citations or references related to the finding. Optional.

Index

The Index class provides MongoDB-based indexing and management of finding model definitions stored as .fm.json files in a defs/ directory structure (e.g., in a clone of the Open Imaging Finding Model repository). It enables fast lookup by ID, name, or synonym and manages collections for finding models, people, and organizations.

Basic Usage

import asyncio
from findingmodel.index import Index

async def main():
    # Initialize with MongoDB connection from settings
    index = Index()
    
    # Set up indexes for first-time use
    await index.setup_indexes()
    
    # Get count of indexed models
    count = await index.count()
    print(f"Total models indexed: {count}")
    
    # Lookup by ID, name, or synonym (async method)
    metadata = await index.get("abdominal aortic aneurysm")
    if metadata:
        print(metadata.model_dump())
# > {'attributes': [{'attribute_id': 'OIFMA_MSFT_898601',
# >                  'name': 'presence',
# >                  'type': 'choice'},
# >                 {'attribute_id': 'OIFMA_MSFT_783072',
# >                  'name': 'change from prior',
# >                  'type': 'choice'}],
# >  'description': 'An abdominal aortic aneurysm (AAA) is a localized dilation of '
# >                 'the abdominal aorta, typically defined as a diameter greater '
# >                 'than 3 cm, which can lead to rupture and significant '
# >                 'morbidity or mortality.',
# >  'filename': 'abdominal_aortic_aneurysm.fm.json',
# >  'name': 'abdominal aortic aneurysm',
# >  'oifm_id': 'OIFM_MSFT_134126',
# >  'synonyms': ['AAA'],
# >  'tags': None}

    # Search for models (returns list of IndexEntry objects)
    results = await index.search("abdominal", limit=5)
    for result in results:
        print(f"- {result.name}: {result.oifm_id}")
    
    # Check if a model exists
    exists = await index.contains("pneumothorax")
    print(f"Pneumothorax exists: {exists}")

asyncio.run(main())

Directory Synchronization

async def sync_directory():
    index = Index()
    
    # Update index from a directory of .fm.json files
    # Returns (added, updated, removed) counts
    added, updated, removed = await index.update_from_directory("path/to/defs")
    print(f"Sync complete: {added} added, {updated} updated, {removed} removed")
    
    # Add or update a single file
    from findingmodel import FindingModelFull
    model = FindingModelFull.model_validate_json(open("model.fm.json").read())
    result = await index.add_or_update_entry_from_file("model.fm.json", model)
    print(f"File update result: {result}")

asyncio.run(sync_directory())

Working with Contributors

async def get_contributors():
    index = Index()
    
    # Get a person by GitHub username
    person = await index.get_person("johndoe")
    if person:
        print(f"Name: {person.name}, Email: {person.email}")
    
    # Get an organization by code
    org = await index.get_organization("MSFT")
    if org:
        print(f"Organization: {org.name}")
    
    # Count contributors
    people_count = await index.count_people()
    org_count = await index.count_organizations()
    print(f"People: {people_count}, Organizations: {org_count}")

asyncio.run(get_contributors())

See example usage in notebook.

Tools

All tools are available through findingmodel.tools. Import them like:

from findingmodel.tools import create_info_from_name, add_details_to_info
# Or import the entire tools module
import findingmodel.tools as tools

Note: Previous function names (e.g., describe_finding_name, create_finding_model_from_markdown) are still available but deprecated. They will show deprecation warnings and point to the new names.

create_info_from_name()

Takes a finding name and generates a usable description and possibly synonyms (FindingInfo) using OpenAI models (requires OPENAI_API_KEY to be set to a valid value).

import asyncio
from findingmodel.tools import create_info_from_name

async def describe_finding():
    # Generate basic finding information
    info = await create_info_from_name("Pneumothorax")
    print(f"Name: {info.name}")
    print(f"Synonyms: {info.synonyms}")
    print(f"Description: {info.description[:100]}...")
    return info

info = asyncio.run(describe_finding())
# Output:
# Name: pneumothorax
# Synonyms: ['PTX']
# Description: Pneumothorax is the presence of air in the pleural space...

add_details_to_info()

Takes a described finding as above and uses Perplexity to get a lot of possible reference information, possibly including citations (requires PERPLEXITY_API_KEY to be set to a valid value).

import asyncio
from findingmodel.tools import add_details_to_info
from findingmodel import FindingInfo

async def enhance_finding():
    # Start with basic finding info
    finding = FindingInfo(
        name="pneumothorax", 
        synonyms=['PTX'],
        description='Pneumothorax is the presence of air in the pleural space'
    )
    
    # Add detailed information and citations
    enhanced = await add_details_to_info(finding)
    
    print(f"Detail length: {len(enhanced.detail)} characters")
    print(f"Citations found: {len(enhanced.citations)}")
    
    # Show first few citations
    for i, citation in enumerate(enhanced.citations[:3], 1):
        print(f"  {i}. {citation}")
    
    return enhanced

enhanced_info = asyncio.run(enhance_finding())
# Output:
# Detail length: 2547 characters  
# Citations found: 8
#   1. https://pubs.rsna.org/doi/full/10.1148/rg.2020200020
#   2. https://ajronline.org/doi/full/10.2214/AJR.17.18721
#   3. https://radiopaedia.org/articles/pneumothorax

create_model_from_markdown()

Creates a FindingModel from a markdown file or text using OpenAI API.

import asyncio
from pathlib import Path
from findingmodel.tools import create_model_from_markdown, create_info_from_name

async def create_from_markdown():
    # First create basic info about the finding
    finding_info = await create_info_from_name("pneumothorax")
    
    # Option 1: Create from markdown text
    markdown_outline = """
    # Pneumothorax Attributes
    - Size: small (<2cm), moderate (2-4cm), large (>4cm)
    - Location: apical, basilar, lateral, complete
    - Tension: present, absent, indeterminate
    - Cause: spontaneous, traumatic, iatrogenic
    """
    
    model = await create_model_from_markdown(
        finding_info, 
        markdown_text=markdown_outline
    )
    print(f"Created model with {len(model.attributes)} attributes")
    
    # Option 2: Create from markdown file
    # Save markdown to file first
    Path("pneumothorax.md").write_text(markdown_outline)
    
    model_from_file = await create_model_from_markdown(
        finding_info,
        markdown_path="pneumothorax.md"
    )
    
    # Display the attributes
    for attr in model.attributes:
        print(f"- {attr.name}: {attr.type}")
        if hasattr(attr, 'values'):
            print(f"  Values: {[v.name for v in attr.values]}")
    
    return model

model = asyncio.run(create_from_markdown())
# Output:
# Created model with 4 attributes
# - size: choice
#   Values: ['small (<2cm)', 'moderate (2-4cm)', 'large (>4cm)']
# - location: choice  
#   Values: ['apical', 'basilar', 'lateral', 'complete']
# - tension: choice
#   Values: ['present', 'absent', 'indeterminate']
# - cause: choice
#   Values: ['spontaneous', 'traumatic', 'iatrogenic']

create_model_stub_from_info()

Given even a basic FindingInfo, turn it into a FindingModelBase object with at least two attributes:

  • presence: Whether the finding is seen
    (present, absent, indeterminate, unknown)
  • change from prior: How the finding has changed from prior exams
    (unchanged, stable, increased, decreased, new, resolved, no prior)
import asyncio
from findingmodel.tools import create_info_from_name, create_model_stub_from_info

async def create_stub():
    # Create finding info
    finding_info = await create_info_from_name("pneumothorax")
    
    # Create a basic model stub with standard presence/change attributes
    stub_model = create_model_stub_from_info(finding_info)
    
    print(f"Model name: {stub_model.name}")
    print(f"Created model with {len(stub_model.attributes)} attributes:")
    
    for attr in stub_model.attributes:
        print(f"\n- {attr.name} ({attr.type}):")
        if hasattr(attr, 'values'):
            for value in attr.values:
                print(f"  • {value.name}")
    
    # You can also add tags
    stub_with_tags = create_model_stub_from_info(
        finding_info, 
        tags=["chest", "emergency", "trauma"]
    )
    print(f"\nTags: {stub_with_tags.tags}")
    
    return stub_model

stub = asyncio.run(create_stub())
# Output:
# Model name: pneumothorax
# Created model with 2 attributes:
# 
# - presence (choice):
#   • present
#   • absent  
#   • indeterminate
#   • unknown
# 
# - change from prior (choice):
#   • unchanged
#   • stable
#   • increased
#   • decreased
#   • new
#   • resolved
#   • no prior
# 
# Tags: ['chest', 'emergency', 'trauma']

add_ids_to_model()

Generates and adds OIFM IDs to a FindingModelBase object and returns it as a FindingModelFull object. Note that the source parameter refers to the source component of the OIFM ID, which describes the originating organization of the model (e.g., MGB for Mass General Brigham and MSFT for Microsoft).

import asyncio
from findingmodel.tools import (
    add_ids_to_model, 
    create_model_stub_from_info,
    create_info_from_name
)

async def add_identifiers():
    # Create a basic model (without IDs)
    finding_info = await create_info_from_name("pneumothorax")
    stub_model = create_model_stub_from_info(finding_info)
    
    # Add OIFM IDs for tracking and standardization
    # Source can be 3 or 4 letters (e.g., "MGB", "MSFT")
    full_model = add_ids_to_model(stub_model, source="MSFT")
    
    print(f"Model ID: {full_model.oifm_id}")
    print(f"Attribute IDs:")
    for attr in full_model.attributes:
        print(f"  - {attr.name}: {attr.oifma_id}")
        if hasattr(attr, 'values'):
            for value in attr.values:
                print(f"    • {value.name}: {value.oifmv_id}")
    
    return full_model

full_model = asyncio.run(add_identifiers())
# Output:
# Model ID: OIFM_MSFT_123456
# Attribute IDs:
#   - presence: OIFMA_MSFT_789012
#     • present: OIFMV_MSFT_345678
#     • absent: OIFMV_MSFT_901234
#     • indeterminate: OIFMV_MSFT_567890
#     • unknown: OIFMV_MSFT_123456
#   - change from prior: OIFMA_MSFT_789013
#     • unchanged: OIFMV_MSFT_345679
#     • stable: OIFMV_MSFT_901235
#     ...

add_standard_codes_to_model()

Edits a FindingModelFull in place to include some RadLex and SNOMED-CT codes that correspond to some typical situations.

import asyncio
from findingmodel.tools import (
    add_standard_codes_to_model,
    add_ids_to_model,
    create_model_stub_from_info,
    create_info_from_name
)

async def add_medical_codes():
    # Create a full model with IDs
    finding_info = await create_info_from_name("pneumothorax")
    stub_model = create_model_stub_from_info(finding_info)
    full_model = add_ids_to_model(stub_model, source="MSFT")
    
    # Add standard medical vocabulary codes
    add_standard_codes_to_model(full_model)
    
    print("Added standard codes:")
    
    # Check model-level codes
    if full_model.index_codes:
        print(f"\nModel codes:")
        for code in full_model.index_codes:
            print(f"  - {code.system}: {code.code} ({code.display})")
    
    # Check attribute-level codes
    for attr in full_model.attributes:
        if attr.index_codes:
            print(f"\n{attr.name} attribute codes:")
            for code in attr.index_codes:
                print(f"  - {code.system}: {code.code}")
        
        # Check value-level codes
        if hasattr(attr, 'values'):
            for value in attr.values:
                if value.index_codes:
                    print(f"  {value.name} value codes:")
                    for code in value.index_codes:
                        print(f"    - {code.system}: {code.code}")
    
    return full_model

model_with_codes = asyncio.run(add_medical_codes())
# Output:
# Added standard codes:
# 
# Model codes:
#   - RadLex: RID5352 (pneumothorax)
#   - SNOMED-CT: 36118008 (Pneumothorax)
# 
# presence attribute codes:
#   - RadLex: RID39039
#   present value codes:
#     - RadLex: RID28472
#   absent value codes:
#     - RadLex: RID28473
# ...

find_similar_models()

Searches for existing finding models in the database that are similar to a proposed new finding. This helps avoid creating duplicate models by identifying existing models that could be edited instead. Uses AI agents to perform intelligent search and analysis.

import asyncio
from findingmodel.tools import find_similar_models
from findingmodel.index import Index

async def check_for_similar_models():
    # Initialize index (connects to MongoDB)
    index = Index()
    
    # Search for models similar to a proposed finding
    analysis = await find_similar_models(
        finding_name="pneumothorax",
        description="Presence of air in the pleural space causing lung collapse",
        synonyms=["PTX", "collapsed lung"],
        index=index  # Optional, will create one if not provided
    )
    
    print(f"Recommendation: {analysis.recommendation}")
    print(f"Confidence: {analysis.confidence:.2f}")
    
    if analysis.similar_models:
        print("
Similar existing models found:")
        for model in analysis.similar_models:
            print(f"  - {model.name} (ID: {model.oifm_id})")
    
    # The recommendation will be one of:
    # - "edit_existing": Very similar model found, edit it instead
    # - "create_new": No similar models, safe to create new one
    # - "review_needed": Some similarity found, manual review recommended
    
    return analysis

result = asyncio.run(check_for_similar_models())
# Output:
# Recommendation: edit_existing
# Confidence: 0.90
# 
# Similar existing models found:
#   - pneumothorax (ID: OIFM_MSFT_123456)

Key Features:

  • Intelligent search: Uses AI agents to search with various terms and strategies
  • Duplicate prevention: Identifies if a model already exists for the finding
  • Smart recommendations: Provides guidance on whether to create new or edit existing
  • Synonym matching: Checks both names and synonyms for matches
  • Confidence scoring: Indicates how confident the system is in its recommendation

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

findingmodel-0.3.2.tar.gz (35.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

findingmodel-0.3.2-py3-none-any.whl (44.3 kB view details)

Uploaded Python 3

File details

Details for the file findingmodel-0.3.2.tar.gz.

File metadata

  • Download URL: findingmodel-0.3.2.tar.gz
  • Upload date:
  • Size: 35.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.12

File hashes

Hashes for findingmodel-0.3.2.tar.gz
Algorithm Hash digest
SHA256 12f937affe0ec71eedcdbc304097195ac7c818e8c26f5d4021e8d870b20de2be
MD5 0fd05cfbc625104f76dd6f051711daa3
BLAKE2b-256 657799f50772973d3e30526f5bdf4675454536df15c4fcc1915814df604dd0ec

See more details on using hashes here.

File details

Details for the file findingmodel-0.3.2-py3-none-any.whl.

File metadata

File hashes

Hashes for findingmodel-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 dc96a1e071c3e2e4eb528ba88008fd408c344ab3a24ace19e2fbf2d31dbf9c43
MD5 9cfa2ce72e555d7aca590c0611c657d7
BLAKE2b-256 06e90fca61d4ffb1c53d97ac5ff7abd7b566c7c98c160a12d086ca3c652e3f64

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page