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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12f937affe0ec71eedcdbc304097195ac7c818e8c26f5d4021e8d870b20de2be
|
|
| MD5 |
0fd05cfbc625104f76dd6f051711daa3
|
|
| BLAKE2b-256 |
657799f50772973d3e30526f5bdf4675454536df15c4fcc1915814df604dd0ec
|
File details
Details for the file findingmodel-0.3.2-py3-none-any.whl.
File metadata
- Download URL: findingmodel-0.3.2-py3-none-any.whl
- Upload date:
- Size: 44.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc96a1e071c3e2e4eb528ba88008fd408c344ab3a24ace19e2fbf2d31dbf9c43
|
|
| MD5 |
9cfa2ce72e555d7aca590c0611c657d7
|
|
| BLAKE2b-256 |
06e90fca61d4ffb1c53d97ac5ff7abd7b566c7c98c160a12d086ca3c652e3f64
|