Sunday, March 29, 2015

FHIR Identifiers Revisited

I want to revisit the HL7 FHIR identifier as I have discovered a few new things.  The Identifier is a complex object that consists of several properties all of which are optional:

<[name] xmlns="http://hl7.org/fhir"> doco
 <!-- from Element: extension -->
 <use value="[code]"/><!-- 0..1 usual | official | temp | secondary (If known) -->
 <label value="[string]"/><!-- 0..1 Description of identifier -->
 <system value="[uri]"/><!-- 0..1 The namespace for the identifier -->
 <value value="[string]"/><!-- 0..1 The value that is unique -->
 <period><!-- 0..1 Period Time period when id is/was valid for use --></period>
 <assigner><!-- 0..1 Resource(Organization) Organization that issued id (may be just text) --></assigner>
</[name]>

Here is its definition:
"
A numeric or alphanumeric string that is associated with a single object or entity within a given system. Typically, identifiers are used to connect content in resources to external content available in other frameworks or protocols. Typically, identifiers are used to connect content in resources to external content available in other frameworks or protocols. Identifiers are associated with objects, and may be changed or retired due to human or system process and errors.
"

Here are some of the key identifiers used in Radiology informatics:

Name Description
MRN or PatientID Identifies a specific patient
Accession # or Filler Order Number Identifies a specific radiology procedure or exam
Procedure Code Identifies a type of procedure or exam (e.g. CT CHEST W/WO)
DICOM Study Instance UID Identifies a group of related DICOM SOP Instance (or images) - usually for a single radiology procedure
DICOM SOP Instance UID Identifies a single DICOM SOP Instance (or image)


The most important property of the identifier is value which actually holds the identifier itself.

The next more important property is the system which provides a scope or namespace for the identifier.  Given the different types of identifiers and systems that generate them - conflicts are sure to exist and the system provides a way of describing the context or scope of the identifier.  For example, you could have two RIS systems - RIS A and RIS B.  Both RIS systems were implemented independently of each other and started generating accession numbers starting at 1000.  Given that both systems are using the same accession numbers to identify different radiology procedures - the system must be used to make them unique when used together.  The documentation for system is

"
The namespace for the identifier
"

Here is more information about system from the documentation on identifier:
"
The system referred to by means of a URI defines how the identifier is defined (i.e. how the value is made unique). It might be a specific application or a recognized standard/specification for a set or identifiers or a way of making identifiers unique. The valueSHALL be unique within the defined system and have a consistent meaning wherever it appears. Both system and value are always case sensitive.
FHIR defines some useful URIs directly. OIDs (urn:oid:) and UUIDs (urn:uuid:) may be registered in the HL7 OID registry and should be if the content is shared or exchanged across institutional boundaries. If the identifier itself is naturally globally unique (e.g. an OID, a UUID, or a URI with no trailing local part), then the system SHALL be "urn:ietf:rfc:3986", and the URI is in the value.
"
The documentation refers to some useful URIs for various standardized codes (e.g. SNOMED, LOINC, Radlex, HL7 v2, HL7 v3).  Given the identifiers listed above, it seems that using standardized codes for the procedure code makes a lot of sense.  This is not the case for the other types though which would be generated by information systems such as the EMR, HIS, RIS, Modality or PACS.

It is interesting to note that the concept of unique identities is not new to DICOM.  DICOM adopted the ISO UID scheme for identifying types of DICOM Instances (i.e. SOP Class UID) as well as instance data (Study, Series, Instance, etc).  Equipment that generates DICOM SOP Instances (e.g. Modality, PACS, etc) is responsible for making sure the generated UID's are unique.  This is typically done by registering a unique root UID along with an algorithm to generate the numbers following the root.  The algorithm is not standardized and I have seen many systems incorrectly implement this or installations incorrectly configured which resulting in UID's that are not globally unique.

The DICOM UID root is 1.2.840.10008, the numbers following that are used to identify specific things within that root.  For example, the "CT Image Storage" SOP Class UID is 1.2.840.10008.5.1.4.1.1.2 and the UID used for Implicit Little Endian Transfer syntax is 1.2.840.10008.1.2.

You will notice the term "OID" used in the above documentation.  This is the same concept as UID's used it DICOM with a different name and perhaps issuing authority.  OID's seem to be the direction forward for HL7 FHIR and each system that generates identifies will need to register it with HL7 for $100.

The next property of interest is use which can have any of the following values

Definition
usualthe identifier recommended for display and use in real-world interactions.
officialthe identifier considered to be most trusted for the identification of this item.
tempA temporary identifier.
secondaryAn identifier that was assigned in secondary use - it serves to identify the object in a relative context, but cannot be consistently assigned to the same object again in a different context.
I can imagine temp being used to identify unknown patients (e.g. unconscious patient from motor vehicle accident in the ER without any ID).  I suppose secondary could be used for alternative identifiers (e.g. RIS specific patient identifier which is different than patient identifier in the enterprise master patient index).  Official could be used for identifiers that are standardized and therefore the same for all systems in the world.  Some examples include codes (LOINC, HL7 Codes, DICOM SOP Class UIDs, etc).  Usual could be used for identifiers for specific things (patients, visits, exams, studies, images).  Given the list of radiology identifiers above, all of them should probably be "usual" uses except for the procedure code.

The label property might be useful to provide a human readable string for standardized codes.  Standardized codes are usually intended for understanding by computers and not humans.  For example, the DICOM SOP Class UID for storing CT Images is "1.2.840.10008.5.1.4.1.1.2".  Very few people in the world would automatically recognize that number as a CT Image.  For everyone else, it is far more useful to display "CT Image" to the user instead of the number.  This is where the label comes in.  For the radiology identifiers listed above, the best candidate for including a label property is the procedure code.

The last two properties are period and assigner.  The idea behind period is that you can further constrain an identifier through a time period.  I don't have any real world use cases for period so am not sure when it might show up.  The assigner is just a URI to an Organization resource that assigns/generates the identifiers.  This is basically a REST version of the system concept.  

Moving forward with the radiology report repository spike will require that I create identifiers and I should be OK with simply populating the "value" property - although a real implementation should have system populated with a valid OID.

Saturday, March 28, 2015

Happy Birthday Cornerstone!

I just realized that cornerstone had its first birthday a little over one week ago.  The project started based on this discussion on comp.protocols.dicom where folks where folks were disappointed with the lack of open source image viewers.  While there already was two good open source viewers (Ovyiam and DICOM Web Viewer), neither of these were architected such that I could use them to build my own web based medical image viewing applications.  I was already convinced that the future of medical imaging was HTML5/JS based image viewers and being the lazy programmer that I am, didn't want to build basic image viewing functionality over and over again (I have personally coded a ww/wc algorithm at least 15 different times in various languages).  I had a personal need for a javascript SDK that made it easy to display interactive medical images in a web browser - and that is how cornerstone began.

I must admit that starting cornerstone was not easy to do.  I was starting a new business (Lury) and it seemed a bit crazy to spend my time writing code that I would be giving away for free.  This is especially true because I had figured out a number of tricks to make client side rendering possible while the industry norm was (and still is) server side rendering.  The benefits of client side rendering are compelling enough to provide the differentiation needed to make a new startup company like Lury successful in an already competitive market and giving this away for free was very hard to do.

On top of giving away some of these secrets, the code I wrote would be on display for everyone to see.  While I believe I write fairly good code, I am not perfect and what is "right" can sometimes be subjective.  Code reviews are actually quite common in closed source projects and it is one of the most vulnerable experiences you go through as a software developer.  What happens is you get in a room with a bunch of other developers and they look at your code with a magnifying glass and give you feedback.  Your "best effort" is on display and the bulk of the discussion is around how you could do better.  In safe environments, these meetings are productive and are highly educational.  The internet is not a safe place though and making it publicly available for everyone to see and criticize required a tremendous amount of courage.

Looking back on the past year, I can say that making cornerstone open source is the best thing I have ever done.  It has brought me tremendous joy to see others using cornerstone.  There are at least 50 projects that I know of using cornerstone today and probably many I don't know about.  Many of these projects are positively impacting patient outcomes and probably would not have been possible without cornerstone.  I have also made many new friends all over the world - some of which have given me an open invitation to stay with them whenever I might visit.

I want to say "thank you" to everyone who has supported the cornerstone project - it would not have been possible without your emails of encouragement, bug reports, bug fixes and new features.  The future is bright for cornerstone and everyone is welcome to be part of this!

Thursday, March 26, 2015

Radiology Report Repository Spike Update #2

Today I made progress on the spike and have it successfully creating a new Patient resource if it doesn't already exist.  The source code for the spike/prototype can be found here.  Here is a screen shot of the spike at work receiving the same message twice:




The first time it receives the message, it doesn't find an existing patient resource so it creates a new one.  The second time it receives the message, it finds the one it previously created so does not create a new one.  Note that you can issue a HTTP DELETE on the resource id to remove like this:



A few notes about this:
1) Doing everything in JavaScript is really cool.  I don't think I would be this far along if I had tried to do it all in Mirth Connect
2) Mapping from V2.x code to V3/FHIR wasn't exactly straightforward.  2x has many codes that are not in v3/FHIR.
3) I should probably bring up my own FHIR server at some point.  Not sure if I should use one of the open source versions or build a simple one myself.


Next up - creating the DiagnosticReport resource.

Wednesday, March 25, 2015

Integrating Meteor client only application with ASP.NET MVC 5

I recently had a project where I wanted to use Meteor but we were unable to utilize the meteor server for our phase 1 deliverable.  Instead we had to integrate with an existing ASP.NET MVC 5 application.  Here is how I did it:

1) Use HTTP instead of DDP for all communication to the server.  Meteor makes client/server communication really easy through publications, DDP and meteor methods.  While there are some DDP libraries that may allow your non meteor server to integrate via DDP, this was not the route we took.  Instead we decided to do all RESTful calls to our server as it had existing REST APIs that we wanted to us.

2) Tell the meteor client code to disconnect from the meteor server.  The meteor client code assumes there is a meteor server and immediately tries to establish a DDP connection to communicate with it.  Not only for normal client/server calls, but also to receive notifications of hot code pushes.  Since we don't have a meteor server in our case, we need to explicitly disconnect like so in my client/main.js:

Meteor.disconnect();

3) Build meteor for deployment.  Building meteor for deployment will generate a single JS and CSS file from all JS/CSS used by the client side in your project.  This means it will concatenate, uglify and minify everything for you.  Create a build output directory "build" as a peer to your meteor project and generate the build outout:

meteor build --directory ../build

4) Copy the generated js and css files into your ASP.NET MVC project.  The generated files should be in the build/bundle/programs/web.browser directory.  You should find a scripts folder in your ASP.NET MVC project, I created a sub directory to hold my meteor code.  Note that the meteor build creates a unique filename for the js and css file (presumably based on a file hash as it does not rename the file if the file contents dont change).  I simply renamed the js and css files from the generated name to a consistent name (e.g. main.js and main.css) so I didn't have to add/remove files to TFS all the time.  I put the files in a bundle to ensure that new versions invalidated older cached versions of the files.

5) Load the meteor js and css files from your ASP.NET CSHTML file and add the meteor runtime config.  The meteor client code depends on a global variable named __meteor_runtime_config__ which the meteor server uses to pass several properties to the client.  Since we don't have a meteor server, you need to set this up and pass it to the client from your ASP.NET MVC app.  Here is our CSHTML that we are using:

@model MeteorViewModel
@{
    ViewBag.Title = "";
    Layout = "";
    ViewBag.RootUrl = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~"));
}

<link rel="stylesheet" type="text/css" class="__meteor-css__" href="@Url.Content("~/Scripts/Meteor/main.css?meteor_css_resource=true")">

<script type="text/javascript">
__meteor_runtime_config__ = {
    "meteorRelease": "METEOR@1.0.3.1",
    "ROOT_URL": "@ViewBag.RootUrl" + "Scripts/Meteor/Public/",
    "ROOT_URL_PATH_PREFIX": "",
    "autoupdateVersion": "af58dd0d8e3b4c9b85ee2fa53553d2502663b530",
    "autoupdateVersionRefreshable": "a6de052a32154229bfac08baf16f2141a6b943e2",
    "autoupdateVersionCordova": "none",
    "Data": "@Model.Data"
};
</script>


@Scripts.Render("~/bundles/Meteor")


A few notes about this:
1) We need to set the ROOL_URL correctly for where we are serving the public folder from
2) I added the "Data" property to pass some data from our ASP.NET MVC app to the meteor app
3) We use ASP.NET MVC's bundle feature with the meteor code to avoid caching issues on the browser

You should be able to follow the above steps to integrate Meteor client only applications with other server side stacks.

Tuesday, March 24, 2015

Radiology Report Repository Spike Update #1

Yesterday I started working on the spike for the Radiology Report Repository.  The first thing I did was create a sample ORU message to work with.  I decided to make the most basic and simple ORU message possible and this is what I came up with:

MSH|^~\&||ABC|||201503231355||ORM^O01|1000|D|2.4|||AL|NE
PID|1|2000^^^^MR|||DUCK^DONALD||19340609|M|||1113 QUACK STREET^^DUCKBURG^CALISOTA^^^^^|||||||
PV1|1|I||EL||||||SURG||||PHY||||IN|||||||||||||||||||||ABC||ADM|||201503231330||||||||
OBR|1||3000|RAD^CARM^RAD C-ARM|||201503233154||||||||||||||||||F|||||||||
OBX|1|TX|L.RADREA2^Reason for Exam:||Report text

The key things in this message are:

Patient Name: DUCK^DONALD
Patient MRN: 2000
Accession Number: 3000
Report Date: March 23, 2015
Report Text: Report Text
Exam Code: CARM

I also need to send this message to my HL7 listener, so I downloaded and install HL7 Inspector for this.  After installing HL7 Inspector, copy the above test ORU message above into your clipboard, then right click the background of HL7 Inspector and choose "Import from Clipboard", click OK on the "Import Options" dialog and you should see the message displayed like this:




Next I need an HL7 listener.  I initially started with Mirth Connect but quickly abandoned it as I realized I would need to write quite a bit of logic and it would be very hard to develop/debug/test that logic using Mirth.  I decided to try building the prototype using Node.js and google helped me find the following code to receive HL7 Messages.  I created a new project using WebStorm, pasted that code in there, started up the Node.js process, configured HL7 Inspector to send to port 6020 and sent the message to it from HL7 Inspector:




Success!  Next up is parsing the HL7 message and implement the following pseudo code logic:

parse the HL7 message
search for Patient resource given the MRN
if Patient resource does not exist
  create Patient resource javascript object
  POST Patient resource javascript object
Create DiagnosticReport javascript object with reference to Patient resource
POST DiagnosticReport javascript object

Given this pseudo code, the first step is to parse the HL7 message.   A google search points me to the L7 library.  A bit of code later and I have parsed the fields I need to create a Patient resource:

Next up we need to start implementing the pseudo code.

Monday, March 23, 2015

HL7 FHIR Identifiers

Before moving on with this spike, I wanted to better understand how HL7 FHIR deals with identifiers since this is fundamental to actually finding my reports given what we have in a DICOM Study.  The DiagnosticReport resource has a property named identifier which is of type identifier. The documentation states that the identifier is:

"
The local ID assigned to the report by the order filler, usually by the Information System of the diagnostic service provider.
"

When it comes to radiology reports, the order filler number is supposed to match what is in the accession number field in the DICOM study so this is what we need to use to actually find the DiagnosticReport for a given DICOM Study.  Lets take a look at the identifier we sent when we created the diagnosticReport in the prior blog post:

  "identifier": {
    "use" : "official",
    "system" : "?",
    "value" : "1000"
  }

But we know that an accession number is not globally unique so we need additional matching criteria.  The patient identifier (or MRN) is a good secondary key so lets look at that.  The Patient resource has a property named identifier that has zero to many of type identifier.  Here is its documentation:

"
An identifier that applies to this person as a patient.
"

And here is the identifier we used for the Patient resource:

  "identifier": [{
    "use" : "usual",
    "label" : "MRN",
    "system" : "urn:oid:0.1.2.3.4.5.6.7",
    "value" : "654321"
  }],

In both cases the "value" is the actual identifier - but are the "use", "label" and "system" properties for?  Looking at the documentation for the identifier type we learn the following:

Here is the documentation on the use property:

usualthe identifier recommended for display and use in real-world interactions.
officialthe identifier considered to be most trusted for the identification of this item.
tempA temporary identifier.
secondaryAn identifier that was assigned in secondary use - it serves to identify the object in a relative context, but cannot be consistently assigned to the same object again in a different context.
In the above examples we used "usual" and "official" but it isn't clear when (or if) I should use one over the other.  I do a quick google search on the terms "HL7 FHIR identifier use usual official" but none of the results on the first two pages are helpful.  My search terms are very specific so I am thinking this may be an area of HL7 FHIR that is not well documented yet.  Lets analyze the identifiers from the sample DiagnosticReport resources on the spark server and see if there is any consistency.  I use the Advanced REST Client to make an open ended query for DiagnosticReports:

http://spark.furore.com/fhir/DiagnosticReport?_format=application%2fjson%2bfhir


I find two kinds:

identifier: {
  use: "official"
  system: "http://acme.com/lab/reports"
  value: "12Z986912-16258694"
}
identifier: {
  use: "official"
  system: "http://www.bmc.nl/zorgportal/identifiers/reports"
  value: "nr1239044"
}

Since both kinds used the code "official" for "use", I am lead to believe that I should do the same with one reservation - none of these DiagnosticReport examples are radiology reports.  All of the examples with identifiers on the spark server appear to be for lab results of some type.  I don't know much about lab results so it is possible that how they are identified and referenced is different than how radiology reports are.  It is interesting that the system is a url to some other system.  Presumably this other system generated the actual identifier, but given that I am building a radiology report repository from HL7 ORU messages, the originating RIS may not have a URL like this to refer to.  The spark server does not appear to validate the system at all (you will notice I used the system "?" for the diagnostic report I created) so for this spike, it probably doesn't matter what I put in there. 

Looking at the schema for the identifier, I noticed that none of the properties are required!  For this spike, I may be able to simply omit the use and system.  I try creating another DiagnosticReport with this change and the spark server accepts the HTTP POST and creates the resource!

So now I have a bit better understanding of HL7 FHIR identifiers, but still not full understanding.  I have some emails out to people that I am hoping can help provide more information.  For now I am going to leave this and move on with the spike.




Sunday, March 22, 2015

Creating an HL7 FHIR DiagnosticReport Resource

With our recent success creating and searching for Patient resources, it seems that creating a DiagnosticReport resource may be just as easy.  Lets begin by mapping the minimum number of fields we need from an HL7 ORU message:

FHIR DiagnosticReport HL7 2.x Field Note
name OBR-4 The type of procedure - need lookup table
status OBR-25 Final, prelim, etc
issued OBR-7 report date
subject Link to the patient resource we created or found
performer Lookup the organization resource
identifier OBR-3 Filler order number (accession number)
text.div OBX The actual report text

The first thing we need to do is get references to the patient and organization resources for this report.  We could use the patient we previously created, but I am not going to use it in this example because it may not be available in the future (the spark server may reset its data someday) and I can envision someone trying what I describe below and want it to work without having to create a patient resource first.  I am going to use an existing patient and organization that appears to be part of the standard dataset in the spark server:

Patient with id pat1:

http://spark.furore.com/fhir/Patient/pat1

Organization with id 1:

http://spark.furore.com/fhir/Organization/1

Here is a JSON document for the DiagnosticReport resource we will use:


  "resourceType" : "DiagnosticReport",
  "text" : { 
    "status" : "generated",
    "div" : "Report text here"
  },
  "name" : {
    "coding" : [{
      "system" : "http://loinc.org",
      "code" : "38269-7"
    }]
  },
  "status" : "final",
  "issued": "2015-03-22",
  "subject" : {
    "reference" : "Patient/pat1"
  },
  "performer" : {
    "reference" : "Organization/1",
    "display": "Acme Healthcare, Inc."
  },
  "identifier": {
    "use" : "official",
    "system" : "?",
    "value" : "1000"
  }
}

Posting this creates the new resource with id returned in the Location response header:


Success!  Two issues with the above:
1) I am not sure what LOINC codes are.  I used a code from an existing DiagnosticReport in the spark database but will probably need to do lookup the real code by mapping what I get in OBR-4 through some lookup table.
2) I don't understand how identifiers are handled yet.  It seems you can scope them somehow using the system property.  Will need to research this more later.

The next step is to try and create Patient and DiagnosticReport resources from an actual ORU message.

Saturday, March 21, 2015

Searching for HL7 FHIR Patient Resources

With our previous success with creating a Patient resource, we now look to searching for them to avoid creating duplicates.  Glancing through the search documentation reveals that a search is done using an HTTP get to the base resource along with search criteria as query parameters.  Using the Advanced REST Client, we can easily search for the patient we previously created by its MRN using the following URL:

http://spark.furore.com/fhir/Patient?identifier=654321&_format=application%2fjson%2bfhir



If you look at the response, you will notice the "totalResults" has the value 3.  There are in fact 3 patient resources with the identifier 654321.  Two of these patients are ones I created while writing the prior blog entry.  One of them already existed in the spark server.  This raises an interesting question of how unique entities are managed in HL7 FHIR, but not something I am going to worry about right now for this spike - I want to move forward and create the DiagnosticReport resource.  Given the ease of creating the Patient resource, I am hoping it is fairly straightforward.

Creating a new HL7 FHIR Patient Resource

Now that we have a mapping from an HL7 v2.x ORU PID segment into an HL7 FIHR Patient resource, our next task is to actually try creating a new Patient resource.  With REST, new resources can be created in one of two ways - HTTP POST and HTTP PUT.  HTTP PUT is used when we know the identifier for a given resource and HTTP POST is used when the server generates the identifier.  In the case of HL7 FHIR, all resource identifiers are server generated so we cannot use HTTP PUT and must use HTTP POST.  According to the HL7 FHIR documentation for creating resources, we simply POST the JSON document to the root resource.  Sounds simple enough, we can try this out easily using any number of tools:

1) CURL - Command line utility that lets you make HTTP requests
2) Advanced REST Client - A Google chrome extension that lets you make HTTP requests with a nice UI.

I am going to start with the Advanced REST Client since it is easier to work with.  Lets being by creating a sample Patient resource in JSON:


  "resourceType" : "Patient",
  "text" : { 
    "status" : "generated",
    "div" : ""
  },
  "identifier": [{
    "use" : "usual",
    "label" : "MRN",
    "system" : "urn:oid:0.1.2.3.4.5.6.7",
    "value" : "654321"
  }],
  "name" : [{
    "use" : "official",
    "family" : ["Donald"],
    "given" : ["Duck"]
  }],
  "gender": {
    "coding" : [{
      "system" : "v3/AdministrativeGender",
      "code" : "M",
      "display" : "Male"
    }]
  },
  "maritalStatus" : {
    "coding" : [{
      "system" : "v3/MaritalStatus",
      "code" : "M",
      "display" : "Married"
    }]
  },
  "birthDate": "1944-11-17",
  "deceasedBoolean" : false, 
  "active": "true" 
}

Now using Advanced REST Client, lets POST this to the publicly accessible spark server.  Start up Advanced REST Client and enter the following URL:

http://spark.furore.com/fhir/Patient?_format=application%2fjson%2bfhir

Click the "POST" radio button and paste the above JSON into the "Payload" section.  Change the content type to "application/json"in the combo box and then press the "Send" button.  If all goes well, you should see the URL to the newly created resource in the Location response header:


You can verify that the newly created patient is in fact accessible by making a GET request for the resources URI:


Well that was amazingly easy!  One thing I should mention here is that I left the text.div property empty.  I believe that HTML is supposed to go there that produces a human understandable representation of this resource.  I didn't bother with that for now as this is just a spike and I don't really need this functionality.

Now that we know how to create a Patient resource, we need to look at how we can search for existing ones that we can use instead of always creating a new one.


Building a Radiology Report Repository using FHIR - The Patient Resource

At the end of my last post, I said I would talk about creating ImagingStudy resources as a DICOM CSTORE SCP.  After giving this more thought, I realized it wasn't necessary to meet my immediate needs.  Given that my system is already storing the DICOM just fine and I only need to display the corresponding report - what I really need to do is create DiagnosticReport resources given an inbound HL7 2.x ORU feed and then find them given the information I have received via DICOM.  As stated before, Accession number is the primary identifier used to bridge DICOM and HL7 so making those the same is the minimum solution I can build to make it work.  At this point I am thinking of creating a spike to learn more about how this might work and hopefully flush out some more issues.

Assuming I have an inbound HL7 2.x ORU message, what I need to do is create a JSON document for a DiagnosticReport and issue an HTTP post to the HL7 FHIR server to create it.  Looking at the schema for the DiagnosticReport, I immediately notice that it has references to other resources that are not optional - specifically:

1. subject - refers to the Patient resource
2. performer - refers to the organization or practitioner (radiologist) that created the report

Since these are required properties (1..1) we will need to find (or create) these resources in the HL7 FHIR server before we can create the corresponding DiagnosticReport.  The organization is something that we will need to manually create because it will be shared with all reports (for the spike we will simplify things to be single organization/issuer only).  Since we are building this system from scratch, we can't assume that some other system has populated these resources for us so we will have to do it ourself.  Lets start with the Patient resource.

Fortunately for us, every property of the patient resource is optional!  Theoretically we could create an empty Patient resource for each ORU message and reference that from our DiagnosticReport so at least it follows the schema constraints.  Since this is a spike, it seems reasonable that we could start with this, but I know we have many of the properties needed by the Patient resource in the ORU message so I am going to start with a simple mapping.  The patient demographics are transmitted in the PID segment with HL7 V2.x so lets start there:

ORU FHIR Patient
PID-2 (Patient ID External ID) identifier
PID-3 (Patient ID Internal ID) identifier
PID-4 identifier
PID-5 (Patient Name) name
PID-7 (Date/Time of birth) birthDate
PID-8 (Sex) gender
PID-16 (Marital Status) maritalStatus


A few notes:
1) There is often multiple identifiers for a given patient and the Patient resource supports this by having the identifier property 0..* (0 to many).  HL7 PID has several fields which can contain a patient identifier so I am going to map all of them into the identifier property
2) HL7 FHIR uses the HumanName schema for names which is different than how HL7 V2.x encodes names.  We will need to extract out each component of the name (family, given, etc) and create a JSON object for it
3) HL7 FHIR uses the dateTime type that differs from how HL7 V2.x encodes date times.  We will need to extract the date/time components and create a JSON object for it for the birthDate property
4) HL7 FHIR uses the administrative-gender code for the gender - we will need to map from HL7 v2.x codes to HL7 FHIR codes via lookup table
5) HL7 FHIR uses the marital-status code and we will need to map from the corresponding HL7 V2.x codes

The Patient resource also has references to others resources (managingOrganization, careProvider, etc) that I can probably populate from the ORU but I can see that will create a lot of extra work that isn't needed for this spike and am going to ignore it for now.

So now that we know how to create a Patient resource JSON document from an HL7 ORU message, lets see how we would go about creating a new one and searching for an existing one.


The HL7 FHIR ImagingStudy Resource

Continuing on with the exploration of using HL7 FHIR as a Radiology Report Repository I want to take a look at the ImagingStudy resource.  The first thing to take notice of is that there is a 0 to many relationship between the DiagnosticReport resource and the ImagingStudy resource.  The optionality of the ImagingStudy is needed for two reasons:
1) DiagnosticReport can be used for non radiology reports than don't have any images
2) Some Radiology Reports may not have a corresponding ImagingStudy.  There may in fact be a DICOM study for a report but the PACS isn't hooked up to the FHIR server yet so it doesn't know about it.  Another possibility is that there is no DICOM Study for this report - this could be the case if the study is a film.

The real question is why would there be more than one ImagingStudy for a Radiology Diagnostic Report?  A few reasons come to mind:
1) In some cases there may be multiple DICOM Studies acquired for one report.  I believe this is uncommon but it is certainly possible.  One example of where this might happen is when you have a mixed modality procedure (e.g. PET/CT) and have to use two different scanners to complete it.  Each scanner would produce a unique DICOM Study Instance UID so there would have to be one ImagingStudy for each DICOM Study that is associated with the DiagnosticReport.  Another example is where a site wants to add scanned documents to the PACS.  The system that scans the documents may generate a new DICOM Study Instance UID for the scanned documents, but they need to be viewable within the PACS with the actual radiology images.
2) DICOM has a feature called "Key Object Selection Document" which is a DICOM SOP Instance which has pointers to one or more SOP Instances.  In many cases, there are a few images in a DICOM Study which can be used to illustrate the radiologists findings.  Displaying just these "key images" to physicians and patients helps them understand the findings as they don't have to navigate through the rest of the study to find the images of interest.

Interestingly enough, the documentation for the ImagingStudy resource specifically calls out the key image use case.  It refers to the ImagingStudy as a "manifest of a set of images".  You will also notice that it implements this manifest by mimicking the DICOM Study/Series/Instance hierarchy with one to many relationships between each.  The manifest concept is very exciting because it means that a client can obtain a list of all DICOM UID's for a study with a single REST call.  This may in fact be preferable to the DICOMWeb WADO-RS Retreieve Metadata but more compact as WADO-RS includes all metadata tags and ImagingStudy just has a subset.  Of specific note is the more "user friendly" json schema of the ImagingStudy resource compared to DICOMWeb's JSON Mapping:

HL7 FHIR Instance:
{
  number: 0,
  uid: "1.2.3.4.5.6",
  sopclass: "1.2.840.10008.5.1.4.1.1.2",
  type : "IMAGE"
}

Equivalent DICOM JSON Mapping:
[
  {
    "00200013" : {
      "vr" : "IS",
      "Value": "0"
    }
  },
  {
    "00080018" : {
      "vr" : "UI",
      "Value": "1.2.3.4.5.6"
    }
  },
  {
    "00080016" : {
      "vr" : "UI",
      "Value": "1.2.840.10008.5.1.4.1.1.2"
    }
  },
  {
    "00041430" : {
      "vr" : "CS",
      "Value": "IMAGE"
    }
  }
]


Clearly the DICOM JSON mapping is larger and more difficult to work with.  I blogged about this before and look forward to a day when DICOM has a more compact JSON Mapping, preferably with normalization.  Fortunately FHIR solves a problem not yet solved by DICOM (summary of images) and is far easier to work with - this alone makes me want to immediately embrace it as a way to get a high level description of a study.

Moving on we find that the ImagingStudy has a link to the following resources:
1. Patient (subject) - Patient demographics such as name, gender, date of birth, etc
2. DiagnosticOrder (order) - Information provided by the ordering physician (perhaps signs and symptoms, etc)
3. Practitioner (referrer) - The physician who ordered the exam
4. Practitioner (Interpreter) -  The radiologist who read the exam

You will also notice that there are other important properties such as accessionNo (the accession number) and uid (the DICOM Study Instance UID) which are critical for matching and lookup purposes.

Next up will be a look at how we can create ImagingStudy resources as a DICOM CSTORE SCP.


Digging deeper into the HL7 FHIR for Radiology

Today I continued my exploration of building a Radiology Report Repository using HL7 FHIR.  As you may recall from my earlier post, my goal is to display a radiology report received via HL7 v2.x ORU to a user along side the corresponding images.  The first challenge you run into is how to connect HL7 messages to DICOM studies.  HL7 and DICOM are different standards and the data for them often comes from different systems which leads to interoperability issues - specifically around identifiers.  In radiology, the main identifier used to connect HL7 ORU to DICOM Studies is the accession number.  The accession number comes from DICOM Tag 0080,0050 and from HL7 in OBR-3 (aka filler order number).  Ideally these would exactly match making it easy to connect the two, but that isn't always the case:

1) There may be leading or trailing whitespace on these values.  DICOM values in particular must be even lengths so an extra space is often tacked on the end of the accession number if it has an odd length.
2) There may be leading zeros on one identifier but not the other.  For example, one could be 0001000 and the other could be 1000
3) There may be leading letters on one identifier but not the other.  For example, one could be A1000 and the other could be 1000

These variabilities are often discovered during the implementation phase of an interface and often require transformation of the data to get things to match.  Transformations such as whitespace removal, substring operations, string to number conversions and pattern matching substitutions (e.g regex) take care of most cases but more complex are sometimes needed that require conditional logic.  Mirth Connect supports transformations like this using JavaScript.  Some DICOM Systems will support transformations through what is commonly referred to as "tag morphing".  Tag morphing is a fairly common feature and selling point of VNA products.

In larger more complex enterprises, you may actually have multiple RIS and PACS systems which complicates the accession number matching.  Accession numbers are not globally unique and it is quite possible to have two or more different reports (for different patients!) with the exact same accession number.  It is easy to see how this can happen when you have two or more RIS systems (which typically generate the accession number) as they may have been implemented independently of each other.  I have also heard of cases where a RIS would re-use an accession number although I am not sure why that would happen.  Accession number alone may not be enough so additional matching criteria may be needed.  Here are some additional fields that could be used for the matching criteria:

1) Date or date/time.  In DICOM this could be Study Date 0008,0020 and in HL7 this could be observation date time OBR-7
2) Issuer.  In DICOM this could be 0008,0051 and in HL7 this could be the Universal ID OBR 3.3
3) Patient Identifier.  In DICOM this could be 0010,0020 and in HL7 this could be PID-2, PID-3, PID-4.

The data transformation needs listed for accession number above may also be needed for the other matching fields.  Given the above issues with connecting the HL7 v2.x world to DICOM, one might ask how this HL7 FHIR handles this.  Based on my understanding so far, FHIR is aware of DICOM in two ways:
1) It has a resource naming ImagingStudy which has many DICOM Attributes including the DICOM Study Instance UID
2) It has a collection of links to specific images in the DiagnosticReport resource.  Presumably these links would be to JPEGs of key images in the study rendered using WADO-URI.

With this base understanding, the next step is to figure out how to populate a FHIR system from incoming HL7 v2.x messages and DICOM instances.

Friday, March 20, 2015

HL7 FHIR and Radiology

I am working on a project where we need to display radiology reports received via HL7 v2.x along side the DICOM Images.  Given that few systems support querying via HL7, the way this is usually solved is by listening to incoming HL7 messages and storing them in a database.  This kind of solution is not hard to do - you can use the excellent Mirth Connect open source project to receive the HL7 messages, transform them and then store them in a database.  While I could simply add a few tables to our existing SQL database and have mirth file directly to it, such a solution would result in unnecessary coupling between the report module and the rest of the system.  What feels right is to create a standalone "Report Repository" which would expose a REST interface to store, get and update reports.

The idea behind this "Report Repository" seemed like something that HL7 FHIR would support, but every time I tried to understand it, I got lost in the sea of healthcare informatics that is not directly related to radiology imaging.  I was fortunate enough to get some time from Brad Genereaux today and he gave me a quick 10 minute bootstrap on HL7 FHIR from a radiology perspective.  We left the conversation both saying that we should blog about this and wondered which of us would do it first. I guess I win!

Anyway, here are some of my notes:

The HL7 FHIR has a resource type named Diagnostic Report which is used to store radiology reports as well as other reports (labs, etc).

There is a publicly accessible FHIR server named Spark which you can access to see examples of resources.  Spark is open source and written in C#, you can find the github repo here.

You can find an example of a Radiology Report with resource id 102 in XML format:

http://spark.furore.com/fhir/DiagnosticReport/102

If you want the report in JSON format, add the following query parameter:

http://spark.furore.com/fhir/DiagnosticReport/102?_format=application%2fjson%2bfhir

A radiology report has a few key properties:

The report text itself - this can be found in the text.div property.  The text seems to be formatted as HTML.  Brad mentioned that there may be a way to access the report text unformatted but couldn't remember how to do it.

The patient the report is for can be found in the subject.reference property.  This is a link to a Patient resource.  Brad mentioned that there may be a way to make a FHIR request where the links are replaced with the actual data reducing the number of HTTP requests to get what you want.  If this works, you could get the actual patient info (name, id, etc) along with the report in one request

The status of the report (final, preliminary, addendum) can be found in the status field (in this case it is final)

According to the Diagnostic Report schema, there should be a link to the interpreting radiologist, but that isn't present in this specific example.

In this specific example, it appears that there is a structure measurement as well (bone density).  This isn't an immediate concern of mine so I am not going to explore it much further, but it would be interesting to see how this maps to DICOM SR.

A big thanks to Brad for his time today, hopefully others will find this information helpful.