This is part of my HL7 article series. In this tutorial, we will look at how binary data is handled during HL7 message processing. Because HL7 2.x message data is transmitted as text, handling binary data requires additional customization. We will look at some ways in which this can be handled programmatically. Before we get started on this tutorial, have a quick look at my earlier article titled “A Very Short Introduction to the HL7 2.x Standard”. If you want to understand the "nuts and bolts" of the standard from a programming perspective, it might also be useful for you to look at my tutorial titled "HL7 Programming using Java". For real-life situations, you will typically employ a library such as "HAPI" to handle your HL7 message processing requirements within your custom Java applications. My previous tutorials on using HAPI illustrated how to utilize this library to create, send, receive, extend as well as parse HL7 messages. In this tutorial, we will use Java and the HAPI library to process binary data for handling non-textual clinical data such as reports, waveform information, etc often required during a number of clinical workflows. Let us get started.
Orders and Observation Reporting in HL7 - Quick Overview
The HL7 2.x standard provides a comprehensive set of message types and triggers to support the process of requesting and receiving order-related messages. An example of an order in a clinical setting is when a doctor requests a pathology lab to analyze a tissue sample and send back a report on a particular patient. Order messages sometimes may not be about a patient at all, and instead may simply be a request from one department to another to request medical supplies. These type of workflows are typically achieved by two or more HL7 interfaces taking on various "application roles" as they are known in the HL7 standard. These application roles include "order placer", "order filler", etc, indicating who is requesting/placing the order and who is actually fulfilling a specific order. The HL7 interfaces involved achieve the overall result by transmitting and responding to a variety of "solicited" and "unsolicited" messages as part of the role play that was described earlier. A "solicited" message is one that is typically received by a HL7 system in response to a query for information that it initiated. On the other hand, an "unsolicited" message is one that is broadcast by a HL7 system to other HL7 systems without the message being explicitly solicited (or asked for) by those receiving systems. In these situations, the receiving systems are typically in a "standby mode" to receive such messages from the senders of these messages. The receiving system may then respond back in one of two ways. It may either respond back with an acknowledgment message (indicating acceptance of these messages) and be done with this communication entirely, or sometimes respond with more than one message where the first message will be acknowledgment of the reception of the message which may then be followed by one or more unsolicited messages which contains additional information such as a when new observations need to reported as they emerge. Please also note that the orders-related workflow can be sometimes be extremely complex and may require many back and forth interactions using HL7 message to achieve the desired result. For instance, scenarios such as changing, or for canceling the original order, etc. are also required in the workflow as well. I will let you explore those workflows on your own. I put a diagram together below that shows an example of message flow during order processing.
“Poverty is a very complicated issue, but feeding a child isn’t.” ~ Jeff Bridges
Now that we understand a little bit about orders and order responses, let us quickly review the specific HL7 message types and triggers involved. In HL7, the order messages (ORM) are used to signal order requests by the "placer system" to the "filler system". The filler systems may then respond to the placer with observation-related messages (ORU) in either solicited or unsolicited modes. In the solicited mode of response, the filler system merely responds back with any existing observations that match the criteria specified in the original message. However, the receiving systems may also respond back with unsolicited messages when new observations are generated. The observation messages consist of a number of message segments such as MSH, PID, PV1, OBR, OBX, etc. Since we have looked at MSH, PID, PV1 segments already and we know what they are used for, we can focus our attention on the OBR and the OBX segments as they enable the mechanisms through which many of the complex data such as PDF reports, images and waveforms are transmitted as part of the order/observation reporting workflow. The OBR segment serves as a kind of report header. The main purpose of the OBR segment is to help identify the relevant observation set which comprises of unique observations (zero or more) noted during a particular procedure. The attributes or field data of the OBR segment therefore apply to all of the included observation segments (OBX) that follow. The OBR segment may also help confirm the status and completion of the order itself (note that certain domains may use a different and more specialized segment instead of the OBR segment). The OBX segments that follow the OBR segment are used to transmit the unique/individual observations that were captured reported during the procedure that was performed. The OBX segments sometimes may be followed by NTE segments that carry notes and other comments about these observations. OBX segments are sometimes required to carry data such as images, PDF documents, waveforms, etc in addition to textual data. We will spend the rest of the tutorial on how this is achieved.
Tackling Binary Data Transmission using Base-64 Encoding
In my earlier tutorials in this HL7 article series, you will recall having looked at some of the functionality provided by HAPI parsers. I had mentioned there that HAPI parsers assist in the process of encoding a message using which we can convert a message object to pipe delimited or sometimes XML format for transmission over the wire, or for storage to disk or a database. In computer science, an encoder is something that converts some piece of information simply from one format to another. The encoder may take a variety of shapes and forms. It may refer to a piece of software, hardware or may refer to just an algorithm used within a specific software. The encoding itself is performed for many reasons such as for standardization, for compression, or to increase speed of delivery of information, etc. The reverse process of encoding is known as decoding and refers to the process of converting the encoded information back to the original form which may sometimes be done either on a remote system where the information was transmitted to, or even locally (think of what happens when you persist information to a local file or a database for example).
Users of any modern operating system such as Linux, Windows or Mac OSX use literally hundreds of encoders in their day to day operations without even being aware of it. Operations on file data alone require the assistance of numerous encoders/decoders behind the scenes. These are needed for the purposes of storing as well as retrieving data such as text files, a variety of binary data files such as Word and PDF documents as well as multimedia files such as images, audio files as well as videos. Sometimes, these encoders are used ingeniously to overcome limitations in some legacy platforms which don't support a certain binary format by taking binary data and converting them to textual form to enable transmission or storage. Base64 is an example of one such encoding mechanism which assists in representing binary data in an ASCII string format by translating it into a radix-64 representation. It is very popular, and is used in many day to day scenarios such as when sending attachments through email, sending binary data through web requests or responses, and also to store complex data in XML format, etc. In HL7, this encoding mechanism is often utilized in order to transmit binary data such as reports, waveforms, images, etc. often needed during order response processing which I touched on earlier.
In ORU messages, binary data often needs to carried in the OBX segment to communicate observation-related information. Base64 comes to our rescue by enabling the encoded data to be carried in the 5th field of an OBX segment (Observation Value). If the data in this field were not encoded correctly, this could cause problems with the overall parsing mechanisms since collisions may arise as a result of potential conflicts with HL7 reserved characters ("|^~\&") which are employed to demarcate segments, fields, components, etc in the HL7 2.x standard. Therefore, data in the binary files are Base64-encoded and then placed directly into the OBX-5 field, and the entire message is then transmitted to the receiving system which then needs to reverse this entire process in order to assemble the transmitted message and its binary payload in its entirety. Let us proceed to look at how a pathology report can be transmitted as part of a ORU order response. Please note that sometimes large binary payloads are split into multiple parts and sent in separate OBX segments. The rules for such customizations are often left entirely to the sites to decide on as there is often more than one way to handle such scenarios in HL7.
Tools and Resources Needed
- JDK 1.4 SDK or higher
- Apache Commons Codec for Base-64 encoding support (note: Java 8 has built-in support for this)
- Eclipse or any other Java IDE (or even a text editor)
- Download HAPI compiled library from the HAPI project website
- View HAPI source code on their GitHub site here
- Download HAPI Test Panel (optional) from here
- You can also find all the code demonstrated in the tutorial on GitHub here
Example of Sending a Pathology Report in HL7
Before I demonstrate the overall functionality, I will create a small helper class called OurBase64Helper to assist with Base-64 encoding and decoding functionality required during the HL7 message processing. Separating out behaviour like this will enable you to write automated tests, add logging, and troubleshoot problems much more easily. This small class shown here provides wrapper methods around the pre-built .NET functionality to take in input data in the form of a binary file (or simply a string of data) and can either convert it into Base64 format or decode it back to the original format. Each implementation is unique, and you may not require such a helper class as you can easily call the Base64 classes to achieve the desired behavior.
A custom exception class can be used to signal Base64 encoding or decoding errors like this, or you can simply use the IllegalArgumentException class that comes with Java. See the use of the BadBase64EncodingException class in the helper class shown above. Also, note the check using divide by 4. This is to prevent an expensive conversion operation if it can be caught and prevented as early as possible if the transmitted content were not encoded correctly. Some libraries have built-in support to check if a certain byte content is Base-64 encoded. I will let you explore this on your own.
Now that we have our helper class, let us look at how to construct our order response message (ORU R01) and include the contents of a pathology report in PDF format into an OBX segment that is going to be included in the message. The code illustration below shows a small console program class that uses a message factory to create a ORU R01 message which is then transmitted to a remote system using MLLP over TCP/IP on Port 57550 here. I have already covered the basics of sending HL7 messages in a previous tutorial "HL7 Programming using Java and HAPI - Sending HL7 Messages". So, refer to that article if you want to understand more on how to transmit messages to a remote system using the HAPI HL7 library.
“Poetry is a form of mathematics, a highly rigorous relationship with words.” ~ Tahar Ben Jelloun
The console program utilizes a message factory called OruMessageFactory shown below to help build the ORU R01 message. This factory class in turn delegates the work of actual message construction to a builder class called OurOruR01MessageBuilder which is shown beneath. Separating the delegation of message construction is very useful when it comes to HL7 message processing particularly when dealing with messages with a large number of message segments. However, I will leave it to you to chose the approach that best fits your needs.
To be able to run this example, I used the HAPI Test Panel software to emulate a remote HL7 system by configuring a listener on the specified port (port 57550 in our example). Our HL7 client was able to successfully create a ORU R01 order response message and transmit this message to the listener. It also received an acknowledgment message from the listener which is then output to the console. I have truncated some of the Base-64 encoded data transmitted in the OBX-5 field for easier illustration here. However, you will notice a much longer message on your machine when you run this program locally.
“Live as if you were to die tomorrow. Learn as if you were to live forever.” ~ Mahatma Gandhi
Example of Receiving a Pathology Report in HL7
Now that we have looked at how to send binary data using a ORU R01 HL7 message, let us look at how to receive and extract the binary data from the transmitted data. This part is relatively easy to do as you simply need to reverse the data processing logic that was done previously. We simply extract the OBX-5 data and run it through the Base-64 decoder and store our PDF file to disk. Sometimes, the name of the PDF file is also transmitted along in the ORU R01 message to enable the receiving system to extract the binary data and save the contents to an output file with same name as specified in the HL7 message.
The program console output from running our program is shown below. The OBX-5 data here has been truncated for display purposes here. It will be lengthier when you run this locally on your machine.
Screen capture above shows the extracted pathology report in PDF format that we had previously encoded into the ORU R01 HL7 message. The entire PDF was successfully reproduced even though it was transformed into textual form during an intermediate step for transmission in a HL7 message using the Base-64 encoding mechanism. You can apply the same process to transmit images, audio or video files. Sometimes, when the input files are very large, the input data is broken into smaller chunks which are then transmitted in multiple OBX segments. Feel free to explore this approach on your own as this is proving to be a lengthy tutorial already.
Please be advised that there is a separate category of HL7 messages called "Medical Document Management (MDM)". These message types are specifically tailored for transmitting information regarding medical documents. The use of these message types is beyond the scope of this tutorial, but I still wanted to bring this to your attention. Also, when needing to transfer binary data, some sites use an approach where a hyperlink of the document is transmitted in the observation value field, and the receiving system uses this link to download the relevant document using that link. Waveform information is another type of information that is communicated in HL7, and it is sometimes transmittted as XML. Because you cannot transfer XML within HL7 2.x, you will want to consider using Base-64 encoding to solve that problem*. HL7 2.x is a flexible standard and permits multiple ways to achieve the same goal. Ultimately, it is left entirely upto to the implementing sites to decide which approach works best for them. Please consult your local HL7 group or an HL7 expert for additional help or assistance on this topic.
That concludes my tutorial on how to handle transmission of binary data within HL7 systems. I used a fictional example involving sending and receiving a pathology report in PDF form for my code examples shown in this tutorial. However, the same approach can be extended to transmit other binary files including images, audio and video files, etc. In the next tutorial in my HL7 article series, I am going to be heading back to finish my .NET articles on HL7 using NHAPI (a .NET port of HAPI) to look at how to parse HL7 messages using that framework. See you then!
* - HL7 3.0 supports XML natively and so you should be able to send XML payload as part of the message payload using CDATA to wrap your waveform content. Please refer to official HL7 documentation for more information