Dimse (Dimse v0.8.7)

Copy Markdown View Source

Pure Elixir DICOM DIMSE networking library.

Provides the DICOM Upper Layer Protocol (PS3.8), DIMSE-C message services (PS3.7 Chapter 9) and DIMSE-N notification/management services (PS3.7 Chapter 10) for building DICOM SCP (server) and SCU (client) applications on the BEAM.

Public API

Listener (SCP)

Dimse.start_listener(port: 11112, handler: MyApp.DicomHandler)
Dimse.stop_listener(ref)

Client (SCU) — DIMSE-C

{:ok, assoc} = Dimse.connect("192.168.1.10", 11112, calling_ae: "MY_SCU", called_ae: "REMOTE_SCP")
:ok = Dimse.echo(assoc)
:ok = Dimse.store(assoc, sop_class_uid, sop_instance_uid, data_set)
{:ok, results} = Dimse.find(assoc, :study, query_data, timeout: 30_000)
:ok = Dimse.cancel(assoc, message_id)
{:ok, result} = Dimse.move(assoc, :study, query, dest_ae: "DEST_AE")
{:ok, data_sets} = Dimse.get(assoc, :study, query)

Client (SCU) — DIMSE-N

{:ok, status, data} = Dimse.n_get(assoc, sop_class_uid, sop_instance_uid)
{:ok, status, data} = Dimse.n_set(assoc, sop_class_uid, sop_instance_uid, modifications)
{:ok, status, data} = Dimse.n_action(assoc, sop_class_uid, sop_instance_uid, action_type_id, action_info)
{:ok, status, created_sop_instance_uid, data} = Dimse.n_create(assoc, sop_class_uid, attributes)
{:ok, status, nil}  = Dimse.n_delete(assoc, sop_class_uid, sop_instance_uid)
{:ok, status, data} = Dimse.n_event_report(assoc, sop_class_uid, sop_instance_uid, event_type_id, event_info)

Connection management

:ok = Dimse.release(assoc)
:ok = Dimse.abort(assoc)

Architecture

Each DICOM association is a dedicated GenServer process (Dimse.Association) supervised under a DynamicSupervisor. Ranch handles TCP acceptance. This gives fault isolation per-association and natural backpressure via max_children.

Dependencies

  • dicom — command set encoding (Implicit VR Little Endian), UID generation, transfer syntax registry, SOP class lookup
  • ranch — TCP acceptor pool
  • telemetry — observability events

Summary

Functions

Sends an A-ABORT to forcefully terminate the association.

Sends a C-CANCEL-RQ to cancel a pending C-FIND operation.

Opens an association to a remote DICOM AE (SCU client).

Sends a C-ECHO request on an established association.

Sends a C-FIND request and returns matching data sets.

Sends a C-GET request to retrieve instances on the same association.

Sends a C-MOVE request to retrieve instances to a destination AE.

Sends an N-ACTION-RQ to request an action on a managed SOP Instance.

Sends an N-CREATE-RQ to create a new managed SOP Instance.

Sends an N-DELETE-RQ to delete a managed SOP Instance.

Sends an N-EVENT-REPORT-RQ to notify the SCP of an event.

Sends an N-GET-RQ to retrieve attributes from a managed SOP Instance.

Sends an N-SET-RQ to modify attributes on a managed SOP Instance.

Sends an A-RELEASE-RQ to gracefully close the association.

Starts a DIMSE listener on the given port.

Stops a running DIMSE listener.

Sends a C-STORE request with the given data set.

Opens an association, executes a function, and releases gracefully.

Functions

abort(assoc)

@spec abort(pid()) :: :ok

Sends an A-ABORT to forcefully terminate the association.

cancel(assoc, message_id)

@spec cancel(pid(), integer()) :: :ok

Sends a C-CANCEL-RQ to cancel a pending C-FIND operation.

The message_id should be the MessageID of the original C-FIND-RQ.

connect(host, port, opts \\ [])

@spec connect(String.t(), pos_integer(), keyword()) :: {:ok, pid()} | {:error, term()}

Opens an association to a remote DICOM AE (SCU client).

Options

  • :calling_ae — local AE title (default: "DIMSE")
  • :called_ae — remote AE title (default: "ANY-SCP")
  • :abstract_syntaxes — list of SOP Class UIDs to propose
  • :transfer_syntaxes — list of Transfer Syntax UIDs to propose
  • :max_pdu_length — max PDU length (default: 16_384)
  • :timeout — total association establishment timeout in ms (default: 30_000)
  • :tls — TLS options (keyword list). When present, the SCU connects via TLS instead of plain TCP. Accepts standard :ssl options such as :cacertfile, :verify, :certfile, and :keyfile.
  • :role_selections — list of Dimse.Pdu.RoleSelection structs proposing SCU/SCP roles per SOP class (PS3.7 Annex D.3.3.4)
  • :user_identity — Dimse.Pdu.UserIdentity struct for SCU authentication (PS3.7 Annex D.3.3.7)

Returns {:ok, association_pid} or {:error, reason}.

echo(assoc, opts \\ [])

@spec echo(
  pid(),
  keyword()
) :: :ok | {:error, term()}

Sends a C-ECHO request on an established association.

find(assoc, sop_class_or_level, query_data, opts \\ [])

@spec find(pid(), String.t() | atom(), binary(), keyword()) ::
  {:ok, [binary()]} | {:error, {:cancelled, [binary()]} | term()}

Sends a C-FIND request and returns matching data sets.

Parameters

  • assoc — association pid from Dimse.connect/3
  • sop_class_or_level — SOP Class UID string, or query level atom (:patient, :study, :worklist)
  • query_data — encoded query identifier data set

Options

  • :priority — request priority (default: 0x0000 medium)
  • :timeout — response timeout in ms (default: 30_000)

get(assoc, sop_class_or_level, query_data, opts \\ [])

@spec get(pid(), String.t() | atom(), binary(), keyword()) ::
  {:ok, [binary()]} | {:error, term()}

Sends a C-GET request to retrieve instances on the same association.

The SCP sends matching instances back as C-STORE sub-operations on the same association. The SCU auto-accepts and returns the data sets.

Parameters

  • assoc — association pid from Dimse.connect/3
  • sop_class_or_level — SOP Class UID string, or query level atom (:patient, :study)
  • query_data — encoded query identifier data set

Options

  • :priority — request priority (default: 0x0000 medium)
  • :timeout — response timeout in ms (default: 30_000)

Returns {:ok, [binary()]} with the received data sets.

move(assoc, sop_class_or_level, query_data, opts \\ [])

@spec move(pid(), String.t() | atom(), binary(), keyword()) ::
  {:ok, map()} | {:error, term()}

Sends a C-MOVE request to retrieve instances to a destination AE.

The SCP pushes matching instances to the destination AE via C-STORE sub-operations and reports sub-operation counts in the response.

Parameters

  • assoc — association pid from Dimse.connect/3
  • sop_class_or_level — SOP Class UID string, or query level atom (:patient, :study)
  • query_data — encoded query identifier data set

Options

  • :dest_ae — destination AE title (required)
  • :priority — request priority (default: 0x0000 medium)
  • :timeout — response timeout in ms (default: 30_000)

Returns {:ok, %{completed: n, failed: n, warning: n}} on success.

n_action(assoc, sop_class_uid, sop_instance_uid, action_type_id, data, opts \\ [])

@spec n_action(pid(), String.t(), String.t(), integer(), binary() | nil, keyword()) ::
  {:ok, integer(), binary() | nil}
  | {:error, {:status, integer(), binary() | nil} | term()}

Sends an N-ACTION-RQ to request an action on a managed SOP Instance.

Returns {:ok, status, data} or {:error, reason}.

n_create(assoc, sop_class_uid, data, opts \\ [])

@spec n_create(pid(), String.t(), binary() | nil, keyword()) ::
  {:ok, integer(), String.t() | nil, binary() | nil}
  | {:error, {:status, integer(), binary() | nil} | term()}

Sends an N-CREATE-RQ to create a new managed SOP Instance.

Options

  • :sop_instance_uid — optional proposed SOP Instance UID
  • :timeout — response timeout in ms (default: 30_000)

Returns {:ok, status, created_sop_instance_uid, data} or {:error, reason}.

n_delete(assoc, sop_class_uid, sop_instance_uid, opts \\ [])

@spec n_delete(pid(), String.t(), String.t(), keyword()) ::
  {:ok, integer(), nil} | {:error, {:status, integer(), nil} | term()}

Sends an N-DELETE-RQ to delete a managed SOP Instance.

Returns {:ok, status, nil} or {:error, reason}.

n_event_report(assoc, sop_class_uid, sop_instance_uid, event_type_id, data, opts \\ [])

@spec n_event_report(
  pid(),
  String.t(),
  String.t(),
  integer(),
  binary() | nil,
  keyword()
) ::
  {:ok, integer(), binary() | nil}
  | {:error, {:status, integer(), binary() | nil} | term()}

Sends an N-EVENT-REPORT-RQ to notify the SCP of an event.

Returns {:ok, status, data} or {:error, reason}.

n_get(assoc, sop_class_uid, sop_instance_uid, opts \\ [])

@spec n_get(pid(), String.t(), String.t(), keyword()) ::
  {:ok, integer(), binary() | nil}
  | {:error, {:status, integer(), binary() | nil} | term()}

Sends an N-GET-RQ to retrieve attributes from a managed SOP Instance.

Options

  • :attribute_identifier_list — list of {group, element} tags to retrieve
  • :timeout — response timeout in ms (default: 30_000)

Returns {:ok, status, data} or {:error, reason}.

n_set(assoc, sop_class_uid, sop_instance_uid, data, opts \\ [])

@spec n_set(pid(), String.t(), String.t(), binary(), keyword()) ::
  {:ok, integer(), binary() | nil}
  | {:error, {:status, integer(), binary() | nil} | term()}

Sends an N-SET-RQ to modify attributes on a managed SOP Instance.

Returns {:ok, status, data} or {:error, reason}.

release(assoc, timeout \\ 30000)

@spec release(pid(), timeout()) :: :ok | {:error, term()}

Sends an A-RELEASE-RQ to gracefully close the association.

start_listener(opts \\ [])

@spec start_listener(keyword()) :: {:ok, term()} | {:error, term()}

Starts a DIMSE listener on the given port.

Options

  • :port — TCP port to listen on (default: 11112)
  • :handler — module implementing Dimse.Handler behaviour (required)
  • :ae_title — local AE title (default: "DIMSE")
  • :max_associations — max concurrent associations (default: 200)
  • :num_acceptors — Ranch acceptor pool size (default: 10)
  • :max_pdu_length — max PDU length in bytes (default: 16_384)
  • :tls — TLS options (keyword list). When present, the listener uses TLS (:ranch_ssl) instead of plain TCP. Accepts standard :ssl options such as :certfile, :keyfile, :cacertfile, :verify, and :fail_if_no_peer_cert. PS3.15 Annex B recommends port 2762 for DICOM-TLS.

Returns {:ok, listener_ref} or {:error, reason}.

stop_listener(ref)

@spec stop_listener(term()) :: :ok | {:error, term()}

Stops a running DIMSE listener.

store(assoc, sop_class_uid, sop_instance_uid, data, opts \\ [])

@spec store(pid(), String.t(), String.t(), binary(), keyword()) ::
  :ok | {:error, term()}

Sends a C-STORE request with the given data set.

Parameters

  • assoc — association pid from Dimse.connect/3
  • sop_class_uid — SOP Class UID of the instance
  • sop_instance_uid — SOP Instance UID of the instance
  • data — encoded data set binary

Options

  • :priority — request priority (default: 0x0000 medium)
  • :move_originator_ae — AE title of the C-MOVE originator
  • :move_originator_message_id — message ID from the C-MOVE request
  • :timeout — response timeout in ms (default: 30_000)

with_connection(host, port, opts, fun)

@spec with_connection(String.t(), pos_integer(), keyword(), (pid() -> term())) ::
  {:ok, term()} | {:error, term()}

Opens an association, executes a function, and releases gracefully.

Handles the full connection lifecycle: connect, execute, release. If the function raises or the association fails, the connection is aborted to ensure cleanup.

Parameters

  • host — remote hostname or IP
  • port — remote port
  • opts — connection options (same as connect/3)
  • fun — function receiving the association pid

Examples

Dimse.with_connection("192.168.1.10", 11112, [calling_ae: "SCU"], fn assoc ->
  :ok = Dimse.echo(assoc)
  {:ok, results} = Dimse.find(assoc, :study, query)
  results
end)

Returns {:ok, fun_result} or {:error, reason}.