HL7 Programming using .NET and NHAPI - Handling Binary Data

This is part of my HL7 article series. In this tutorial, we will take a small detour and 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 using a couple of .NET libraries that we looked at in my previous tutorials. 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 .NET - A Short Tutorial". For real-life situations, you will typically employ a library such as "NHAPI" (a port of the original Java-based HAPI HL7 framework which I covered in my previous tutorials) to handle your HL7 message processing requirements within your custom .NET applications. My previous tutorials on using NHAPI illustrated how to utilize this library to create, transmit as well as process HL7 messages. In this tutorial, we will put NHAPI in conjunction with some classes in the core .NET framework as well as by using an additional third party library called "NHAPI Tools" 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.

Orders and Order Response Workflow

“Life moves pretty fast. If you don’t stop and look around once in a while, you could miss it.” ~ Ferris (from the movie “Ferris Bueller’s Day Off”)

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 NHAPI parsers. I had mentioned there that NHAPI 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).

Base-64 Encode/Deccode Operation Overview

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

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 core .NET classes to achieve the desired behavior.

    using System;
    using System.IO;

    namespace CommonUtils
    {
        public class OurBase64Helper
        {
            public string ConvertToBase64String(FileInfo inputFile)
            {
                if (!inputFile.Exists)
                    throw new FileNotFoundException($"The specified input file {inputFile.Name} does not exist");

                return Convert.ToBase64String(File.ReadAllBytes(inputFile.FullName));
            }

            public byte[] ConvertFromBase64String(string base64EncodedString)
            {
                if (string.IsNullOrEmpty(base64EncodedString))
                    throw new ArgumentNullException(nameof(base64EncodedString), "You must supply data for Base64 decoding operation");

                if (base64EncodedString.Length % 4 != 0)
                    throw new BadBase64EncodingException("The BASE-64 encoded data is not in correct form (divide by 4 resulted in a remainder)");

                try
                {
                    return Convert.FromBase64String(base64EncodedString);
                }
                catch (Exception )
                {
                    throw new ApplicationException("Unable to decode Base-64 string supplied for operation. Please check your inputs") ;
                }
            }
        }
    }

A custom exception class can be used to signal Base64 encoding or decoding errors like this, or you can simply use the ArgumentException class that comes with .NET. 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.

    using System;

    namespace CommonUtils
    {
        public class BadBase64EncodingException : Exception
        {
            public BadBase64EncodingException(string errorMessage):base(errorMessage)
            {

            }
        }
    }

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 NHAPI and .NET - Sending HL7 Messages". So, refer to that article if you want to understand more on how to transmit messages to a remote system using NHAPI and NHAPI Tools.

    using System;
    using System.Diagnostics;
    using System.Text;
    using NHapi.Base.Parser;
    using NHapiTools.Base.Net;

    namespace SendingBinaryDataExample
    {
        public class NHapiSendOrderResponseMessageExample
        {
            private static int PORT_NUMBER = 57550;// change this to whatever your port number is
            public static void Main(String[] args)
            {
                try
                {
                    // create the HL7 message
                    // this OruMessageFactory class is not from NHAPI but my own wrapper class
                    // check my GitHub page or see my earlier article for reference
                    var oruMessage = OruMessageFactory.CreateMessage();

                    // create a new MLLP client over the specified port (note this class is from NHAPI Tools)
                    //Note that using higher level encodings such as UTF-16 is not recommended due to conflict with
                    //MLLP wrapping characters

                    var connection = new SimpleMLLPClient("localhost", PORT_NUMBER, Encoding.UTF8);

                    // send the previously created HL7 message over the connection established
                    var parser = new PipeParser();
                    LogToDebugConsole("Sending ORU R01 message:" + "\n" + parser.Encode(oruMessage));
                    var response = connection.SendHL7Message(oruMessage);

                    // display the message response received from the remote party
                    var responseString = parser.Encode(response);
                    LogToDebugConsole("Received response:\n" + responseString);

                }
                catch (Exception e)
                {
                    LogToDebugConsole($"Error occured while creating and transmitting HL7 message {e.Message}");
                }
            }

            private static void LogToDebugConsole(string informationToLog)
            {
                Debug.WriteLine(informationToLog);
            }

        }
    }

“What man actually needs is not a tensionless state but rather the striving and struggling for some goal worthy of him.” ~ Viktor Frankl

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.

    using NHapi.Base.Model;

    namespace SendingBinaryDataExample
    {
        public class OruMessageFactory
        {
            //you will pass in parameters here in the form of a DTO or domain object
            //for message construction in your implementation
            public static IMessage CreateMessage()
            {
                return new OurOruR01MessageBuilder().Build();
            }
        }
    }
    using System;
    using System.Globalization;
    using System.IO;
    using CommonUtils;
    using NHapi.Model.V23.Datatype;
    using NHapi.Model.V23.Message;

    namespace SendingBinaryDataExample
    {
        internal class OurOruR01MessageBuilder
        {
            private ORU_R01 _oruR01Message;
            private readonly OurBase64Helper _ourBase64Helper = new OurBase64Helper();
            private readonly string _pdfFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Test Files", "Sample Pathology Lab Report.pdf");
            
            /*You can pass in a domain or data transfer object as a parameter
            when integrating with data from your application here
            I will leave that to you to explore on your own
            Using fictional data here for illustration*/

            public ORU_R01 Build()
            {
                var currentDateTimeString = GetCurrentTimeStamp();
                _oruR01Message = new ORU_R01();
                
                CreateMshSegment(currentDateTimeString);
                CreatePidSegment();
                CreatePv1Segment();
                CreateObrSegment();
                CreateObxSegment();
                return _oruR01Message;
            }

            private void CreateMshSegment(string currentDateTimeString) 
            {
                var mshSegment = _oruR01Message.MSH;
                mshSegment.FieldSeparator.Value = "|";
                mshSegment.EncodingCharacters.Value = "^~\\&";
                mshSegment.SendingApplication.NamespaceID.Value = "Our System";
                mshSegment.SendingFacility.NamespaceID.Value = "Our Facility";
                mshSegment.ReceivingApplication.NamespaceID.Value = "Their Remote System";
                mshSegment.ReceivingFacility.NamespaceID.Value = "Their Remote Facility";
                mshSegment.DateTimeOfMessage.TimeOfAnEvent.Value = currentDateTimeString;
                mshSegment.MessageControlID.Value = GetSequenceNumber();
                mshSegment.MessageType.MessageType.Value = "ORU";
                mshSegment.MessageType.TriggerEvent.Value = "R01";
                mshSegment.VersionID.Value = "2.3";
                mshSegment.ProcessingID.ProcessingID.Value = "P";
            }

            private void CreatePidSegment() 
            {
                var pidSegment = _oruR01Message.GetRESPONSE().PATIENT.PID;

                var patientName = pidSegment.GetPatientName(0);
                patientName.FamilyName.Value = "Mouse";
                patientName.GivenName.Value = "Mickey";
                pidSegment.SetIDPatientID.Value = "378785433211";
                var patientAddress = pidSegment.GetPatientAddress(0);
                patientAddress.StreetAddress.Value = "123 Main Street";
                patientAddress.City.Value = "Lake Buena Vista";
                patientAddress.StateOrProvince.Value = "FL";
                patientAddress.Country.Value = "USA";
            }

            private void CreatePv1Segment() 
            {
                var patientInformation = _oruR01Message.GetRESPONSE().PATIENT;
                var visitInformation = patientInformation.VISIT;
                var pv1Segment = visitInformation.PV1;
                pv1Segment.PatientClass.Value = "O"; // to represent an 'Outpatient'
                var assignedPatientLocation = pv1Segment.AssignedPatientLocation;
                assignedPatientLocation.Facility.NamespaceID.Value = "Some Treatment Facility";
                assignedPatientLocation.PointOfCare.Value = "Some Point of Care";
                pv1Segment.AdmissionType.Value = "ALERT";
                var referringDoctor = pv1Segment.GetReferringDoctor(0);
                referringDoctor.IDNumber.Value = "99999999";
                referringDoctor.FamilyName.Value = "Smith";
                referringDoctor.GivenName.Value = "Jack";
                referringDoctor.IdentifierTypeCode.Value = "456789";
                pv1Segment.AdmitDateTime.TimeOfAnEvent.Value = GetCurrentTimeStamp();
            }

            private void CreateObrSegment()
            {
                var ourOrderObservation = _oruR01Message.GetRESPONSE().GetORDER_OBSERVATION();
                var obrSegment = ourOrderObservation.OBR;
                obrSegment.FillerOrderNumber.UniversalID.Value = "123456";
                obrSegment.UniversalServiceIdentifier.Text.Value = "Document";
                obrSegment.ObservationEndDateTime.TimeOfAnEvent.SetLongDate(DateTime.Now);
                obrSegment.ResultStatus.Value = "F";
            }

            private void CreateObxSegment()
            {
                var ourOrderObservation = _oruR01Message.GetRESPONSE().GetORDER_OBSERVATION();
                var observationSegment = ourOrderObservation.GetOBSERVATION(0);
                var obxSegment = observationSegment.OBX;

                obxSegment.SetIDOBX.Value = "0";
                //see HL7 table for list of permitted values here. We will use "Encapsulated Data" here
                obxSegment.ValueType.Value = "ED";
                obxSegment.ObservationIdentifier.Identifier.Value = "Report";

                //"Varies" is a NHAPI class to handle data where the appropriate 
                //data type is not known until run-time (e.g. OBX-5)
                var varies = obxSegment.GetObservationValue(0);
                var encapsulatedData = new ED(_oruR01Message, "PDF Report Content");

                encapsulatedData.SourceApplication.NamespaceID.Value = "Our .NET Application";
                encapsulatedData.TypeOfData.Value = "AP"; //see HL7 table 0191: Type of referenced data
                encapsulatedData.DataSubtype.Value = "PDF";
                encapsulatedData.Encoding.Value = "Base64";

                var base64EncodedStringOfPdfReport = _ourBase64Helper.ConvertToBase64String(new FileInfo(_pdfFilePath));
                encapsulatedData.Data.Value = base64EncodedStringOfPdfReport;
                varies.Data = encapsulatedData;
            }

            private static string GetCurrentTimeStamp()
            {
                return DateTime.Now.ToString("yyyyMMddHHmmss",CultureInfo.InvariantCulture);
            }

            private static string GetSequenceNumber()
            {
                const string facilityNumberPrefix = "1234"; // some arbitrary prefix for the facility
                return facilityNumberPrefix + GetCurrentTimeStamp();
            }
        }
    }

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.

Sending message:
MSH|^~\&|Our System|Our Facility|Their Remote System|Their Remote Facility|20181006144216||ORU^R01|123420181006144216|P|2.3
PID|378785433211||||Mouse^Mickey||||||123 Main Street^^Lake Buena Vista^FL^^USA
PV1||O|Some Point of Care^^^Some Treatment Facility|ALERT||||99999999^Smith^Jack^^^^^^^^^^456789||||||||||||||||||||||||||||||||||||20181006144216
OBR|||^^123456|^Document||||201810061442|||||||||||||||||F
OBX|0|ED|Report||Our .NET Application^AP^PDF^Base64^JVBERi0xLjUNJeLjz9MNCjMxI..(data truncated here for easier display. There will be way more content here)

Received response:
MSH|^~\&|Their Remote System|Their Remote Facility|Our System|Our Facility|20181006144219.488-0600||ACK^R01|5|P|2.3
MSA|AA|123420181006144216

HAPI Test Panel ORU R01 Message

“Music is the universal language of mankind.” ~ Henry Wadsworth Longfellow

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.

    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using CommonUtils;
    using NHapi.Base.Parser;
    using NHapi.Model.V23.Datatype;
    using NHapi.Model.V23.Message;

    namespace ReceivingBinaryDataExample
    {
        public class Program
        {
            private static readonly string OruR01MessageWithBase64EncodedPdfReportIncluded = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Test Files", "SaravananOruR01Message.hl7");
            private static string _extractedPdfOutputDirectory = "C:\\HL7TestOutputs";

            public static void Main(string[] args)
            {
                try
                {
                    // instantiate a PipeParser, which handles the "traditional or default encoding"
                    var ourPipeParser = new PipeParser();
                    // parse the string format message into a Java message object
                    var hl7Message = ourPipeParser.Parse(File.ReadAllText(OruR01MessageWithBase64EncodedPdfReportIncluded, Encoding.UTF8));

                    //cast to message to an ORU R01 message in order to get access to the message data
                    var oruR01Message = hl7Message as ORU_R01;
                    if (oruR01Message != null)
                    {
                        // Display the updated HL7 message using Pipe delimited format
                        LogToDebugConsole("Parsed HL7 Message:");
                        LogToDebugConsole(ourPipeParser.Encode(hl7Message));

                        var encapsulatedPdfDataInBase64Format = ExtractEncapsulatedPdfDataInBase64Format(oruR01Message);

                        //if no encapsulated data was found, you can cease operation
                        if (encapsulatedPdfDataInBase64Format == null) return;

                        var extractedPdfByteData = GetBase64DecodedPdfByteData(encapsulatedPdfDataInBase64Format);

                        WriteExtractedPdfByteDataToFile(extractedPdfByteData);
                    }
                }
                catch (Exception e)
                {
                    LogToDebugConsole($"Error occured during Order Response PDF extraction operation -> {e.StackTrace}");
                }
            }

            private static ED ExtractEncapsulatedPdfDataInBase64Format(ORU_R01 oruR01Message)
            {
                //start retrieving the OBX segment data to get at the PDF report content
                LogToDebugConsole("Extracting message data from parsed message..");
                var ourOrderObservation = oruR01Message.GetRESPONSE().GetORDER_OBSERVATION();
                var observation = ourOrderObservation.GetOBSERVATION(0);
                var obxSegment = observation.OBX;

                var encapsulatedPdfDataInBase64Format = obxSegment.GetObservationValue(0).Data as ED;
                return encapsulatedPdfDataInBase64Format;
            }

            private static byte[] GetBase64DecodedPdfByteData(ED encapsulatedPdfDataInBase64Format)
            {
                var helper = new OurBase64Helper();

                LogToDebugConsole("Extracting PDF data stored in Base-64 encoded form from OBX-5..");
                var base64EncodedByteData = encapsulatedPdfDataInBase64Format.Data.Value;
                var extractedPdfByteData = helper.ConvertFromBase64String(base64EncodedByteData);
                return extractedPdfByteData;
            }

            private static void WriteExtractedPdfByteDataToFile(byte[] extractedPdfByteData)
            {
                LogToDebugConsole($"Creating output directory at '{_extractedPdfOutputDirectory}'..");

                if (!Directory.Exists(_extractedPdfOutputDirectory))
                    Directory.CreateDirectory(_extractedPdfOutputDirectory);

                var pdfOutputFile = Path.Combine(_extractedPdfOutputDirectory, "ExtractedPdfReport.pdf");
                LogToDebugConsole(
                    $"Writing the extracted PDF data to '{pdfOutputFile}'. You should be able to see the decoded PDF content..");
                File.WriteAllBytes(pdfOutputFile, extractedPdfByteData);

                LogToDebugConsole("Extraction operation was successfully completed..");
            }

            private static void LogToDebugConsole(string informationToLog)
            {
                Debug.WriteLine(informationToLog);
            }
        }
    }

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.

Parsed HL7 Message:
MSH|^~\&|Our System|Our Facility|Their Remote System|Their Remote Facility|20181006181311||ORU^R01|123420181006181311|P|2.3
PID|378785433211||||Mouse^Mickey||||||123 Main Street^^Lake Buena Vista^FL^^USA
PV1||O|Some Point of Care^^^Some Treatment Facility|ALERT||||99999999^Smith^Jack^^^^^^^^^^456789||||||||||||||||||||||||||||||||||||20181006181311
OBR|||^^123456|^Document||||201810061813|||||||||||||||||F
OBX|0|ED|Report||Our .NET Application^AP^PDF^Base64^JVBERi0xLjUNJeLjz9MNCjMxI..(data truncated here for easier display. There will be way more content here)

Extracting PDF data stored in Base-64 encoded form from OBX-5..
Creating output directory at 'C:\HL7TestOutputs'..
Writing the extracted PDF data to 'C:\HL7TestOutputs\ExtractedPdfReport.pdf'. You should be able to see the decoded PDF content..
Extraction operation was successfully completed..

Extracted PDF Pathology Report

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.

Conclusion

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, we will look at how to perform message validation using NHAPI. 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