Patient Express Portal — a lightweight lab/imaging results portal + Orthanc/OHIF PACS integration for OpenEMR

Hi everyone,

I’d like to share a project I’ve been building on top of OpenEMR:
Patient Express Portal — a lightweight, “express” patient-facing
portal focused on the two things patients actually check most often:
lab results and imaging studies. It sits alongside OpenEMR’s native
patient portal, not instead of it — there’s a one-click button to jump
into the full native portal whenever more depth is needed.

Repo: GitHub - luisuriarte/patient-express-portal · GitHub

What it does

  • Lab Results tab — pulls from procedure_order/procedure_report
    (grouped by encounter), and also surfaces scanned PDF/JPG lab
    reports that were uploaded as plain OpenEMR documents (not through a
    procedure order). If a scanned document shares an encounter with a
    real lab panel, it’s merged into the same card; otherwise it gets its
    own “Lab Document” card.
  • Imaging tab — lists DICOM studies and non-DICOM
    images/PDFs (X-rays, dental photos, etc.), with inline viewing for
    images/PDFs and a direct link into an OHIF Viewer instance for
    DICOM studies. Documents that share the same StudyInstanceUID are
    automatically grouped into a single “Study” card (instead of one card
    per DICOM file/series), even when a study has dozens of instances.
  • Imaging Report form (forms/imaging_report) — lets clinical
    staff upload DICOM files, images, PDFs, or a ZIP archive containing
    a full multi-series study
    , and have everything pushed to an
    Orthanc PACS server automatically. Each file is uploaded
    individually and force-reassigned to a deterministic
    StudyInstanceUID tied to the procedure order, so a whole study
    (however many folders/series the ZIP contains) always lands as one
    coherent study in Orthanc/OHIF.
  • Category mapping admin screen — instead of hardcoding which
    OpenEMR document categories count as “imaging” vs “laboratory” (every
    clinic’s category tree looks different), there’s a small admin screen
    backed by its own table (express_portal_category_mapping). You only
    need to mark the root category of a branch (e.g. “Dental Imaging”)
    and every child category inherits the classification automatically by
    walking up categories.parent — no core OpenEMR tables or files are
    altered for this.

Stack / dependencies

  • OpenEMR (developed/tested against a recent 7.x-line build)
  • Orthanc (with the DICOMweb plugin) as the PACS server — installed
    natively on Ubuntu via the official Orthanc apt repository (not
    Docker)
  • OHIF Viewer (v3) — deployed via Docker, pointed at Orthanc’s
    DICOMweb endpoint
  • No third-party PHP packages beyond what OpenEMR already ships with

Core file changes (important, please read)

To make all of this work end-to-end, I also had to make small
modifications to two OpenEMR core files: the Procedure Provider
screen and the Orders form (needed so a PACS provider can be
configured/selected the same way a lab provider is, and so orders can
carry the extra imaging/PACS context this module needs). These are
not module-only additions — they touch existing core OpenEMR code.

To keep this as transparent and low-risk as possible for anyone
installing this, the repo includes a patches/ folder with the exact
diffs against core, rather than silently overwriting files. Please
review those diffs before applying them, and expect to have to
re-apply/re-check them after upgrading OpenEMR core, since they aren’t
guaranteed to survive an update untouched.

Status & feedback welcome

This is still evolving — built for my own clinics in Argentina, but
designed so category mapping and PACS provider config (procedure_providers)
aren’t hardcoded, so it should adapt to other setups. I’d genuinely
appreciate feedback, bug reports, or PRs, especially around:

  • Edge cases in ZIP/DICOM upload (multi-study ZIPs, unusual folder
    structures)
  • Whether the Orthanc/OHIF integration pattern (per-file upload +
    forced StudyInstanceUID reassignment) makes sense vs. other
    approaches people have used

One heads-up: since this started as an internal tool for
Spanish-speaking clinics, some UI strings and code comments are still
in Spanish
— apologies in advance, happy to work through translating
the remaining bits if there’s interest in using this more broadly.

Thanks for reading, and thanks to this community for OpenEMR itself —
none of this would exist without it.


In spanish

Hola a todos,

Quiero compartir un proyecto que vengo construyendo sobre OpenEMR:
Patient Express Portal — un portal de paciente liviano y “express”,
enfocado en las dos cosas que los pacientes más consultan: resultados
de laboratorio y estudios de imágenes. Convive junto al portal nativo
de OpenEMR, no lo reemplaza — tiene un botón de un clic para saltar al
portal nativo completo cuando se necesita más profundidad.

Repositorio: GitHub - luisuriarte/patient-express-portal · GitHub

Qué hace

  • Pestaña de Laboratorio — trae datos de procedure_order/
    procedure_report (agrupados por encuentro), y además muestra
    informes de laboratorio escaneados (PDF/JPG) que fueron subidos como
    documentos comunes de OpenEMR (no a través de una orden de
    procedimiento). Si un documento escaneado comparte encuentro con un
    panel de laboratorio real, se fusiona en la misma tarjeta; si no,
    aparece como su propia tarjeta de “Documento de Laboratorio”.
  • Pestaña de Imágenes — lista estudios DICOM e imágenes/PDFs
    no-DICOM (radiografías, fotos dentales, etc.), con visualización
    inline para imágenes/PDFs y un link directo a un visor OHIF para
    los estudios DICOM. Los documentos que comparten el mismo
    StudyInstanceUID se agrupan automáticamente en una sola tarjeta de
    “Estudio” (en vez de una tarjeta por archivo/serie DICOM), incluso
    cuando un estudio tiene decenas de instancias.
  • Formulario de Informe de Imágenes (forms/imaging_report) —
    permite que el personal clínico suba archivos DICOM, imágenes, PDFs,
    o un archivo ZIP con un estudio completo de varias series, y que
    todo se suba automáticamente a un servidor PACS Orthanc. Cada
    archivo se sube individualmente y se reasigna por la fuerza a un
    StudyInstanceUID determinístico ligado a la orden de procedimiento,
    de modo que un estudio completo (sin importar cuántas carpetas/series
    traiga el ZIP) siempre termine como un único estudio coherente en
    Orthanc/OHIF.
  • Pantalla de administración de mapeo de categorías — en vez de
    hardcodear qué categorías de documentos de OpenEMR cuentan como
    “imágenes” o “laboratorio” (el árbol de categorías de cada clínica es
    distinto), hay una pequeña pantalla de administración respaldada por
    su propia tabla (express_portal_category_mapping). Solo hace falta
    marcar la categoría raíz de una rama (por ejemplo, “Dental Imaging”)
    y todas las categorías hijas heredan la clasificación automáticamente
    subiendo por categories.parent — no se altera ninguna tabla ni
    archivo del núcleo de OpenEMR para esto.

Stack / dependencias

  • OpenEMR (desarrollado/probado contra una build reciente de la línea 7.x)
  • Orthanc (con el plugin DICOMweb) como servidor PACS — instalado
    de forma nativa en Ubuntu vía el repositorio apt oficial de Orthanc
    (no Docker)
  • OHIF Viewer (v3) — desplegado vía Docker, apuntando al endpoint
    DICOMweb de Orthanc
  • Sin paquetes PHP de terceros más allá de lo que ya trae OpenEMR

Cambios en archivos del núcleo (importante, por favor leer)

Para que todo esto funcione de punta a punta, también tuve que hacer
pequeñas modificaciones a dos archivos del núcleo de OpenEMR: la
pantalla de Proveedores de Procedimiento y el formulario de
Órdenes (necesario para poder configurar/elegir un proveedor PACS
de la misma forma que un proveedor de laboratorio, y para que las
órdenes puedan llevar el contexto extra de imágenes/PACS que necesita
este módulo). No son solo agregados del módulo — tocan código existente
del núcleo de OpenEMR.

Para que esto sea lo más transparente y de bajo riesgo posible para
quien lo instale, el repositorio incluye una carpeta patches/ con los
diffs exactos contra el núcleo, en vez de sobrescribir archivos en
silencio. Por favor revisen esos diffs antes de aplicarlos, y tengan en
cuenta que probablemente haya que volver a aplicarlos/revisarlos
después de actualizar el núcleo de OpenEMR, ya que no está garantizado
que sobrevivan intactos a una actualización.

Estado y feedback bienvenido

Todavía está en evolución — lo construí para mis propias clínicas en
Argentina, pero está diseñado para que el mapeo de categorías y la
configuración del proveedor PACS (procedure_providers) no estén
hardcodeados, así que debería adaptarse a otras configuraciones.
Agradezco de verdad feedback, reportes de bugs, o PRs, especialmente
sobre:

  • Casos borde en la subida de ZIP/DICOM (ZIPs con múltiples estudios,
    estructuras de carpetas poco comunes)
  • Si el patrón de integración con Orthanc/OHIF (subida archivo por
    archivo + reasignación forzada de StudyInstanceUID) tiene sentido
    frente a otros enfoques que haya usado la comunidad

Un aviso: como esto arrancó como una herramienta interna para clínicas
de habla hispana, todavía hay textos de la interfaz y comentarios de
código en español
— mis disculpas de antemano, y con gusto voy
traduciendo lo que falte si hay interés en usarlo más ampliamente.

Gracias por leer, y gracias a esta comunidad por OpenEMR en sí mismo —
nada de esto existiría sin eso.

Luis.

1 Like

Patient Express Portal: https://demoexpress.origen.ar (patient login: aguilar@origen.ar / 4rG3ntin4)

1 Like

Hi Luis,
Have you looked at my DICOM study zip upload handling in patient documents?
I still need a refactor of my DICOM viewer however I’d love to see Orthanc/OHIF integration in openemr! hint lol

Images are uploaded via a form that must be installed in OpenEMR, located in the Forms folder, called imaging_report. This form is used to upload images and generate an image report. Simultaneously, it sends the images to the PACS server (Orthanc).

  1. Input: upload.php (AJAX from new.php)
  • The form sends the file to upload.php (forms/imaging_report/upload.php:108-116).

  • Depending on the extension: .zip → imaging_upload_zip(), any other (.dcm, .jpg, .pdf…) → imaging_upload_document().

  • Before uploading, the form validates CSRF, PID (active session), and file type/size (50 MB max, values ​​allowed in imaging_upload_functions.php:70-81). 2. The Study ID: It is generated deterministically for each order:

imaging_study_uid_for_order($procedureOrderId, $providerId) (imaging_upload_functions.php:124-127):
1.2.840.113619.2.55.<procedure_order_id>.<provider_id>
The entire series uploaded to the same order ends up in a single email with the StudyInstanceUID in Orthanc. This UID is stored in form_imaging_report_images.study_instance_uid and is subsequently used by the OHIF viewer (Imaging.php constructs the viewer_url with it).

  1. Saved in documents (OpenEMR)
    imaging_upload_document() (imaging_upload_functions.php:220):

  2. Resolves the destination category by modality (imaging_upload_category_id, 145 — “Diagnostic Imaging” → subcategory “Magnetic Resonance Imaging”/“Computed Tomography”).

  3. $doc->createDocument(pid, categoryId, name, mime, content, …, procedureOrderId, ‘procedure_order’) → creates a row in documents + a physical file in sites/default/documents// (:244-258).

  4. Updates documents.encounter_id if there is an active encounter (:265-267). 4. Resolve the PACS provider for the order: PacsProvider::resolveForOrder($procedureOrderId) reads procedure_order.lab_id → procedure_providers (:270).

  5. Send to Orthanc (PACS), according to file type:

  • Native DICOM (.dcm): PacsService::uploadNativeDicom() — POST /instances with the binary (PacsService.php:81-103). Orthanc responds with the internal IDs (mapping in the response): ID (instance), ParentStudy, ParentSeries. Then modifyInstance() — POST /instances//modify with Replace: StudyInstanceUID= + Force: true (:132-156) → reassigns the study to group it with the rest of the order. If the modify command fails, it retains the original study and saves those IDs plus the actual UID (imaging_upload_functions.php:291-324).

  • Image (JPG/PNG/WEBP): convert it to DICOM using uploadImageAsDicom() — POST /tools/create-dicom with tags constructed from patient_data + the StudyInstanceUID from the order (PacsService.php:164-197, tags in imaging_build_dicom_tags, pp. 88-118).

  • PDF: uploadEncapsulatedPdf() — POST /tools/create-dicom with Content: data:application/pdf;base64,… and Parent: <orthanc_study_id> to attach it to the study from the order (pp. 207-223).

  1. Compressed ZIP (the “compressed DICOM” case/complete studies)
    imaging_upload_zip() (imaging_upload_functions.php:411):
  • Unzips ZipArchive to temp; the ZIP file itself is not sent to the PACS.

  • Iterates through each internal file, creating a $_FILES block for each and calling imaging_upload_document() with the same path (document + PACS + log).

  • DICOM detection by magic bytes “DICM” at offset 128 (:455) — supports DICOM files without .dcm extensions, typical of PACS exports.

  • Skips non-image files (.DS_Store, Thumbs.db, etc.).

  • Since each native instance is reassigned to the command (step 4), all folders/series within the ZIP file are grouped into a single study.

  • The ZIP file can contain 100+ instances → set_time_limit(0) (:432). A single file failure does not stop the batch; it ultimately reports count_ok/count_fail.

  1. Log: form_imaging_report_images
    Each file (individual or ZIP) inserts a row with study_instance_uid, pacs_instance_id, pacs_series_id, pacs_study_id, document_id, provider_id, status (uploaded / failed / skipped), and DICOM modality (:351-373). This table feeds:
  • the “Uploaded documents” list of the form (imaging_get_report_images),
  • and, via study_instance_uid, the matching with the OHIF listener’s study cards in the portal.

Use version 8.2.0 for this

Patient Express Portal — Quick Guide

Patient Express Portal is not an OpenEMR module in the traditional sense — it’s a folder that you place in the OpenEMR root. Inside it there’s another folder, forms/imaging_report, which is a standard OpenEMR form and needs to be installed as such.

Installation

  1. Run the SQL script in patch/sql. It adds two fields to procedure_order and two fields to procedure_providers — required for the module to work.
  2. There’s also a second SQL script, images_procedures.sql, which preloads a set of imaging procedures.

How it works

First, set up the provider with user credentials, the PACS server (in this case Orthanc), and the viewer. You can use one of Orthanc’s own viewers (e.g., Stone Viewer), and you’ll also configure the connection details for OHIF Viewer.

Create the order as usual, but select procedure type Imaging, along with Requesting Service and Anatomic Region. These last two fields are preloaded via the table.sql script that ships with the imaging_report form (covered below).

The order search filters to show only imaging procedures.

The imaging_report form is where the study report is written. It comes with built-in templates, located in forms/imaging_report/js/templates.js.

The first step in the form is selecting the order number, which auto-populates the order data.

The form also handles uploading images — DICOM, JPG, or PNG. DICOM files can be uploaded compressed (as a ZIP); the module recursively scans for DICOM files inside and extracts study metadata automatically.

You first choose which folder to upload to. If “Also send to PACS server” is enabled, the images are sent to Orthanc either as a ZIP or one file at a time — since Orthanc supports ZIP uploads, sending compressed is more efficient. Note that in OpenEMR, documents are always stored individually regardless.

Next, complete the report.

When you save, a PDF report is generated automatically and stored in the same document folder as the uploaded images.

Patient Portal Access

To access Patient Express Portal, the patient must already have access to the standard OpenEMR patient portal — Express Portal uses the same username and password as the native portal.

From here, patients can use either the express portal or the native OpenEMR portal.

The portal has three tabs: one for lab results, one for imaging, and one that links directly to the native OpenEMR portal (no need to log in again — it goes straight through).

In the Lab tab: if a lab result (image or PDF) has been uploaded to the patient’s OpenEMR documents, Express Portal shows an orange View Document button. If the result comes from a procedure instead, a View/Download PDF button opens the generated report in a new browser tab. Results are grouped by procedure.

In the Imaging tab, images from the patient’s OpenEMR documents are shown grouped by encounter; DICOM images are grouped by study number instead. Clicking a DICOM study opens it in the OHIF Viewer, while PDF, PNG, or JPG files open directly in a new browser tab.

Each generated PDF image report also includes a QR code at the bottom center; scanning it opens the corresponding viewer URL directly.

Live demo