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

**URL:** <https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035>\
**Category:** OpenEMR Development\
**Created:** [September 7, 2026, 12:58am UTC](https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035 "2026-09-07T00:58:21Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![luisuriarte](https://community.open-emr.org/user_avatar/community.open-emr.org/luisuriarte/32/15503_2.png) [@luisuriarte](https://community.open-emr.org/u/luisuriarte)\
**Post date:** [September 7, 2026, 12:58am UTC](https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035/1 "2026-09-07T00:58:21Z")

</div>

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](https://github.com/luisuriarte/patient-express-portal)

### 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](https://github.com/luisuriarte/patient-express-portal)

### 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.

---

<div class="post-metadata">

**Author:** ![luisuriarte](https://community.open-emr.org/user_avatar/community.open-emr.org/luisuriarte/32/15503_2.png) [@luisuriarte](https://community.open-emr.org/u/luisuriarte)\
**Post date:** [September 7, 2026, 10:11am UTC](https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035/2 "2026-09-07T10:11:52Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![sjpadgett](https://community.open-emr.org/user_avatar/community.open-emr.org/sjpadgett/32/21623_2.png) [@sjpadgett](https://community.open-emr.org/u/sjpadgett)\
**Post date:** [September 7, 2026, 10:22am UTC](https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035/3 "2026-09-07T10:22:26Z")

</div>

> [@luisuriarte](#):
>
> - Edge cases in ZIP/DICOM upload (multi-study ZIPs, unusual folder  
> structures)

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

---

<div class="post-metadata">

**Author:** ![luisuriarte](https://community.open-emr.org/user_avatar/community.open-emr.org/luisuriarte/32/15503_2.png) [@luisuriarte](https://community.open-emr.org/u/luisuriarte)\
**Post date:** [September 7, 2026, 12:24pm UTC](https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035/4 "2026-09-07T12:24:44Z")

</div>

Images are uploaded via a form that must be installed in OpenEMR, located in the Forms folder, called [`imaging_repor`](https://github.com/luisuriarte/patient-express-portal/tree/main/forms/imaging_report)`t`. 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

---

<div class="post-metadata">

**Author:** ![luisuriarte](https://community.open-emr.org/user_avatar/community.open-emr.org/luisuriarte/32/15503_2.png) [@luisuriarte](https://community.open-emr.org/u/luisuriarte)\
**Post date:** [September 18, 2026, 10:57pm UTC](https://community.open-emr.org/t/patient-express-portal-a-lightweight-lab-imaging-results-portal-orthanc-ohif-pacs-integration-for-openemr/27035/5 "2026-09-18T22:57:12Z")

</div>

**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.

 ![imaging01](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/8/8/889b4048f4332701560386460cde47d593a97cd5.png)

 ![imaging02](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/8/9/89934cee595ed3e7906f260bc52491d105866748.png)

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).

 ![imaging04](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/c/3/c34c08d4ae912ac96e398b625df3c621d576271f.png)

The order search filters to show only imaging procedures.

 ![imaging05](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/6/6/66f2adbb3aaba2589ad4bfe2b96a1e9ed5346452.png)

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`.

 ![imaging03](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/9/a/9a285d5201a342b8ee30e8f91c73151223ec70a5.png)

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

 ![imaging06](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/e/d/ed88590aa13cdeeed2735278aa39840080a5f620.png)

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.

 ![imaging07](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/8/1/8126acc9f519b631d817863166f7d7b80ebd8770.png)

Next, complete the report.

 ![imaging08](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/4/3/43f3d66a8afcd9f57cd6e3464fb8d2fc8500e677.png)

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

 ![imaging09](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/7/0/70e0bd4599d37cda05f6d234f5aee01eba49d111.png)

 ![imaging10](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/0/b/0b29784fcc067144683237630a2701f1377cf746.png)

**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.

 ![imaging11](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/1/f/1f6ed32898c644a1959676b7b2c3bec74931ea83.png)

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

 ![imaging12](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/9/3/9382213d4d450ce418e460a3d65b87172ab5e800.png)

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).

 ![imaging13](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/d/4/d4c2c136033b1070e0a23063a225ab2e736ae399.png)

 ![imaging14](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/7/5/755c03f730e660e218752385dc951572c6b78dbd.png)

 ![imaging15](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/b/8/b80df45ce50e5897d4056cb7b731a7a2f8eceadb.jpeg)

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.

 ![imaging16](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/f/3/f3175d097f5511354987098bd9f18d73e71833d6.png)

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.

 ![imaging17](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/e/a/eab7ad42419256d8e1876bec68aaa01bf3f69b8c.jpeg)

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

 ![{046F7B46-5D34-415B-8DAB-BDF421B1E836}](https://discourse-uploads-openemr.s3.dualstack.us-east-1.amazonaws.com/original/3X/c/2/c2970a916ae7e476a23fa763b99ac4209b3fbeb9.png)

**Live demo**

- Patient portal: [https://demoexpress.origen.ar/](https://demoexpress.origen.ar/) — `aguilar@origen.ar` / `4rG3ntin4`
- OpenEMR demo instance: [https://demo.origen.ar/](https://demo.origen.ar/) — `clinician1` / `4rG3ntin4`
