Rechercher

Article
· Avr 10, 2023 9m de lecture

Sending DICOM files between IRIS for Health and PACS software

Welcome community members to a new article! this time we are going to test the interoperability capabilities of IRIS for Health to work with DICOM files.

Let's go to configure a short workshop using Docker. You'll find at the end of the article the URL to access to GitHub if you want to make it run in your own computer.

Previously to any configuration we are going to explain what is DICOM:

  • DICOM is the acronym of Digital Imaging and Communication in Medicine and it's a images and medic data transmission standard. In this protocol is included the format of the DICOM file and the communication protocol based on TCP/IP.
  • DICOM files support images and clinical documentation (you can include in a DICOM file images or documents "dicomized" as images).
  • DICOM protocol define services/operations for the DICOM files. You can request the storages of an image (C-STORE), execute queries (C-FIND) o move this images among the systems of the medical organizations (C-MOVE). You can review all these available services from this URL .
  • All the systems involved in a DICOM based communication request a DICOM message as response.

You can see here a typical example of the architecture for a system designed to work with DICOM:

General scheme of DICOM Network architecture

We have some "modalities" (these modalities could be machines as scanners, MRIs or just the software that will store it) identified by the AE Title o AET (Application Entity Title). This AET will be unique for each modality and must be configured in those other modalities or systems that are going to communicate with it, in such a way that communication between both modalities is allowed.

As you can see in the graph, the modalities are configured to store their images in a DICOM file server that may or may not belong to a PACS (Picture Archiving and Communication System) that is later consulted from a PACS web interface. It is increasingly common to include a VNA (Vendor Neutral Archive) system in organizations that is responsible for centralized storage and viewing of all DICOM files used by the organization.

In general, in the most modern modalities, the destination of the generated images can be configured, but on many occasions it may be necessary or to carry out some type of action on the DICOM image fields (modify the patient identifier, include the clinical episode to the one with which it is related, etc) or, due to the inability of the modality, to take charge of capturing and forwarding the generated image to the system responsible for archiving. It is in these cases that the existence of an integration engine that provides us with such functionality is necessary, and there is none better than IRIS for Health!

For our example we will consider the following scenario:

  • A certain modality is generating images that need to be sent to a PACS for registration.
  • Our DICOM or PACS server will receive these images and must forward them to a specific VNA.


To simulate our PACS we will use Orthanc, an open source tool that will provide us with the basic functionalities for archiving and viewing DICOM images (more information here). Orthanc is kind enough to provide us with its use through an image that we can mount in Docker without any complications. Finally we will deploy an IRIS for Health container (it depends on when you read this article, the license may have expired, in that case you just have to update the docker-compose file of the code) in which we can mount our production.

Let's take a look at the docker-compose we've configured:

version: '3.1'  # Secrets are only available since this version of Docker Compose
services:
  orthanc:
    image: jodogne/orthanc-plugins:1.11.0
    command: /run/secrets/  # Path to the configuration files (stored as secrets)
    ports:
      - 4242:4242
      - 8042:8042
    secrets:
      - orthanc.json
    environment:
      - ORTHANC_NAME=orthanc
    volumes:
      - /tmp/orthanc-db/:/var/lib/orthanc/db/
    hostname: orthanc
  iris:
    container_name: iris
    build:
      context: .
      dockerfile: iris/Dockerfile
    ports:
    - "52773:52773"
    - "2010:2010"
    - "23:2323"
    - "1972:1972"
    volumes:
    - ./shared:/shared
    command:
      --check-caps false
    hostname: iris
secrets:
  orthanc.json:
    file: orthanc.json

Access to the Orthanc web viewer will be done through port 8042 (http://localhost:8042), the IP destined to receive images via TCP/IP will be 4242 and its configuration will be done from the orthanc.json file. The management portal of our IRIS for Health will be 52773.

Let's see what orthanc.json contains:

{
    "Name" : "${ORTHANC_NAME} in Docker Compose",
    "RemoteAccessAllowed" : true,
    "AuthenticationEnabled": true,
    "RegisteredUsers": {
        "demo": "demo-pwd"
    },
    "DicomAssociationCloseDelay": 0,
    "DicomModalities" : {
        "iris" : [ "IRIS", "host.docker.internal", 2010 ]
      }
}

 

As you can see we have defined a demo user with a password demo-pwd and we have declared a mode called IRIS that will use port 2010 to receive images from Orthanc, "host.docker.internal" is the mask used by Docker to access other deployed containers.

Let's check that after running the docker-compose build and docker-compose up -d we can access our IRIS for Health and Orthanc without problems:

IRIS for Health is successfully deployed.

Orthanc works too, so come on, get messy!

Let's access the namespace called DICOM and open its production. We can see in it the following business components:

We are going to review just the necessary components to manage the first case that we have presented for now. A modality that generates DICOM images but from which we cannot send them to our PACS. To do this we will use a Business Service of the standard class EnsLib.DICOM.Service.File configured to read all the .dcm files stored in the /shared/durable/in/ directory and send them to the Business Process of the Workshop.DICOM.Production.StorageFile class.

Let's take a closer look at the main method of this Business Process:

/// Messages received here are instances of EnsLib.DICOM.Document sent to this
/// process by the service or operation config items. In this demo, the process is ever
/// in one of two states, the Operation is connected or not.
Method OnMessage(pSourceConfigName As %String, pInput As %Library.Persistent) As %Status
{
    #dim tSC As %Status = $$$OK
    #dim tMsgType As %String
    do {
        
        If pInput.%Extends("Ens.AlarmResponse") {
            
            #; We are retrying, simulate 1st call
            #; Make sure we have a document
            Set pInput=..DocumentFromService
            $$$ASSERT(..CurrentState="OperationNotConnected")
        }
            
        #; If its a document sent from the service
        If pSourceConfigName'=..OperationDuplexName {
            
            #; If the operation has not been connected yet
            If ..CurrentState="OperationNotConnected" {
                
                #; We need to establish a connection to the operation,
                #; Keep hold of the incoming document
                Set ..DocumentFromService=pInput
                
                #; We will be called back at OnAssociationEstablished()
                Set tSC=..EstablishAssociation(..OperationDuplexName)
                
            } elseif ..CurrentState="OperationConnected" {
                
                #; The Operation is connected
                #; Get the CommandField, it contains the type of request, it should ALWAYS be present
                Set tMsgType=$$$MsgTyp2Str(pInput.GetValueAt("CommandSet.CommandField",,.tSC))
                If $$$ISERR(tSC) Quit
                #; We are only handling storage requests at present
                $$$ASSERT(tMsgType="C-STORE-RQ")
        		
        		// set patientId = pInput.GetValueAt("DataSet.PatientID",,.tSC)
        		// Set ^PatientImageReceived(patientId) = pInput.GetValueAt("DataSet.PatientName",,.tSC)
                #; We can forward the document to the operation
                Set tSC=..SendRequestAsync(..OperationDuplexName,pInput,0)
            }
            
        } elseif pSourceConfigName=..OperationDuplexName {
            
            #; We have received a document from the operation
            Set tMsgType=$$$MsgTyp2Str(pInput.GetValueAt("CommandSet.CommandField",,.tSC))
            If $$$ISERR(tSC) Quit
            #; Should only EVER get a C-STORE-RSP
            $$$ASSERT(tMsgType="C-STORE-RSP")

            #; Now close the Association with the operation, we will be called back at
            #; OnAssociationReleased()
            Set tSC=..ReleaseAssociation(..OperationDuplexName)
            
            #; Finished with this document
            Set ..DocumentFromService="",..OriginatingMessageID=""
        }
    } while (0)
    
    Quit tSC
}

As we can see, this class is configured to check the origin of the DICOM file, if it does not come from the Business Operation defined in the OperationDuplexName parameter, it will mean that we must forward it to the PACS and therefore the metadata of the DICOM message located in the CommandSet section under the name CommandField shall be of type C-STORE-RQ (store request) prior to connection establishment. In this URL you can check the different values ​​that this metadata can take (in hexadecimal).

In the case that the message comes from the indicated Business Operation, it is a sign that it corresponds to a DICOM response message to our previously sent DICOM, therefore it is validating that the CommandField of said message is of type C-STORE-RSP.

Let's analyze a little more in detail the key configuration of the Business Operation EnsLib.DICOM.Operation.TCP used to send our DICOM to our PACS via TCP/IP:

We have declared as IP the name of the hostname specified in the docker-compose in which Orthanc is deployed, as well as the port.

We have configured two key elements for sending to PACS: the AET of our IRIS for Health (IRIS) and the AET of our PACS (ORTHANC). Without this configuration, no image sending is possible, as both IRIS and ORTHANC will validate that the sending/receiving modality has permission to do so.

Where do we configure which modalities can send images from IRIS and which modalities can send images to us? It's very simple: we have access to the DICOM configuration functionality from the IRIS management portal:

From this menu we can not only indicate which modalities can send us and to which we can send DICOM images, we can also indicate what type of images we will be able to send and receive, in such a way that we can reject any image that falls outside of this parameterization. As you can see in the image above we have configured connections both from IRIS to Orthanc and from Orthanc to IRIS. By default Orthanc supports any type of image, so we don't need to modify anything in its configuration.

In order not to have problems with the images that we can send and receive from IRIS, we will configure the "Presentation Context" call, made up of "Abstract Syntax" made up of the combination of DICOM services (Store, Get, Find...) and an object (MR images , CT, etc...) and the "Transfer Syntax" that defines how information is exchanged and how data is represented.

Well, we already have configured any possible connection between IRIS and Orthanc and vice versa. Let's proceed to launch a test including a DICOM file in the path defined in our Business Service:


Very good! Here we have registered our DICOM files and we can see how they have gone through our production until they are sent to Orthanc. Let's go into more detail by checking out a message.

Here we have our message with its CommandField set to 1, corresponding to C-STORE-RQ, now let's review the response we received from Orthanc:

We can see that the value of CommandFile 32769 corresponds in hexadecimal to 8001, which, as we have seen in this URL, is equivalent to type C-STORE-RSP. We can also see that the response message is a DICOM message that only contains the values ​​defined in the Command Set.

Let's check from Orthanc that we have received the messages correctly:

Here are our messages successfully archived in our PACS. Goal achieved! We can now store the DICOM images of our modality in our PACS without any problem.

In the next article we will deal with the opposite direction of communication, sending from the PACS to our modality configured in IRIS.

Here you have available the code used for this article: https://github.com/intersystems-ib/workshop-dicom-orthanc

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer
Article
· Avr 7, 2023 2m de lecture

Autoscaling IRIS Workloads. My adventure with IKO, HPA, and Traffic Cop

This week I was able to demo a proof of concept for our FMS interface on traffic cop architecture to my team. We are working on modernizing an Interoperability production running on mirrored Health Connect instances. We deploy IRIS workloads on Red Hat OpenShift Container Platform using InterSystems Kubernetes Operator (IKO). We can define any number of replicas for the compute stateful set where each compute pod runs our Interoperability production. We introduced Horizontal Pod Autoscaler (HPA) to scale up the number of compute pods based on memory or CPU utilization. But IKO scaled down because it wanted to keep the defined replicas. When compute pods receive shutdown signal while they are busy, messages in queues do not get processed right away. 
We are transitioning to "Traffic Cop" architecture to enable us to autoscale our workloads. Instead of deploying interoperability production on multiple compute pods, we deploy it on a mirrored data pod which functions as a traffic cop. We will create more REST interfaces where the message processing happens on stateless compute pods which can be deployed without IKO and no interoperability production will be on stateless computes. 
Compute and webgateway containers run as sidecar containers in one pod where webgateway receives requests to be processed in its paired compute. 
Along the way I have created a REST service running on our stateless compute pods which was started with a Swagger API specification. InterSystems IRIS API Management tools generated the code for the REST interface instead of manually coding it.

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer
Annonce
· Mars 24, 2023

[Video] Git Source Control for InterSystems IRIS Interoperability with Docker and VSCode

Hi Developers,

Often we create and edit InterSystems IRIS Interoperability solutions via a set of UI tools that is provided with IRIS. But it is sometimes difficult to setup the development environment to handle changes we make in the UI to source control.  

This video illustrates how git-source-control helps with source control Interoperability components while changing it in the UI.

⏯ Git Source Control for InterSystems IRIS Interoperability with Docker and VSCode

Add these two lines in your iris.script during docker build:

zpm "install git-source-control"
do ##class(%Studio.SourceControl.Interface).SourceControlClassSet("SourceControl.Git.Extension")

And Interoperability UI components will start working with git. 

Example application.

Discussion (0)2
Connectez-vous ou inscrivez-vous pour continuer
Veuillez noter que cette publication est obsolète.
Annonce
· Mars 20, 2023

Sneak Peek (at least for this week)

 Hi Community:

As you may know, I am the person who does the program and scheduling for our Global Summit sessions, so I can give you a sneak peek at our agenda before it is published.

I'm happy to report that we will have two special, four-hour workshops on Sunday, June 4, which is the day you arrive to register in Hollywood, Florida.

One is the return of the standing-room only 2022 workshop with our FHIR guru, Russ Leftwich - "Getting to Know FHIR: The Best Explanation of FHIR They've Ever Heard." Needless to say, we've reserved a bigger room.

Plus, we are planning eight related breakout sessions:

  1. "FHIR to IntegratedML: Can You Get There from Here?'
  2. "Building a FHIR Façade" 
  3. "How to Customize Your FHIR Server"
  4. "Clinical Alerts and Notifications on FHIR: Putting the Healthcare Action Engine into the Workflow"
  5. "Performing Advanced FHIR Validation"
  6. "Clinical Research Data Pipeline using FHIR and OMOP"
  7. "FHIR in the Cloud"
  8. "Introducing Bulk FHIR Export"

Our second workshop in an introduction to containers -- "Working with Containers and InterSystems Technology" - led by Derek Robinson from our online learning team. It will prepare you for these two related sessions

  1. "The Container Lifecycle: When is Your App Ready to Accept Work?"
  2. "Can you Autoscale This?  Lessons from the Field on Kubernetes"

Register TODAY for Global Summit 2023 to take advantage of Early Bird Prices. (You will be able to register for sessions, including the workshops, soon.)

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer
Article
· Mars 16, 2023 6m de lecture

Kinds of properties in IRIS

InterSystems IRIS has quite a few different kinds properties. Let’s put them in order so that they make better sense.

First of all, I would divide them into categories:

  • Atomic or simple properties (all those %String, %Integer, %Data and other system or user datatypes)
  • References to stored objects
  • Built-in objects
  • Streams (both binary and character)
  • Collections (which are divided into arrays and lists)
  • Relationships (one-many and parent-children)

Some of these kinds of properties are quite straightforward. For example, atomic properties:

Property Name As %Name;
Property DoB As %Date
Property Age As %Integer

They are also easily created using Studio Wizard:

The concepts of references to stored objects and built-in objects are also quite easy to grasp. Basically, if the class of an object you’re trying to use as a property extends %Persistent, then it’s a reference to a stored object. If the related class extends %SerialObject – then it’s a built-in object, because such objects can’t be stored themselves, only inside other objects. And in a class definition they look exactly the same:

Property Human as Sample.Human;

To create this kind of property, on the second step of the Wizard in Studio enter the name of the class:

Streams are also quite easy to explain. You have a big chunk of unstructured data, be it binary or character, and here is your stream. Can be anything – audio, video, document, text etc. Depending on what type of data you want to store, you may choose to use Binary or Character Stream. Another defining characteristic of a stream is the place where you want to store it – on disk (takes less space but accessible from your OS unless you set up access rules for the folder) or in the database (secure, no unauthorized access, takes more space). To work with streams, use classes from package %Stream. For example:

Property Log As %Stream.GlobalCharacter;
Property Instruction As %Stream.FileCharacter(LOCATION = "D:\Temp");
Property Doc As %Stream.FileBinary;
Property Audio As %Stream.GlobalBinary;

In this case input the classname of the stream you wish to use:

Then there are two types of collections:

  • Lists – a collection where each element is defined by its position in the collection
Property FavoriteColors as list of %String;
  • Arrays – key-value pairs, where value is defined by its key that is set by a user
Property Contacts as array of %String;

Both collections can be of simple types or of objects. And when we’re talking about a collection of objects, the objects inside collection aren’t “aware” that they are inside any collection. Meaning that it’s a one-way link.

When working with arrays, it’s necessary to remember that both key and value should be new and useful piece of info. There is no point of making an integer key that will imitate a position in a list. In the example above, key of the Contacts array is a name of the type of the contact, e.g. “Facebook”, “LinkedIn”, “Twitter” etc and the value is a link to a personal page.

Under the hood, when working with a list, you’re working with the class %Collection.ListOfDT for a list of simple types, %Collection.ListOfObj when working with a list of objects and %Collection.ListOfStream when dealing with a list of streams.

The same is true for arrays. %Collection.ArrayOfDT is used when working with an array of simple datatypes, %Collection.ArrayOfObj – when working with an array of objects and %Collection.ArrayOfStream – when working with an array of streams.

For the collection on the second step of the Wizard in Studio choose the second option “The collection of type” 

and then specify the type:

And probably the most challenging kind of the property for people who switch from relational databases – relationship. The relationship is a two-way one-to-many link between two stored objects. In relational DBMS people are taught that they need an additional table to store the foreign keys to two other tables to realize one-to-many link of independent entities. For example:

table Invoice – table Product – table Invoice_Product

There is no need for an additional table/class to do this in IRIS. If there’s no need to often query the information about all the invoices in which the exact Product is listed you can make products as an array or list in an invoice. In this case you will have to manually ascertain logical integrity so that your invoice doesn’t reference products that are no longer in a DB.

To automatically check for the logical integrity of data in this case you can use a relationship between these two classes: Invoice, Product. There are two types of relationships:

  • Parent-children – it’s a dependent link between objects of two different persistent classes. It means that the dependent object can’t exist without the main object. For example, let’s say that a chapter in a book can’t exist without a book. This will be an example of parent-children relationship, where a book is a main object and chapter is a dependent object and if we delete a book all the chapters from this book will be deleted as well.
  • One-many – it’s an independent link between objects of one or two persistent classes. It means that both objects can exist and make sense one without the other and if you try to delete the aggregating object, you’ll get an error saying that you first have to unlink all the linked objects. If you try to delete the linked objects, they will disappear from the aggregating object. Let’s take our invoice and products as an example. We have many products in an invoice. If we try to delete an invoice, we first need to delete all the products from our invoice. If we delete a product, it will disappear from the invoice.

Since it’s a two-way link you need to define the properties in both classes. For example in a class Sample.Invoice you will have the definition:

Relationship Products As Sample.Product [ Cardinality = many, Inverse = Invoice ];


Note that the property is called Relationship and that it has two characteristics:

Cardinality = manymeaning that there are links to many objects inside this property

Inverse = Invoicethis is the name of the property on the other side of the relationship

At the same time in the other class (or it can be in the same class for one-many relationship) there should be the mirror property:

Relationship Invoice As Sample.Invoice [ Cardinality = one, Inverse = Products ];

Here cardinality “one” means that in this property there is a link only to one object.

To create a relationship using Wizard in Studio just choose on the second step Relationship:

Then choose the correct cardinality for the property in the class and fill in the name of the related class and property in it:

For the example with the book with chapters the properties would look as follows.

In a class Sample.Book:

Relationship Chapters As Sample.Chapter [ Cardinality = children, Inverse = Book ];

In a class Sample.Chapter:

Relationship Book As Sample.Book [ Cardinality = parent, Inverse = Chapters ];

This is the end of a brief overview of different kinds of properties present in IRIS. Hope it makes it clearer.

4 Comments
Discussion (4)2
Connectez-vous ou inscrivez-vous pour continuer