Current section
Files
Jump to
Current section
Files
lib/dicom/sr/sub_templates/echocardiography.ex
defmodule Dicom.SR.SubTemplates.Echocardiography do
@moduledoc """
Sub-templates for Echocardiography reports (TID 5201-5240).
Covers:
- TID 5201 Patient Characteristics
- TID 5202 Echo Section
- TID 5203 Echo Measurement
- TID 5204 Wall Motion Analysis
- TID 5240 Myocardial Strain Analysis
"""
alias Dicom.SR.{Code, Codes, ContentItem, Measurement}
@doc """
TID 5201 -- Patient Characteristics.
Returns a CONTAINER with body surface area, weight, and height measurements.
Options:
- `:measurements` (required) -- list of Measurement.t() (BSA, weight, height)
"""
@spec patient_characteristics(keyword()) :: ContentItem.t()
def patient_characteristics(opts) when is_list(opts) do
measurements = Keyword.fetch!(opts, :measurements)
children = Enum.map(measurements, &Measurement.to_content_item/1)
ContentItem.container(Codes.patient_characteristics(),
relationship_type: "CONTAINS",
children: children
)
end
@doc """
TID 5202 -- Echo Section.
Returns a CONTAINER grouping echo measurements for a specific cardiac
structure (e.g. left ventricle, aortic valve).
Options:
- `:structure` (required) -- Code.t() identifying the cardiac structure
- `:measurements` (optional) -- list of ContentItem.t() from `echo_measurement/1`
- `:findings` (optional) -- list of String.t() or Code.t()
"""
@spec echo_section(keyword()) :: ContentItem.t()
def echo_section(opts) when is_list(opts) do
structure = Keyword.fetch!(opts, :structure)
children =
[
ContentItem.code(Codes.finding_site(), structure, relationship_type: "HAS CONCEPT MOD")
]
|> append_items(Keyword.get(opts, :measurements, []))
|> append_findings(Keyword.get(opts, :findings, []))
ContentItem.container(Codes.echo_section(),
relationship_type: "CONTAINS",
children: children
)
end
@doc """
TID 5203 -- Echo Measurement.
Returns a NUM content item for a single echo measurement.
This is a thin wrapper that delegates to `Measurement.to_content_item/1`.
"""
@spec echo_measurement(Measurement.t()) :: ContentItem.t()
def echo_measurement(%Measurement{} = measurement) do
Measurement.to_content_item(measurement)
end
@doc """
TID 5204 -- Wall Motion Analysis.
Returns a CONTAINER with wall motion scores and regional assessments.
Options:
- `:wall_motion_score` (optional) -- Measurement.t() for the overall score
- `:wall_motion_score_index` (optional) -- Measurement.t() for WMSI
- `:regional_assessments` (optional) -- list of ContentItem.t() (code items for wall segments)
"""
@spec wall_motion_analysis(keyword()) :: ContentItem.t()
def wall_motion_analysis(opts) when is_list(opts) do
children =
[]
|> maybe_add_measurement(opts[:wall_motion_score])
|> maybe_add_measurement(opts[:wall_motion_score_index])
|> append_items(Keyword.get(opts, :regional_assessments, []))
ContentItem.container(Codes.wall_motion_score(),
relationship_type: "CONTAINS",
children: children
)
end
@doc """
Builds a regional wall motion assessment for a specific segment.
Returns a CODE content item mapping a segment to a motion category
(normal, hypokinesis, akinesis, dyskinesis).
"""
@spec regional_wall_motion(Code.t(), Code.t()) :: ContentItem.t()
def regional_wall_motion(%Code{} = segment, %Code{} = motion_category) do
ContentItem.code(Codes.wall_motion_abnormality(), motion_category,
relationship_type: "CONTAINS",
children: [
ContentItem.code(Codes.finding_site(), segment, relationship_type: "HAS CONCEPT MOD")
]
)
end
@doc """
TID 5240 -- Myocardial Strain Analysis.
Returns a CONTAINER with global and optional regional strain measurements.
Options:
- `:global_strain` (required) -- Measurement.t() for GLS
- `:regional_strains` (optional) -- list of Measurement.t() for regional segments
"""
@spec myocardial_strain_analysis(keyword()) :: ContentItem.t()
def myocardial_strain_analysis(opts) when is_list(opts) do
global_strain = Keyword.fetch!(opts, :global_strain)
children =
[Measurement.to_content_item(global_strain)]
|> append_measurements(Keyword.get(opts, :regional_strains, []))
ContentItem.container(Codes.global_longitudinal_strain(),
relationship_type: "CONTAINS",
children: children
)
end
# -- Private Helpers --
defp append_items(items, more), do: items ++ more
defp append_findings(items, findings) do
items ++
Enum.map(findings, fn
%Code{} = code ->
ContentItem.code(Codes.finding(), code, relationship_type: "CONTAINS")
text when is_binary(text) ->
ContentItem.text(Codes.finding(), text, relationship_type: "CONTAINS")
end)
end
defp maybe_add_measurement(items, nil), do: items
defp maybe_add_measurement(items, %Measurement{} = m) do
items ++ [Measurement.to_content_item(m)]
end
defp append_measurements(items, measurement_list) do
items ++ Enum.map(measurement_list, &Measurement.to_content_item/1)
end
end