Spaces:
Running
Running
Static MEDTRACE workstation: export, recorded responses and assets (part 5)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- GIF/Brain.gif +3 -0
- GIF/gif-01-timeline-3d.gif +3 -0
- GIF/gif-02-3d-rotate.gif +3 -0
- GIF/gif-03-show-me-why.gif +3 -0
- GIF/gif-04-slice-sync.gif +3 -0
- README.md +1321 -12
- api/patients.json +1 -0
- api/segmentations/009ae3f3-123e-4211-a6b9-019aef159d79.json +1 -0
- api/segmentations/02502ba4-5660-47b5-bffa-2670a34dfe7b.json +1 -0
- api/segmentations/026d5991-8bf0-4f22-9be3-a427cd017cdb.json +1 -0
- api/segmentations/059c5744-67cd-40d2-8d09-a44ccb563c16.json +1 -0
- api/segmentations/0664cdeb-7fac-4da4-a80a-a55c6723ae2e.json +1 -0
- api/segmentations/06f843bf-75d5-45a7-8630-56ea34a8acc0.json +1 -0
- api/segmentations/071261ee-f4de-4fd3-a188-c9c1111010c3.json +1 -0
- api/segmentations/0a15d8e4-145f-4cab-9167-8b13a9ff444f.json +1 -0
- api/segmentations/0cd019eb-859f-4687-8c74-fa0b7a36e4e4.json +1 -0
- api/segmentations/0f6cdab3-55b8-48fa-82ee-f8b17515cab4.json +1 -0
- api/segmentations/11d9fd96-2cf5-4a80-8a39-8ad4ce075a86.json +1 -0
- api/segmentations/1348e608-daf3-4473-a3f4-a8fb177d8534.json +1 -0
- api/segmentations/164949bd-e96c-4a5f-9003-18a43fe53ec6.json +1 -0
- api/segmentations/169e9c49-2ef2-4c22-9245-3dc3ad32b545.json +1 -0
- api/segmentations/1922d1cd-b1f5-441f-a0c2-ac2249440084.json +1 -0
- api/segmentations/1fec1237-f8c1-4712-a3d8-32d9a334f24e.json +1 -0
- api/segmentations/2463dc35-d8b6-4998-b056-c2e12d7271dd.json +1 -0
- api/segmentations/25404115-694f-41e1-8308-c135a6327bb1.json +1 -0
- api/segmentations/25a4be4c-f837-4009-be28-7f070e8e5dd4.json +1 -0
- api/segmentations/26166fcc-7850-41ae-bd87-de6d814a5c30.json +1 -0
- api/segmentations/26272b5b-80dd-4201-8001-3de172691935.json +1 -0
- api/segmentations/27e46bbd-9461-4c3d-8fde-414291f5bc72.json +1 -0
- api/segmentations/2a721993-2746-49fd-ab08-363c33187bc1.json +1 -0
- api/segmentations/2db7f389-59e8-4f51-892a-45aa64724a4d.json +1 -0
- api/segmentations/2f4c8574-9ea0-4334-b96f-29f82fd556d5.json +1 -0
- api/segmentations/311dcc60-732d-49ef-98e8-146429b35fa8.json +1 -0
- api/segmentations/33bf232d-ced9-495b-9152-eb6d149877d3.json +1 -0
- api/segmentations/3c71eff6-5c5c-4e23-a140-eeb7fc48c2aa.json +1 -0
- api/segmentations/3d314d09-a3c4-4ca8-be24-8d35bfcab419.json +1 -0
- api/segmentations/4815a12f-0b7c-467f-ba55-afcd5064394a.json +1 -0
- api/segmentations/495eb8a6-8859-4a12-9c11-b0b88f665c76.json +1 -0
- api/segmentations/49fff636-3d0c-4bbc-866d-fd34845e8803.json +1 -0
- api/segmentations/4f456804-060e-47f1-a54b-c714bfcf58dc.json +1 -0
- api/segmentations/50c7e8b0-067e-4c68-b476-5f78dfcd7a62.json +1 -0
- api/segmentations/53b1674c-bbf3-4bfd-aa8c-61517d151eb1.json +1 -0
- api/segmentations/5b364a21-a771-4f24-b34f-648c56159bb3.json +1 -0
- api/segmentations/62ec3a94-03fb-4477-860a-e4068c3f2bad.json +1 -0
- api/segmentations/6327e26a-4d95-4c6c-855d-f24ae5f91ce5.json +1 -0
- api/segmentations/63ad442f-86f8-4a05-abb1-2aa4b1f9f5d5.json +1 -0
- api/segmentations/6b329524-c37b-4581-9859-84a55b0f7d84.json +1 -0
- api/segmentations/71f83359-3065-4843-8c63-6d27998c4099.json +1 -0
- api/segmentations/7388fa67-3ca2-4c5a-a0bb-2b7dd8374ed5.json +1 -0
- api/segmentations/76fe4d95-b610-42dc-8276-4caa3700a292.json +1 -0
GIF/Brain.gif
ADDED
|
Git LFS Details
|
GIF/gif-01-timeline-3d.gif
ADDED
|
Git LFS Details
|
GIF/gif-02-3d-rotate.gif
ADDED
|
Git LFS Details
|
GIF/gif-03-show-me-why.gif
ADDED
|
Git LFS Details
|
GIF/gif-04-slice-sync.gif
ADDED
|
Git LFS Details
|
README.md
CHANGED
|
@@ -1,12 +1,1321 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
pinned:
|
| 8 |
-
license: mit
|
| 9 |
-
short_description:
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MEDTRACE
|
| 3 |
+
colorFrom: indigo
|
| 4 |
+
colorTo: blue
|
| 5 |
+
sdk: static
|
| 6 |
+
app_file: index.html
|
| 7 |
+
pinned: true
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: Longitudinal AI for brain-MRI disease evolution
|
| 10 |
+
tags:
|
| 11 |
+
- medical-imaging
|
| 12 |
+
- mri
|
| 13 |
+
- brain-tumour
|
| 14 |
+
- segmentation
|
| 15 |
+
- longitudinal
|
| 16 |
+
- cornerstone3d
|
| 17 |
+
- vtk
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
<div align="center">
|
| 21 |
+
|
| 22 |
+
<img src="GIF/Brain.gif" alt="MEDTRACE: longitudinal brain MRI analysis" width="460" />
|
| 23 |
+
|
| 24 |
+
# MEDTRACE
|
| 25 |
+
|
| 26 |
+
### Longitudinal AI for brain-MRI disease evolution
|
| 27 |
+
|
| 28 |
+
**An interactive, evidence-linked map of how a brain tumour changes over time.**
|
| 29 |
+
|
| 30 |
+
<br />
|
| 31 |
+
|
| 32 |
+

|
| 33 |
+

|
| 34 |
+

|
| 35 |
+

|
| 36 |
+
|
| 37 |
+

|
| 38 |
+

|
| 39 |
+

|
| 40 |
+

|
| 41 |
+

|
| 42 |
+

|
| 43 |
+

|
| 44 |
+

|
| 45 |
+

|
| 46 |
+

|
| 47 |
+
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
> [!WARNING]
|
| 51 |
+
> **Research prototype. Not a medical device.**
|
| 52 |
+
> MEDTRACE is not intended for diagnosis, treatment planning, or any clinical decision. It has
|
| 53 |
+
> not been clinically validated. It reports **measured change only**, never a diagnosis, grade,
|
| 54 |
+
> progression judgement, treatment recommendation, or prognosis. All clinical decisions remain
|
| 55 |
+
> with qualified healthcare professionals. Built exclusively on public, de-identified research
|
| 56 |
+
> data.
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
> [!NOTE]
|
| 60 |
+
> **What this hosted demo serves.** 28 glioblastoma patients from
|
| 61 |
+
> [RHUH-GBM](https://doi.org/10.7937/4545-c905), 3 timepoints each, under CC BY 4.0. The figures
|
| 62 |
+
> below describe the full local build across LUMIERE and BraTS 2023, which are covered by data use
|
| 63 |
+
> agreements that grant use but not redistribution, so they are not published here.
|
| 64 |
+
>
|
| 65 |
+
> There is no server. This is a Static Space: the interface and its recorded responses are served
|
| 66 |
+
> from this repository, imaging is range-fetched from the
|
| 67 |
+
> [dataset repository](https://huggingface.co/datasets/AIOmarRehan/medtrace-rhuh-gbm-derived), and
|
| 68 |
+
> both renderers run on your GPU. Nothing is computed on request.
|
| 69 |
+
>
|
| 70 |
+
> [Model](https://huggingface.co/AIOmarRehan/medtrace-brats-segresnet) ·
|
| 71 |
+
> [Dataset](https://huggingface.co/datasets/AIOmarRehan/medtrace-rhuh-gbm-derived) ·
|
| 72 |
+
> [Code](https://github.com/AIOmarRehan/medtrace)
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
<div align="center">
|
| 77 |
+
|
| 78 |
+
<table>
|
| 79 |
+
<tr>
|
| 80 |
+
<td align="center"><b>209</b><br />real patients</td>
|
| 81 |
+
<td align="center"><b>874</b><br />MRI studies</td>
|
| 82 |
+
<td align="center"><b>3,431</b><br />image series</td>
|
| 83 |
+
<td align="center"><b>1,153</b><br />disease observations</td>
|
| 84 |
+
</tr>
|
| 85 |
+
<tr>
|
| 86 |
+
<td align="center"><b>772</b><br />tracked lesions</td>
|
| 87 |
+
<td align="center"><b>6,499</b><br />measurements</td>
|
| 88 |
+
<td align="center"><b>599</b><br />atlas volumes</td>
|
| 89 |
+
<td align="center"><b>59.9</b><br />3D fps measured</td>
|
| 90 |
+
</tr>
|
| 91 |
+
</table>
|
| 92 |
+
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## Contents
|
| 98 |
+
|
| 99 |
+
<table>
|
| 100 |
+
<tr>
|
| 101 |
+
<td valign="top" width="33%">
|
| 102 |
+
|
| 103 |
+
**Understanding it**
|
| 104 |
+
- [The clinical problem](#the-clinical-problem)
|
| 105 |
+
- [What MEDTRACE answers](#what-medtrace-answers)
|
| 106 |
+
- [The workstation](#the-workstation)
|
| 107 |
+
- [Analysis pipeline](#analysis-pipeline)
|
| 108 |
+
|
| 109 |
+
</td>
|
| 110 |
+
<td valign="top" width="33%">
|
| 111 |
+
|
| 112 |
+
**How it works**
|
| 113 |
+
- [Domain model](#domain-model-observations-not-images)
|
| 114 |
+
- [AI segmentation](#ai-segmentation)
|
| 115 |
+
- [Training notebook](#the-training-notebook)
|
| 116 |
+
- [Validation & results](#validation--results)
|
| 117 |
+
- [Measurement & change](#measurement-matching-and-change)
|
| 118 |
+
- [3D disease evolution](#3d-disease-evolution)
|
| 119 |
+
- [Evidence engine](#the-evidence-engine)
|
| 120 |
+
|
| 121 |
+
</td>
|
| 122 |
+
<td valign="top" width="33%">
|
| 123 |
+
|
| 124 |
+
**Running it**
|
| 125 |
+
- [Architecture](#architecture)
|
| 126 |
+
- [Technology stack](#technology-stack)
|
| 127 |
+
- [Datasets](#datasets)
|
| 128 |
+
- [Verification](#verification)
|
| 129 |
+
- [Getting started](#getting-started)
|
| 130 |
+
|
| 131 |
+
</td>
|
| 132 |
+
</tr>
|
| 133 |
+
</table>
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## The clinical problem
|
| 138 |
+
|
| 139 |
+
A patient with a brain tumour is imaged repeatedly: before surgery, after surgery, during
|
| 140 |
+
radiotherapy and chemotherapy, then at follow-up for years. **Clinical decisions are made by
|
| 141 |
+
comparing these examinations, not by reading any one of them.**
|
| 142 |
+
|
| 143 |
+
Today that comparison is largely manual:
|
| 144 |
+
|
| 145 |
+
```
|
| 146 |
+
radiologist opens prior study ──► reads previous report ──► scrolls both studies side by side
|
| 147 |
+
──► re-measures the lesion by hand ──► mentally reconstructs the patient's history
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
Most medical-imaging AI does not help, because it analyses **one scan at one moment**.
|
| 151 |
+
Longitudinal comparison is a recognised underdeveloped area of medical imaging AI, and it is
|
| 152 |
+
exactly where the clinical decision actually happens.
|
| 153 |
+
|
| 154 |
+
MEDTRACE exists to answer one question:
|
| 155 |
+
|
| 156 |
+
<div align="center">
|
| 157 |
+
|
| 158 |
+
### *What changed in this patient between examinations?*
|
| 159 |
+
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
---
|
| 163 |
+
|
| 164 |
+
## What MEDTRACE answers
|
| 165 |
+
|
| 166 |
+
Every screen exists to serve one of five questions. Nothing else is in scope.
|
| 167 |
+
|
| 168 |
+
| Question | How MEDTRACE answers it |
|
| 169 |
+
|---|---|
|
| 170 |
+
| **What changed?** | Quantified, with units and direction: volume, diameter, surface area, growth rate |
|
| 171 |
+
| **Where did it change?** | Highlighted in the image, per lesion, in 2D and 3D |
|
| 172 |
+
| **When did it change?** | Located on the disease timeline, with the interval in days |
|
| 173 |
+
| **How confident are we?** | Per pipeline stage, with the reason in plain language |
|
| 174 |
+
| **Why do you believe that?** | The evidence, one click away |
|
| 175 |
+
|
| 176 |
+
**The signature interaction is the time scrubber.** Dragging it moves imaging, segmentation,
|
| 177 |
+
measurements, clinical events, the 3D surface and the evidence panel together.
|
| 178 |
+
|
| 179 |
+
> [!NOTE]
|
| 180 |
+
> An observation without evidence **cannot exist** in MEDTRACE. This is enforced in the service
|
| 181 |
+
> layer, not by convention. It is why the system can always answer "why".
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## The workstation
|
| 186 |
+
|
| 187 |
+
<div align="center">
|
| 188 |
+
|
| 189 |
+
<img src="docs/screenshots/01-workstation.png" alt="MEDTRACE clinical workstation" width="100%" />
|
| 190 |
+
|
| 191 |
+
*The clinical workstation: patient timeline, synchronised prior/current comparison, measured
|
| 192 |
+
findings, and the AI evidence strip.*
|
| 193 |
+
|
| 194 |
+
</div>
|
| 195 |
+
|
| 196 |
+
<table>
|
| 197 |
+
<tr>
|
| 198 |
+
<td width="50%" valign="top">
|
| 199 |
+
|
| 200 |
+
### 2D comparison
|
| 201 |
+
|
| 202 |
+
<img src="docs/screenshots/02-2d-comparison.png" alt="Synchronised prior and current study" width="100%" />
|
| 203 |
+
|
| 204 |
+
Prior and current study side by side, **linked by fractional depth** rather than world
|
| 205 |
+
coordinates, because unregistered studies differ by tens of millimetres and copying a camera
|
| 206 |
+
between them can place it outside the other volume entirely.
|
| 207 |
+
|
| 208 |
+
<img src="GIF/gif-04-slice-sync.gif" alt="Scrolling one pane moves the other with it" width="100%" />
|
| 209 |
+
|
| 210 |
+
*Scroll one pane and the other follows.*
|
| 211 |
+
|
| 212 |
+
</td>
|
| 213 |
+
<td width="50%" valign="top">
|
| 214 |
+
|
| 215 |
+
### Segmentation overlay
|
| 216 |
+
|
| 217 |
+
<img src="docs/screenshots/03-segmentation-overlay.png" alt="Tumour mask overlaid on native image" width="100%" />
|
| 218 |
+
|
| 219 |
+
The tumour mask drawn in the **series' own voxel grid**, reoriented from the NIfTI affines with
|
| 220 |
+
no resampling. Outline by default, fill on demand.
|
| 221 |
+
|
| 222 |
+
</td>
|
| 223 |
+
</tr>
|
| 224 |
+
</table>
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## Analysis pipeline
|
| 229 |
+
|
| 230 |
+
Deliberately a pipeline of specialised stages, not one large model. **Every stage emits its
|
| 231 |
+
prediction, its confidence, its quality flags and its model version.**
|
| 232 |
+
|
| 233 |
+
```mermaid
|
| 234 |
+
%%{init: {"theme":"base","themeVariables":{"fontSize":"15px","textColor":"#1e293b","nodeTextColor":"#1e293b","lineColor":"#475569","edgeLabelBackground":"#ffffff"},"flowchart":{"nodeSpacing":34,"rankSpacing":50,"padding":10}}}%%
|
| 235 |
+
flowchart LR
|
| 236 |
+
A[("MRI studies")] --> B["Quality control"]
|
| 237 |
+
B --> C["Segmentation"]
|
| 238 |
+
C --> D["Lesion detection"]
|
| 239 |
+
D --> E["Registration"]
|
| 240 |
+
E --> F["Lesion matching"]
|
| 241 |
+
F --> G["Change detection"]
|
| 242 |
+
G --> H[("DiseaseObservation")]
|
| 243 |
+
H --> I["Disease timeline"]
|
| 244 |
+
H --> J["3D evolution map"]
|
| 245 |
+
H --> K["Evidence engine"]
|
| 246 |
+
|
| 247 |
+
classDef src fill:#e0f2fe,stroke:#0284c7,stroke-width:1px,color:#0c4a6e
|
| 248 |
+
classDef stage fill:#ffffff,stroke:#64748b,stroke-width:1px,color:#1e293b
|
| 249 |
+
classDef core fill:#ccfbf1,stroke:#0d9488,stroke-width:2px,color:#134e4a
|
| 250 |
+
classDef view fill:#e0e7ff,stroke:#4f46e5,stroke-width:1px,color:#312e81
|
| 251 |
+
|
| 252 |
+
class A src
|
| 253 |
+
class B,C,D,E,F,G stage
|
| 254 |
+
class H core
|
| 255 |
+
class I,J,K view
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
<details>
|
| 259 |
+
<summary><b>What each stage actually does</b></summary>
|
| 260 |
+
|
| 261 |
+
<br />
|
| 262 |
+
|
| 263 |
+
| Stage | Implementation | Output |
|
| 264 |
+
|---|---|---|
|
| 265 |
+
| **Quality control** | Rule-based validation of sequences, voxel spacing, orientation, protocol drift between timepoints | `QualityFlag[]` per study |
|
| 266 |
+
| **Segmentation** | MONAI `SegResNet`, 18.8 M parameters, 4 sequences in → 3 overlapping compartments out | Tumour compartment masks |
|
| 267 |
+
| **Lesion detection** | Connected-component extraction per compartment, with a measurability floor | `Lesion` candidates |
|
| 268 |
+
| **Registration** | SimpleITK rigid then affine; atlas-space masks need none, native-space ones do | Transform + score |
|
| 269 |
+
| **Lesion matching** | Similarity scoring over overlap (Dice), centroid distance, proximity and volume similarity | `Lesion` identity across time |
|
| 270 |
+
| **Change detection** | Volumetric and morphological comparison with uncertainty propagation | Absolute + relative change, growth rate |
|
| 271 |
+
| **Trajectory** | Longitudinal feature vectors per lesion across all timepoints | Per-lesion history |
|
| 272 |
+
| **Evidence engine** | Structured findings → validated answer → linked evidence | `AIObservation` + `EvidenceItem[]` |
|
| 273 |
+
|
| 274 |
+
</details>
|
| 275 |
+
|
| 276 |
+
---
|
| 277 |
+
|
| 278 |
+
## Domain model: observations, not images
|
| 279 |
+
|
| 280 |
+
The core domain object is **`DiseaseObservation`**, not `MRI`. The timeline, the analytics, the
|
| 281 |
+
AI answers and the audit trail are all *views over observations*.
|
| 282 |
+
|
| 283 |
+
```mermaid
|
| 284 |
+
%%{init: {"theme":"base","themeVariables":{"fontSize":"15px","textColor":"#1e293b","nodeTextColor":"#1e293b","lineColor":"#475569","edgeLabelBackground":"#ffffff"}}}%%
|
| 285 |
+
erDiagram
|
| 286 |
+
PATIENT ||--o{ STUDY : "timepoints"
|
| 287 |
+
PATIENT ||--o{ LESION : "identity"
|
| 288 |
+
PATIENT ||--o{ CLINICAL_EVENT : "treatment"
|
| 289 |
+
PATIENT ||--o{ AI_OBSERVATION : "answers"
|
| 290 |
+
STUDY ||--o{ SERIES : "sequences"
|
| 291 |
+
STUDY ||--o| STUDY_QUALITY : "validation"
|
| 292 |
+
STUDY ||--o{ SEGMENTATION : "masks"
|
| 293 |
+
STUDY ||--o{ LESION_OBSERVATION : "observed in"
|
| 294 |
+
LESION ||--o{ LESION_OBSERVATION : "observed at"
|
| 295 |
+
LESION_OBSERVATION ||--o{ MEASUREMENT : "quantified by"
|
| 296 |
+
LESION_OBSERVATION ||--o{ EVIDENCE_ITEM : "supports"
|
| 297 |
+
AI_OBSERVATION ||--o{ EVIDENCE_ITEM : "must cite"
|
| 298 |
+
MODEL_RUN ||--o{ LESION_OBSERVATION : "produced"
|
| 299 |
+
MODEL_RUN ||--o{ AI_OBSERVATION : "produced"
|
| 300 |
+
```
|
| 301 |
+
|
| 302 |
+
A **`Lesion` belongs to a patient, not a study**, and that is what makes a per-lesion trajectory
|
| 303 |
+
possible, and it is precisely what a conventional viewer or segmentation tool does not provide.
|
| 304 |
+
|
| 305 |
+
<details>
|
| 306 |
+
<summary><b>The 14-table schema</b></summary>
|
| 307 |
+
|
| 308 |
+
<br />
|
| 309 |
+
|
| 310 |
+
`patients` · `studies` · `series` · `study_quality` · `registrations` · `segmentations` ·
|
| 311 |
+
`lesions` · `lesion_observations` · `measurements` · `clinical_events` · `model_runs` ·
|
| 312 |
+
`ai_observations` · `evidence_items` · `audit_events`
|
| 313 |
+
|
| 314 |
+
Managed with SQLAlchemy + Alembic. PostgreSQL stores **metadata only**. Imaging stays on the
|
| 315 |
+
filesystem and is served through a path-allowlisted endpoint, never by a database blob.
|
| 316 |
+
|
| 317 |
+
</details>
|
| 318 |
+
|
| 319 |
+
---
|
| 320 |
+
|
| 321 |
+
## AI segmentation
|
| 322 |
+
|
| 323 |
+
A 3D `SegResNet` trained from scratch on BraTS 2023 GLI, on a Kaggle Tesla T4.
|
| 324 |
+
|
| 325 |
+
```mermaid
|
| 326 |
+
%%{init: {"theme":"base","themeVariables":{"fontSize":"15px","clusterBkg":"#f1f5f9","clusterBorder":"#94a3b8","textColor":"#1e293b","nodeTextColor":"#1e293b","lineColor":"#475569","edgeLabelBackground":"#ffffff"},"flowchart":{"nodeSpacing":34,"rankSpacing":58,"padding":10}}}%%
|
| 327 |
+
flowchart LR
|
| 328 |
+
subgraph IN ["4 co-registered sequences"]
|
| 329 |
+
direction TB
|
| 330 |
+
A1["T1c"]
|
| 331 |
+
A2["T1n"]
|
| 332 |
+
A3["T2-FLAIR"]
|
| 333 |
+
A4["T2w"]
|
| 334 |
+
end
|
| 335 |
+
|
| 336 |
+
B["Normalise"]
|
| 337 |
+
C["Crop"]
|
| 338 |
+
D["SegResNet 3D"]
|
| 339 |
+
E["Sliding window"]
|
| 340 |
+
|
| 341 |
+
subgraph OUT ["3 overlapping compartments"]
|
| 342 |
+
direction TB
|
| 343 |
+
F1["TC"]
|
| 344 |
+
F2["WT"]
|
| 345 |
+
F3["ET"]
|
| 346 |
+
end
|
| 347 |
+
|
| 348 |
+
G["Threshold"]
|
| 349 |
+
H["BraTS labels"]
|
| 350 |
+
|
| 351 |
+
A1 --> B
|
| 352 |
+
A2 --> B
|
| 353 |
+
A3 --> B
|
| 354 |
+
A4 --> B
|
| 355 |
+
B --> C --> D --> E
|
| 356 |
+
E --> F1
|
| 357 |
+
E --> F2
|
| 358 |
+
E --> F3
|
| 359 |
+
F1 --> G
|
| 360 |
+
F2 --> G
|
| 361 |
+
F3 --> G
|
| 362 |
+
G --> H
|
| 363 |
+
|
| 364 |
+
classDef seq fill:#e0f2fe,stroke:#0284c7,stroke-width:1px,color:#0c4a6e
|
| 365 |
+
classDef step fill:#ffffff,stroke:#64748b,stroke-width:1px,color:#1e293b
|
| 366 |
+
classDef model fill:#ccfbf1,stroke:#0d9488,stroke-width:2px,color:#134e4a
|
| 367 |
+
classDef out fill:#ede9fe,stroke:#7c3aed,stroke-width:1px,color:#4c1d95
|
| 368 |
+
|
| 369 |
+
class A1,A2,A3,A4 seq
|
| 370 |
+
class B,C,E,G,H step
|
| 371 |
+
class D model
|
| 372 |
+
class F1,F2,F3 out
|
| 373 |
+
```
|
| 374 |
+
|
| 375 |
+
| Step | What happens |
|
| 376 |
+
|---|---|
|
| 377 |
+
| **T1c · T1n · T2-FLAIR · T2w** | Four sequences, in **this exact channel order** |
|
| 378 |
+
| **Normalise** | Per case, per channel, zero mean unit variance over **non-zero voxels only** |
|
| 379 |
+
| **Crop** | To the non-zero bounding box of the summed channels, 4-voxel margin |
|
| 380 |
+
| **SegResNet 3D** | MONAI `SegResNet`, 18,798,627 parameters, 32 init filters, blocks down `[1,2,2,4]` |
|
| 381 |
+
| **Sliding window** | 128³ patches, 0.5 overlap, gaussian blending |
|
| 382 |
+
| **TC · WT · ET** | Independent sigmoid per channel, so the three compartments **overlap** rather than compete |
|
| 383 |
+
| **Threshold** | 0.5, plus an enhancing-tumour floor of 200 voxels |
|
| 384 |
+
| **BraTS labels** | Written WT→2, then TC→1, then ET→3, in that order |
|
| 385 |
+
|
| 386 |
+
> [!IMPORTANT]
|
| 387 |
+
> **Channel order is not recoverable from the weights.** `[t1c, t1n, t2f, t2w]` is part of the
|
| 388 |
+
> model contract, recorded in `ml/artifacts/model_card.json`. Wrong order → wrong output → *no
|
| 389 |
+
> error*. This is the kind of silent failure the model card exists to prevent.
|
| 390 |
+
|
| 391 |
+
### Held-out test performance
|
| 392 |
+
|
| 393 |
+
**186 cases from 169 subjects never seen in training or tuning.** Splits are computed at
|
| 394 |
+
**subject level**, because BraTS contains 1,251 cases from only 1,133 subjects, so a random split over
|
| 395 |
+
cases would leak the same patient into train and test.
|
| 396 |
+
|
| 397 |
+
| Compartment | Dice (mean) | Dice (median) | HD95 (median) | Sensitivity | Precision | Dice (mean) on a 0 to 1 scale |
|
| 398 |
+
|---|---:|---:|---:|---:|---:|:---|
|
| 399 |
+
| **Whole tumour** | **0.9216** | 0.9489 | 2.45 mm | 0.9248 | 0.9236 | ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱ |
|
| 400 |
+
| **Tumour core** | **0.9078** | 0.9563 | 2.00 mm | 0.9166 | 0.9170 | ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱ |
|
| 401 |
+
| **Enhancing tumour** | **0.8520** | 0.8984 | 1.41 mm | 0.8833 | 0.8487 | ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱ |
|
| 402 |
+
| **Mean of the three** | **0.8938** | - | - | - | - | ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱ |
|
| 403 |
+
|
| 404 |
+
> [!TIP]
|
| 405 |
+
> **Test scored higher than validation** (0.8938 vs 0.8899 mean Dice). Since both epoch
|
| 406 |
+
> selection and post-processing tuning used the validation split, the validation figures are
|
| 407 |
+
> optimistic by construction, so the test figures are the honest ones, and they did not degrade.
|
| 408 |
+
|
| 409 |
+
<details>
|
| 410 |
+
<summary><b>Training configuration and honest limitations</b></summary>
|
| 411 |
+
|
| 412 |
+
<br />
|
| 413 |
+
|
| 414 |
+
| Setting | Value |
|
| 415 |
+
|---|---|
|
| 416 |
+
| Architecture | `monai.networks.nets.SegResNet`, 3D, 32 init filters, blocks down `[1,2,2,4]` |
|
| 417 |
+
| Loss | `DiceFocalLoss(sigmoid=True, squared_pred=True, batch=True)` |
|
| 418 |
+
| Optimiser | AdamW, lr 2e-4, wd 1e-5, `CosineAnnealingLR` |
|
| 419 |
+
| Precision | AMP float16 on Tesla T4 |
|
| 420 |
+
| Patch sampling | 128³, 80% centred on whole tumour, 20% uniform |
|
| 421 |
+
| Augmentation | Random axis flips, intensity scale ±10%, intensity shift ±10% |
|
| 422 |
+
| Epochs | 37 of 60 completed (host RAM exhausted); **epoch 32 selected** on validation mean Dice |
|
| 423 |
+
| Seed | `20260813` for both split and training |
|
| 424 |
+
|
| 425 |
+
**Stated limitations** (from the model card):
|
| 426 |
+
|
| 427 |
+
- Trained on **pre-operative** adult glioma only. Post-treatment appearances, such as resection
|
| 428 |
+
cavities, radiation change, are not represented.
|
| 429 |
+
- Requires all four sequences. Behaviour with a missing sequence is untested.
|
| 430 |
+
- Assumes BraTS preprocessing: skull-stripped, co-registered, 1 mm isotropic.
|
| 431 |
+
- Measures **agreement with one annotation protocol on one dataset**. That is not a measure of
|
| 432 |
+
clinical accuracy.
|
| 433 |
+
|
| 434 |
+
</details>
|
| 435 |
+
|
| 436 |
+
---
|
| 437 |
+
|
| 438 |
+
## The training notebook
|
| 439 |
+
|
| 440 |
+
The model was trained in a single notebook on a Kaggle Tesla T4, and it is in the repository with
|
| 441 |
+
its outputs intact: [`ml/notebooks/brats_segmentation_training_output.ipynb`](ml/notebooks/brats_segmentation_training_output.ipynb).
|
| 442 |
+
GitHub renders it, so every number below can be traced to the cell that printed it. The clean
|
| 443 |
+
unexecuted version is [`brats_segmentation_training.ipynb`](ml/notebooks/brats_segmentation_training.ipynb).
|
| 444 |
+
|
| 445 |
+
Nineteen numbered stages, from configuration through to verifying the exported weights actually
|
| 446 |
+
load. Twenty-two code cells, twenty-one of them executed.
|
| 447 |
+
|
| 448 |
+
### What the data looked like before any model existed
|
| 449 |
+
|
| 450 |
+
<div align="center">
|
| 451 |
+
|
| 452 |
+
<img src="docs/notebook/01-tumour-volume-distribution.png" alt="Tumour volume distributions across the BraTS 2023 GLI training split" width="100%" />
|
| 453 |
+
|
| 454 |
+
</div>
|
| 455 |
+
|
| 456 |
+
Across 1,251 cases the whole tumour has a median volume of 89.3 cm3 and a range of 2.8 to 361.8
|
| 457 |
+
cm3. The compartments are far smaller: enhancing tumour has a median of 17.3 cm3, and its minimum
|
| 458 |
+
is **zero**, which is why the export applies a 200 voxel floor rather than reporting a
|
| 459 |
+
one-voxel enhancing region as a finding.
|
| 460 |
+
|
| 461 |
+
The single most consequential line the notebook printed:
|
| 462 |
+
|
| 463 |
+
```
|
| 464 |
+
tumour occupies 1.07% of all voxels
|
| 465 |
+
-> uniform random patches would be almost pure background; sampling must be biased
|
| 466 |
+
```
|
| 467 |
+
|
| 468 |
+
That measurement is the reason patch sampling is 80% centred on the whole tumour and 20% uniform.
|
| 469 |
+
It was not a hyperparameter guess.
|
| 470 |
+
|
| 471 |
+
<div align="center">
|
| 472 |
+
|
| 473 |
+
<img src="docs/notebook/02-sequences-and-labels.png" alt="The four co-registered sequences with the reference labels overlaid" width="100%" />
|
| 474 |
+
|
| 475 |
+
</div>
|
| 476 |
+
|
| 477 |
+
The four sequences for one case with the reference labels on T1C. Looking at the actual images is
|
| 478 |
+
how the channel order was confirmed, and channel order is not recoverable from the weights: get it
|
| 479 |
+
wrong and the model produces a plausible, wrong answer with no error anywhere.
|
| 480 |
+
|
| 481 |
+
### Training
|
| 482 |
+
|
| 483 |
+
<div align="center">
|
| 484 |
+
|
| 485 |
+
<img src="docs/notebook/03-training-curve.png" alt="Training loss and validation Dice per epoch, with the selected epoch marked" width="100%" />
|
| 486 |
+
|
| 487 |
+
</div>
|
| 488 |
+
|
| 489 |
+
Loss on the left, per-region validation Dice on the right, with the selected epoch marked. Thirty
|
| 490 |
+
seven of a planned sixty epochs completed before host RAM was exhausted, and **epoch 32 was
|
| 491 |
+
selected on mean validation Dice at 0.8867**. The curve is what justifies stopping there rather
|
| 492 |
+
than at the last epoch: validation had flattened well before the run ended.
|
| 493 |
+
|
| 494 |
+
### The test set, run once
|
| 495 |
+
|
| 496 |
+
<div align="center">
|
| 497 |
+
|
| 498 |
+
<img src="docs/notebook/04-test-dice-distribution.png" alt="Test Dice per region, and Dice against tumour size" width="100%" />
|
| 499 |
+
|
| 500 |
+
</div>
|
| 501 |
+
|
| 502 |
+
186 cases from 169 subjects, held out at subject level. Per-region Dice on the left, Dice against
|
| 503 |
+
tumour size on the right, and that right-hand panel is the honest one: agreement collapses on the
|
| 504 |
+
smallest tumours, where a few voxels of disagreement dominate the metric.
|
| 505 |
+
|
| 506 |
+
| Split comparison | Validation | Test | Gap |
|
| 507 |
+
|---|---:|---:|---:|
|
| 508 |
+
| Mean Dice | 0.8899 | **0.8938** | **-0.0039** |
|
| 509 |
+
|
| 510 |
+
The test score is marginally *higher* than validation. There is no overfitting to report, and the
|
| 511 |
+
generalisation gap is smaller than the run-to-run noise.
|
| 512 |
+
|
| 513 |
+
| Region | Cases below 0.5 Dice | Cases with empty ground truth |
|
| 514 |
+
|---|---:|---:|
|
| 515 |
+
| Whole tumour | 2 of 186 | 0 |
|
| 516 |
+
| Tumour core | 5 of 186 | 1 |
|
| 517 |
+
| Enhancing tumour | 8 of 186 | 5 |
|
| 518 |
+
|
| 519 |
+
### The cases it got wrong
|
| 520 |
+
|
| 521 |
+
Most projects show the best cases. The notebook prints the worst four, because those are the ones
|
| 522 |
+
that say something.
|
| 523 |
+
|
| 524 |
+
<table>
|
| 525 |
+
<tr>
|
| 526 |
+
<td width="50%" valign="top">
|
| 527 |
+
|
| 528 |
+
<img src="docs/notebook/05-worst-test-case-1.png" alt="Worst test case by whole-tumour Dice" width="100%" />
|
| 529 |
+
|
| 530 |
+
`BraTS-GLI-00675-001`, whole tumour Dice **0.000**, yet tumour core and enhancing tumour both
|
| 531 |
+
**1.000**. A whole tumour score of zero alongside perfect compartments is a labelling edge case,
|
| 532 |
+
not a model that cannot see the tumour.
|
| 533 |
+
|
| 534 |
+
</td>
|
| 535 |
+
<td width="50%" valign="top">
|
| 536 |
+
|
| 537 |
+
<img src="docs/notebook/06-worst-test-case-2.png" alt="Second worst test case by whole-tumour Dice" width="100%" />
|
| 538 |
+
|
| 539 |
+
`BraTS-GLI-00493-000`, whole tumour Dice **0.184** on 34,451 labelled voxels, while core reaches
|
| 540 |
+
0.924 and enhancing 0.883. The oedema boundary is the disagreement, which is the least
|
| 541 |
+
reproducible boundary between human annotators too.
|
| 542 |
+
|
| 543 |
+
</td>
|
| 544 |
+
</tr>
|
| 545 |
+
</table>
|
| 546 |
+
|
| 547 |
+
> [!NOTE]
|
| 548 |
+
> The worst case is where the enhancing floor and the quality gate earn their place. A study that
|
| 549 |
+
> produces a result like this is flagged rather than reported as a confident measurement, and
|
| 550 |
+
> nothing from this model reaches the measurement pipeline at all. See
|
| 551 |
+
> [Validation & results](#validation--results) for why.
|
| 552 |
+
|
| 553 |
+
Regenerate the figures from the notebook at any time:
|
| 554 |
+
|
| 555 |
+
```bash
|
| 556 |
+
make export-notebook-figures
|
| 557 |
+
```
|
| 558 |
+
|
| 559 |
+
## Validation & results
|
| 560 |
+
|
| 561 |
+
### The decision not to ship the model into the measurement pipeline
|
| 562 |
+
|
| 563 |
+
The trained model scores 0.894 mean Dice on held-out BraTS. It is **deliberately not** used for
|
| 564 |
+
MEDTRACE's measurements, and the reason is measurement, not caution.
|
| 565 |
+
|
| 566 |
+
Run against DeepBraTumIA on **12 randomly chosen real LUMIERE studies**
|
| 567 |
+
(`ml/scripts/compare_on_lumiere.py`):
|
| 568 |
+
|
| 569 |
+
| Region | Median Dice | Studies below 0.5 | Median Dice on a 0 to 1 scale |
|
| 570 |
+
|---|---:|---:|:---|
|
| 571 |
+
| Whole tumour | **0.923** | 0 of 12 | ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱ |
|
| 572 |
+
| Tumour core | 0.816 | 1 of 12 | ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱ |
|
| 573 |
+
| **Enhancing tumour** | **0.486** | **6 of 12** | ▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱ |
|
| 574 |
+
|
| 575 |
+
Splitting by how much enhancement is actually present shows this is not a uniform weakness:
|
| 576 |
+
|
| 577 |
+
| DeepBraTumIA enhancing volume | n | Median ET Dice | Our volume vs theirs |
|
| 578 |
+
|---|---:|---:|---:|
|
| 579 |
+
| Bulky, ≥ 5 cm³ | 5 | **0.861** | 1.03× |
|
| 580 |
+
| Small, < 5 cm³ | 7 | **0.193** | ~4× |
|
| 581 |
+
|
| 582 |
+
**The cause was predicted before the comparison was run.** BraTS is pre-operative glioma, where
|
| 583 |
+
enhancing tumour is a thick contrast-avid ring. LUMIERE is post-treatment: resection margins,
|
| 584 |
+
radiation change and post-surgical enhancement all enhance, and none of it appears in BraTS. The
|
| 585 |
+
model has never been shown a brain that has been operated on.
|
| 586 |
+
|
| 587 |
+
> [!CAUTION]
|
| 588 |
+
> Enhancing tumour is **the compartment MEDTRACE measures and reports change on**. Swapping the
|
| 589 |
+
> pipeline over would inflate every enhancing volume, worst on exactly the small lesions where a
|
| 590 |
+
> change of a few tenths of a cm³ decides whether progression is reported. So the pipeline keeps
|
| 591 |
+
> DeepBraTumIA's masks, and this comparison is documented as *agreement between two automated
|
| 592 |
+
> tools*, not as accuracy.
|
| 593 |
+
|
| 594 |
+
---
|
| 595 |
+
|
| 596 |
+
## Measurement, matching and change
|
| 597 |
+
|
| 598 |
+
### Measurement chosen by measurement
|
| 599 |
+
|
| 600 |
+
Surface area is computed by marching cubes over a **signed distance field**, not over the binary
|
| 601 |
+
mask directly. The estimator was selected by comparison against analytic shapes: it degrades far
|
| 602 |
+
more gracefully with anisotropic voxels, and this data spans **0.36 mm to 6.0 mm** slice spacing.
|
| 603 |
+
|
| 604 |
+
The reported surface area **is** the area of the mesh shipped to the 3D viewer, verified to
|
| 605 |
+
within 0.009% across 1,153 meshes, so the number in the findings panel and the surface on screen
|
| 606 |
+
cannot disagree.
|
| 607 |
+
|
| 608 |
+
### Cross-time lesion matching
|
| 609 |
+
|
| 610 |
+
```mermaid
|
| 611 |
+
%%{init: {"theme":"base","themeVariables":{"fontSize":"15px","textColor":"#1e293b","nodeTextColor":"#1e293b","lineColor":"#475569","edgeLabelBackground":"#ffffff"},"flowchart":{"nodeSpacing":40,"rankSpacing":55,"padding":10}}}%%
|
| 612 |
+
flowchart LR
|
| 613 |
+
P["Prior lesions"] --> S{"Score each pair"}
|
| 614 |
+
C["Current lesions"] --> S
|
| 615 |
+
|
| 616 |
+
S --> O["Overlap Dice"]
|
| 617 |
+
S --> D["Centroid distance"]
|
| 618 |
+
S --> X["Proximity"]
|
| 619 |
+
S --> V["Volume similarity"]
|
| 620 |
+
|
| 621 |
+
O --> M["Optimal assignment"]
|
| 622 |
+
D --> M
|
| 623 |
+
X --> M
|
| 624 |
+
V --> M
|
| 625 |
+
|
| 626 |
+
M --> R1["Matched"]
|
| 627 |
+
M --> R2["New lesion"]
|
| 628 |
+
M --> R3["Disappeared"]
|
| 629 |
+
M --> R4["Uncertain"]
|
| 630 |
+
|
| 631 |
+
classDef inp fill:#e0f2fe,stroke:#0284c7,stroke-width:1px,color:#0c4a6e
|
| 632 |
+
classDef comp fill:#ffffff,stroke:#64748b,stroke-width:1px,color:#1e293b
|
| 633 |
+
classDef dec fill:#ccfbf1,stroke:#0d9488,stroke-width:2px,color:#134e4a
|
| 634 |
+
classDef good fill:#dcfce7,stroke:#16a34a,stroke-width:1px,color:#14532d
|
| 635 |
+
classDef warn fill:#ffedd5,stroke:#ea580c,stroke-width:2px,color:#7c2d12
|
| 636 |
+
|
| 637 |
+
class P,C inp
|
| 638 |
+
class O,D,X,V comp
|
| 639 |
+
class S,M dec
|
| 640 |
+
class R1,R2,R3 good
|
| 641 |
+
class R4 warn
|
| 642 |
+
```
|
| 643 |
+
|
| 644 |
+
| Outcome | Meaning |
|
| 645 |
+
|---|---|
|
| 646 |
+
| **Matched** | Keeps the same `Lesion` id, so the trajectory continues |
|
| 647 |
+
| **New lesion** | An unmatched current lesion, so a new `Lesion` identity is created |
|
| 648 |
+
| **Disappeared** | An unmatched prior lesion, recorded as an absence, not silently dropped |
|
| 649 |
+
| **Uncertain** | `MATCH_UNCERTAIN`, `WEAK_SCORE` or `AMBIGUOUS_ALTERNATIVE`. The finding is de-emphasised and the 3D surface stays grey |
|
| 650 |
+
|
| 651 |
+
A doubtful correspondence is **never presented as a confident one**. `MATCH_UNCERTAIN`,
|
| 652 |
+
`WEAK_SCORE` and `AMBIGUOUS_ALTERNATIVE` de-emphasise the finding in the panel and force the 3D
|
| 653 |
+
surface to render grey rather than in a change colour, because a colour that says "growing" is
|
| 654 |
+
a claim, and it must not be made when the lesion it is compared against may be a different
|
| 655 |
+
lesion.
|
| 656 |
+
|
| 657 |
+
### Change with propagated uncertainty
|
| 658 |
+
|
| 659 |
+
<div align="center">
|
| 660 |
+
|
| 661 |
+
<img src="docs/screenshots/04-findings-panel.png" alt="Findings panel with measured change and confidence" width="80%" />
|
| 662 |
+
|
| 663 |
+
*Measured change per lesion, with confidence and the reason it is reduced.*
|
| 664 |
+
|
| 665 |
+
</div>
|
| 666 |
+
|
| 667 |
+
Confidence is reported **per pipeline stage**, and an answer's confidence is the **minimum**
|
| 668 |
+
across the observations it rests on, never an average. A confident segmentation combined with an
|
| 669 |
+
uncertain registration produces an uncertain change measurement, and averaging would let the
|
| 670 |
+
reliable measurement hide the unreliable one.
|
| 671 |
+
|
| 672 |
+
Eleven quality flags feed this, each translated into plain language: *"the two studies used
|
| 673 |
+
different acquisition protocols"*, *"slice thickness was large enough to affect volume
|
| 674 |
+
measurement"*, *"another lesion scored almost as well as this correspondence"*.
|
| 675 |
+
|
| 676 |
+
---
|
| 677 |
+
|
| 678 |
+
## 3D disease evolution
|
| 679 |
+
|
| 680 |
+
<div align="center">
|
| 681 |
+
|
| 682 |
+
<img src="GIF/gif-01-timeline-3d.gif" alt="Dragging the time scrubber updates the 3D disease map" width="90%" />
|
| 683 |
+
|
| 684 |
+
*Dragging the time scrubber: the lesion surface, the measurements and the evidence move together.*
|
| 685 |
+
|
| 686 |
+
<br />
|
| 687 |
+
|
| 688 |
+
<img src="GIF/gif-02-3d-rotate.gif" alt="Rotating the volume-rendered head" width="90%" />
|
| 689 |
+
|
| 690 |
+
*Rotation follows the pointer, and the head stays solid from every angle.*
|
| 691 |
+
|
| 692 |
+
</div>
|
| 693 |
+
|
| 694 |
+
### The head is volume-rendered, and that was a hard-won decision
|
| 695 |
+
|
| 696 |
+
The brain context was originally a **surface** extracted from the skull-strip mask. It was
|
| 697 |
+
reported as having holes five separate times. Each round found something real: open edges from a
|
| 698 |
+
crop that borrowed its margin from the source volume, front-face culling that erased deep
|
| 699 |
+
concavities, a camera whose view-up was parallel to its view direction, lesions left unlit by a
|
| 700 |
+
single headlight. Each round fixed it, measured the rendered image as clean, and the report still stood.
|
| 701 |
+
|
| 702 |
+
> [!NOTE]
|
| 703 |
+
> A surface leaves only two options, and **each has a failure mode invisible to a software
|
| 704 |
+
> rasteriser**. Translucent, and the result depends on blending order and multisample resolve,
|
| 705 |
+
> which vary by driver. Opaque with the near wall culled, and it is a hollow bowl that hides the
|
| 706 |
+
> anatomy it exists to show. The checks ran under SwiftShader; the defect lived on the GPU.
|
| 707 |
+
|
| 708 |
+
The head is now the patient's own **skull-stripped contrast-enhanced T1, ray-cast as a volume**.
|
| 709 |
+
There is no surface to close, no winding, no culling, no blending order, so *a gap in the anatomy is
|
| 710 |
+
not expressible*. And it is the real anatomy at full 1 mm resolution rather than a smoothed
|
| 711 |
+
approximation of its outer boundary.
|
| 712 |
+
|
| 713 |
+
| Measurement | Measured on the Intel Iris Plus iGPU |
|
| 714 |
+
|---|---|
|
| 715 |
+
| Median frame | **16.7 ms → 59.9 fps** *(16.7 ms is the vsync interval, so the renderer is not the limit)* |
|
| 716 |
+
| 95th percentile frame | 16.8 ms |
|
| 717 |
+
| First frame after load | 4.0 s |
|
| 718 |
+
| Main thread after a 60-step drag | 2 ms |
|
| 719 |
+
|
| 720 |
+
Benchmarked on real hardware rather than in software, because software rendering is exactly how
|
| 721 |
+
five rounds of a rendering defect stayed invisible. Run it yourself: `make benchmark-3d`.
|
| 722 |
+
|
| 723 |
+
<table>
|
| 724 |
+
<tr>
|
| 725 |
+
<td width="50%" valign="top">
|
| 726 |
+
|
| 727 |
+
<img src="docs/screenshots/05-3d-evolution.png" alt="3D disease evolution view" width="100%" />
|
| 728 |
+
|
| 729 |
+
**Coloured by measured change.** Red > +25%, blue < −25%, green stable, **grey for a baseline or
|
| 730 |
+
a doubtful match**. The prior timepoint is drawn as a wireframe.
|
| 731 |
+
|
| 732 |
+
</td>
|
| 733 |
+
<td width="50%" valign="top">
|
| 734 |
+
|
| 735 |
+
<img src="docs/screenshots/06-3d-lesion-selected.png" alt="Single lesion selected in 3D" width="100%" />
|
| 736 |
+
|
| 737 |
+
Selecting a lesion highlights it simultaneously in the 2D panes, the findings list and the 3D
|
| 738 |
+
view. Lesion meshes are **never smoothed**, because that mesh is the source of the reported surface area.
|
| 739 |
+
|
| 740 |
+
</td>
|
| 741 |
+
</tr>
|
| 742 |
+
</table>
|
| 743 |
+
|
| 744 |
+
<details>
|
| 745 |
+
<summary><b>Why the lesions stay as surfaces while the head is a volume</b></summary>
|
| 746 |
+
|
| 747 |
+
<br />
|
| 748 |
+
|
| 749 |
+
The lesions are the *measured* objects. Each needs its own colour for its own change, and a
|
| 750 |
+
surface is the honest way to draw a boundary that came from a mask. They are extracted at full
|
| 751 |
+
resolution (median 71 KB, max 715 KB per mesh) and shipped as binary PLY with per-vertex normals.
|
| 752 |
+
|
| 753 |
+
`ml/scripts/check_mesh_integrity.py` verifies the shipped bytes rather than synthetic spheres:
|
| 754 |
+
unit-length normals, outward orientation, triangle winding consistent with them, zero open edges,
|
| 755 |
+
zero non-manifold edges, one connected component.
|
| 756 |
+
|
| 757 |
+
</details>
|
| 758 |
+
|
| 759 |
+
---
|
| 760 |
+
|
| 761 |
+
## The evidence engine
|
| 762 |
+
|
| 763 |
+
Five fixed clinical questions, answered from measurements already in the database, validated
|
| 764 |
+
before delivery, and stored with the evidence that supports them.
|
| 765 |
+
|
| 766 |
+
```mermaid
|
| 767 |
+
%%{init: {"theme":"base","themeVariables":{"fontSize":"15px","actorFontSize":"15px","noteFontSize":"14px","messageFontSize":"14px","textColor":"#1e293b","actorTextColor":"#1e293b","noteTextColor":"#1e293b","signalTextColor":"#1e293b","actorBkg":"#e0f2fe","actorBorder":"#0284c7","noteBkgColor":"#fef3c7","noteBorderColor":"#d97706","labelBoxBkgColor":"#e0f2fe","labelTextColor":"#1e293b"}}}%%
|
| 768 |
+
sequenceDiagram
|
| 769 |
+
autonumber
|
| 770 |
+
actor U as Clinician
|
| 771 |
+
participant F as findings
|
| 772 |
+
participant A as answers
|
| 773 |
+
participant L as LLM
|
| 774 |
+
participant S as SafetyGuard
|
| 775 |
+
participant D as Database
|
| 776 |
+
|
| 777 |
+
U->>F: Ask one of five questions
|
| 778 |
+
F->>D: Read recorded measurements
|
| 779 |
+
D-->>F: Observations, confidence, flags
|
| 780 |
+
F->>F: Assemble structured findings
|
| 781 |
+
Note over F: No evidence means no observation
|
| 782 |
+
F->>A: Compose deterministic answer
|
| 783 |
+
A-->>S: Ground truth
|
| 784 |
+
F->>L: Same findings, ask for prose
|
| 785 |
+
L-->>S: Draft, or nothing at all
|
| 786 |
+
S->>S: Forbidden claim? Invented number?
|
| 787 |
+
S-->>U: Answer, evidence, confidence
|
| 788 |
+
S->>D: Persist and audit
|
| 789 |
+
```
|
| 790 |
+
|
| 791 |
+
> [!IMPORTANT]
|
| 792 |
+
> **The model never sees pixels, never computes a number, and never has the last word.**
|
| 793 |
+
> `answers.py` composes the answer from findings alone with no model involved, and that sentence is
|
| 794 |
+
> the ground truth. A language model may make it *more readable*; it may not make it *different*.
|
| 795 |
+
|
| 796 |
+
<div align="center">
|
| 797 |
+
|
| 798 |
+
<img src="docs/screenshots/07-evidence-panel.png" alt="AI evidence panel with an answer and evidence chips" width="90%" />
|
| 799 |
+
|
| 800 |
+
<br />
|
| 801 |
+
|
| 802 |
+
<img src="GIF/gif-03-show-me-why.gif" alt="From an answer to its evidence to the study it came from" width="90%" />
|
| 803 |
+
|
| 804 |
+
*Every answer carries its evidence, and every piece of evidence navigates to the study it came from.*
|
| 805 |
+
|
| 806 |
+
</div>
|
| 807 |
+
|
| 808 |
+
An actual answer, generated from real measurements:
|
| 809 |
+
|
| 810 |
+
> No measured enhancing volume changed by more than 25% between week-019-2 and week-033, 98 days
|
| 811 |
+
> apart. 1 lesion changed by less: L06 measures 4.2 mm³, decreased by 5.7%. 5 lesions had no
|
| 812 |
+
> prior to compare against… **Confidence 0.64; slice thickness was large enough to affect volume
|
| 813 |
+
> measurement.**
|
| 814 |
+
|
| 815 |
+
### SafetyGuard
|
| 816 |
+
|
| 817 |
+
Implements a fixed table of permitted and forbidden statements, and nothing beyond it. What a medical
|
| 818 |
+
tool may and may not state is not an engineering decision.
|
| 819 |
+
|
| 820 |
+
| Check | Outcome |
|
| 821 |
+
|---|---|
|
| 822 |
+
| Diagnosis, tumour type or grade | Draft discarded → measurement delivered |
|
| 823 |
+
| Progression, response, improvement, recurrence | Draft discarded → measurement delivered |
|
| 824 |
+
| Treatment recommendation | Draft discarded → measurement delivered |
|
| 825 |
+
| Prognosis or survival | Draft discarded → measurement delivered |
|
| 826 |
+
| Clinical urgency | Draft discarded → measurement delivered |
|
| 827 |
+
| A number not present in the structured findings | Draft discarded → measurement delivered |
|
| 828 |
+
| A lesion or study not in the evidence set | Draft discarded → measurement delivered |
|
| 829 |
+
| **No linked evidence** | **Blocked, nothing delivered** |
|
| 830 |
+
| Confidence below 0.6 | Delivered, marked low, reason in plain language |
|
| 831 |
+
| Quality flags on the inputs | Delivered, flags surfaced alongside |
|
| 832 |
+
|
| 833 |
+
The regular expressions are deliberately coarse and are **not** treated as a semantic filter. A
|
| 834 |
+
pattern cannot understand a sentence, so the guarantee comes from the deterministic fallback, not
|
| 835 |
+
from the cleverness of the patterns. A blocked draft is **kept**, because a block is a signal
|
| 836 |
+
about the pipeline rather than just a filtered string.
|
| 837 |
+
|
| 838 |
+
<details>
|
| 839 |
+
<summary><b>A worked example</b></summary>
|
| 840 |
+
|
| 841 |
+
<br />
|
| 842 |
+
|
| 843 |
+
```
|
| 844 |
+
LLM draft: "The tumour is malignant and has progressed."
|
| 845 |
+
SafetyGuard: MODIFIED, diagnostic or grading claim; progression or response judgement
|
| 846 |
+
Delivered: "The segmented enhancing volume increased from 12.2 cm³ to 19.7 cm³
|
| 847 |
+
(+61.5%) between 2025-06-12 and 2025-09-04."
|
| 848 |
+
```
|
| 849 |
+
|
| 850 |
+
Two false positives were found by measurement and fixed: a timepoint called `week-012` parses as
|
| 851 |
+
the number −12, and a lesion called `L01` as 1, so identifiers are stripped before the numeric
|
| 852 |
+
scan. Rounding is not fabrication. 19.7 written as 20 is accepted, 42 is not.
|
| 853 |
+
|
| 854 |
+
</details>
|
| 855 |
+
|
| 856 |
+
### Reproducibility and audit
|
| 857 |
+
|
| 858 |
+
`findings_hash` digests the question and the entire findings payload. Asking the same question
|
| 859 |
+
about unchanged findings returns the **stored** answer rather than generating a second one, so a
|
| 860 |
+
past statement stays reconstructable. Every generation writes an `AuditEvent` carrying the model
|
| 861 |
+
version, the input hash, the output hash and the safety outcome, with the patient referenced by
|
| 862 |
+
UUID and no findings in the detail.
|
| 863 |
+
|
| 864 |
+
---
|
| 865 |
+
|
| 866 |
+
## Architecture
|
| 867 |
+
|
| 868 |
+
```mermaid
|
| 869 |
+
%%{init: {"theme":"base","themeVariables":{"fontSize":"15px","clusterBkg":"#f8fafc","clusterBorder":"#94a3b8","textColor":"#1e293b","nodeTextColor":"#1e293b","lineColor":"#475569","edgeLabelBackground":"#ffffff"},"flowchart":{"nodeSpacing":40,"rankSpacing":62,"padding":12}}}%%
|
| 870 |
+
flowchart TB
|
| 871 |
+
subgraph BROWSER ["Browser"]
|
| 872 |
+
W["Next.js · React"]
|
| 873 |
+
CS["Cornerstone3D"]
|
| 874 |
+
VTK["vtk.js"]
|
| 875 |
+
end
|
| 876 |
+
|
| 877 |
+
subgraph APILAYER ["FastAPI modular monolith"]
|
| 878 |
+
R["Routers"]
|
| 879 |
+
AN["Analysis"]
|
| 880 |
+
EV["Evidence engine"]
|
| 881 |
+
IG["Ingestion"]
|
| 882 |
+
end
|
| 883 |
+
|
| 884 |
+
subgraph MLLAYER ["ml, separate package"]
|
| 885 |
+
MM["medtrace_ml"]
|
| 886 |
+
ART["artifacts"]
|
| 887 |
+
end
|
| 888 |
+
|
| 889 |
+
subgraph SVC ["Docker Compose"]
|
| 890 |
+
PG[("PostgreSQL")]
|
| 891 |
+
MIO[("MinIO")]
|
| 892 |
+
ORT[("Orthanc")]
|
| 893 |
+
RD[("Redis")]
|
| 894 |
+
end
|
| 895 |
+
|
| 896 |
+
FS[("Filesystem")]
|
| 897 |
+
|
| 898 |
+
W --- CS
|
| 899 |
+
W --- VTK
|
| 900 |
+
W -->|"REST · PLY · NIfTI"| R
|
| 901 |
+
R --> AN
|
| 902 |
+
R --> EV
|
| 903 |
+
R --> IG
|
| 904 |
+
AN --> MM
|
| 905 |
+
EV --> MM
|
| 906 |
+
IG --> MM
|
| 907 |
+
MM --- ART
|
| 908 |
+
AN --> PG
|
| 909 |
+
IG --> PG
|
| 910 |
+
EV --> PG
|
| 911 |
+
IG --> ORT
|
| 912 |
+
AN --> MIO
|
| 913 |
+
R --> RD
|
| 914 |
+
R -->|"path allowlist"| FS
|
| 915 |
+
|
| 916 |
+
classDef ui fill:#e0f2fe,stroke:#0284c7,stroke-width:1px,color:#0c4a6e
|
| 917 |
+
classDef api fill:#ccfbf1,stroke:#0d9488,stroke-width:1px,color:#134e4a
|
| 918 |
+
classDef ml fill:#e0e7ff,stroke:#4f46e5,stroke-width:1px,color:#312e81
|
| 919 |
+
classDef svc fill:#f3e8ff,stroke:#9333ea,stroke-width:1px,color:#581c87
|
| 920 |
+
classDef fs fill:#fef3c7,stroke:#d97706,stroke-width:1px,color:#78350f
|
| 921 |
+
|
| 922 |
+
class W,CS,VTK ui
|
| 923 |
+
class R,AN,EV,IG api
|
| 924 |
+
class MM,ART ml
|
| 925 |
+
class PG,MIO,ORT,RD svc
|
| 926 |
+
class FS fs
|
| 927 |
+
```
|
| 928 |
+
|
| 929 |
+
<table>
|
| 930 |
+
<tr><td valign="top" width="25%">
|
| 931 |
+
|
| 932 |
+
**Browser**
|
| 933 |
+
|
| 934 |
+
- `Next.js · React`: Zustand, TanStack Query
|
| 935 |
+
- `Cornerstone3D`: 2D volumes + labelmaps
|
| 936 |
+
- `vtk.js`: 3D volume ray-cast
|
| 937 |
+
|
| 938 |
+
</td><td valign="top" width="25%">
|
| 939 |
+
|
| 940 |
+
**FastAPI**
|
| 941 |
+
|
| 942 |
+
- `Routers`: patients, studies, timeline, files, evidence
|
| 943 |
+
- `Analysis`: pipeline orchestration
|
| 944 |
+
- `Evidence engine`: findings, safety, audit
|
| 945 |
+
- `Ingestion`: BraTS, LUMIERE, quality control
|
| 946 |
+
|
| 947 |
+
</td><td valign="top" width="25%">
|
| 948 |
+
|
| 949 |
+
**ml, a separate package**
|
| 950 |
+
|
| 951 |
+
- `medtrace_ml`: measure, matching, change, mesh, volume, labels
|
| 952 |
+
- `artifacts`: weights, TorchScript, model card
|
| 953 |
+
|
| 954 |
+
</td><td valign="top" width="25%">
|
| 955 |
+
|
| 956 |
+
**Services**
|
| 957 |
+
|
| 958 |
+
- `PostgreSQL`: metadata only
|
| 959 |
+
- `MinIO`: object storage
|
| 960 |
+
- `Orthanc`: DICOM
|
| 961 |
+
- `Redis`: cache
|
| 962 |
+
- `Filesystem`: `data/raw`, `data/derived`
|
| 963 |
+
|
| 964 |
+
</td></tr>
|
| 965 |
+
</table>
|
| 966 |
+
|
| 967 |
+
**ML code stays out of application code.** The application depends on model *contracts*, never on
|
| 968 |
+
training code. Two Python packages, `medtrace-api` and `medtrace-ml`, with the API importing the
|
| 969 |
+
latter but never the reverse.
|
| 970 |
+
|
| 971 |
+
<div align="center">
|
| 972 |
+
|
| 973 |
+
<img src="docs/screenshots/10-api-docs.png" alt="MEDTRACE OpenAPI documentation" width="90%" />
|
| 974 |
+
|
| 975 |
+
*Every endpoint is typed end to end: Pydantic v2 on the server, generated TypeScript contracts in
|
| 976 |
+
the browser.*
|
| 977 |
+
|
| 978 |
+
</div>
|
| 979 |
+
|
| 980 |
+
<details>
|
| 981 |
+
<summary><b>Repository layout</b></summary>
|
| 982 |
+
|
| 983 |
+
<br />
|
| 984 |
+
|
| 985 |
+
```
|
| 986 |
+
medtrace/
|
| 987 |
+
├── apps/
|
| 988 |
+
│ ├── api/ FastAPI modular monolith
|
| 989 |
+
│ │ ├── alembic/versions/ schema migrations
|
| 990 |
+
│ │ ├── medtrace/
|
| 991 |
+
│ │ │ ├── analysis/ the measurement pipeline
|
| 992 |
+
│ │ │ ├── domain/ models.py, enums.py
|
| 993 |
+
│ │ │ ├── evidence/ findings · answers · llm · safety · service
|
| 994 |
+
│ │ │ ├── ingestion/ BraTS and LUMIERE readers, quality control
|
| 995 |
+
│ │ │ └── routers/
|
| 996 |
+
│ │ ├── scripts/ one-off data operations
|
| 997 |
+
│ │ └── tests/
|
| 998 |
+
│ └── web/ Next.js clinical workstation
|
| 999 |
+
│ ├── scripts/ browser verification and the 3D benchmark
|
| 1000 |
+
│ └── src/{app,components,lib,store}
|
| 1001 |
+
├── ml/
|
| 1002 |
+
│ ├── medtrace_ml/ measure · lesions · matching · change · trajectory
|
| 1003 |
+
│ │ registration · mesh · volume · labels
|
| 1004 |
+
│ ├── notebooks/ training notebook, its generator and its checks
|
| 1005 |
+
│ ├── scripts/ dataset preparation, model and mesh verification
|
| 1006 |
+
│ ├── artifacts/ trained weights, TorchScript, model card
|
| 1007 |
+
│ └── reports/ evaluation and agreement CSVs
|
| 1008 |
+
├── packages/ reserved for shared contracts
|
| 1009 |
+
├── infrastructure/ MLflow image, database init
|
| 1010 |
+
├── data/
|
| 1011 |
+
│ ├── raw/ the datasets
|
| 1012 |
+
│ ├── derived/ generated meshes and atlas volumes
|
| 1013 |
+
│ └── kaggle/ upload staging, manifest and splits kept
|
| 1014 |
+
└── docs/screenshots/ interface captures used in this README
|
| 1015 |
+
```
|
| 1016 |
+
|
| 1017 |
+
</details>
|
| 1018 |
+
|
| 1019 |
+
---
|
| 1020 |
+
|
| 1021 |
+
## Technology stack
|
| 1022 |
+
|
| 1023 |
+
<table>
|
| 1024 |
+
<tr><td valign="top" width="50%">
|
| 1025 |
+
|
| 1026 |
+
**Frontend**
|
| 1027 |
+
|
| 1028 |
+
| Layer | Choice |
|
| 1029 |
+
|---|---|
|
| 1030 |
+
| Framework | Next.js 15.5 · React 19.1 |
|
| 1031 |
+
| Language | TypeScript 5.9 (strict) |
|
| 1032 |
+
| Styling | Tailwind CSS 4 |
|
| 1033 |
+
| Client state | Zustand 5 |
|
| 1034 |
+
| Server state | TanStack Query 5 |
|
| 1035 |
+
| 2D viewer | Cornerstone3D 5.7 |
|
| 1036 |
+
| 3D renderer | vtk.js 36.4 |
|
| 1037 |
+
|
| 1038 |
+
**Backend**
|
| 1039 |
+
|
| 1040 |
+
| Layer | Choice |
|
| 1041 |
+
|---|---|
|
| 1042 |
+
| API | FastAPI · Pydantic v2 |
|
| 1043 |
+
| ORM | SQLAlchemy 2 · Alembic |
|
| 1044 |
+
| Database | PostgreSQL 17 |
|
| 1045 |
+
| Object storage | MinIO |
|
| 1046 |
+
| DICOM | Orthanc 24.10 |
|
| 1047 |
+
| Cache | Redis 7 |
|
| 1048 |
+
|
| 1049 |
+
</td><td valign="top" width="50%">
|
| 1050 |
+
|
| 1051 |
+
**Machine learning**
|
| 1052 |
+
|
| 1053 |
+
| Layer | Choice |
|
| 1054 |
+
|---|---|
|
| 1055 |
+
| Framework | PyTorch 2.10 (cu128) |
|
| 1056 |
+
| Medical DL | MONAI 1.6 |
|
| 1057 |
+
| Registration | SimpleITK |
|
| 1058 |
+
| Imaging I/O | nibabel · NumPy · SciPy |
|
| 1059 |
+
| Meshing | scikit-image marching cubes |
|
| 1060 |
+
| Training | Kaggle Tesla T4 |
|
| 1061 |
+
| LLM | MedGemma (text-only) behind a provider adapter |
|
| 1062 |
+
|
| 1063 |
+
**Engineering**
|
| 1064 |
+
|
| 1065 |
+
| Layer | Choice |
|
| 1066 |
+
|---|---|
|
| 1067 |
+
| Infrastructure | Docker Compose, 5 services |
|
| 1068 |
+
| Testing | Pytest · Vitest · Playwright |
|
| 1069 |
+
| Linting | Ruff · ESLint |
|
| 1070 |
+
| CI | GitHub Actions |
|
| 1071 |
+
|
| 1072 |
+
</td></tr>
|
| 1073 |
+
</table>
|
| 1074 |
+
|
| 1075 |
+
> [!NOTE]
|
| 1076 |
+
> **On the LLM:** the default configuration is `MEDTRACE_LLM_PROVIDER=none`, and that is a
|
| 1077 |
+
> *working* configuration rather than a disabled one. MedGemma-27B does not fit on the
|
| 1078 |
+
> development GPU, and the five clinical questions must be answerable regardless, so the
|
| 1079 |
+
> deterministic composer answers them. Point `MEDTRACE_LLM_BASE_URL` at a vLLM, Ollama or
|
| 1080 |
+
> llama.cpp endpoint and it will rephrase; if that endpoint is unreachable you lose wording, not
|
| 1081 |
+
> correctness.
|
| 1082 |
+
|
| 1083 |
+
---
|
| 1084 |
+
|
| 1085 |
+
## Datasets
|
| 1086 |
+
|
| 1087 |
+
Public, de-identified research data only. No real hospital patient data enters this repository
|
| 1088 |
+
under any circumstances.
|
| 1089 |
+
|
| 1090 |
+
| Dataset | Role | Scale ingested |
|
| 1091 |
+
|---|---|---|
|
| 1092 |
+
| **BraTS 2023 GLI** | Segmentation training and held-out evaluation | 1,251 cases from 1,133 subjects · 118 with two labelled timepoints |
|
| 1093 |
+
| **LUMIERE** | The longitudinal dataset: timeline, matching, trajectory | 91 patients · 638 timepoints · 2,487 series |
|
| 1094 |
+
|
| 1095 |
+
LUMIERE's acquisition metadata records **three field strengths, 21 scanner models and slice
|
| 1096 |
+
thickness from 0.8 mm to 6.0 mm**, so quality control and confidence reporting are built against
|
| 1097 |
+
measured heterogeneity rather than imagined inputs.
|
| 1098 |
+
|
| 1099 |
+
<details>
|
| 1100 |
+
<summary><b>Data we hold and deliberately will not use</b></summary>
|
| 1101 |
+
|
| 1102 |
+
<br />
|
| 1103 |
+
|
| 1104 |
+
LUMIERE ships **survival time in weeks, IDH status and MGMT methylation** for all 91 patients.
|
| 1105 |
+
That makes outcome and molecular prediction technically possible with the data already on disk.
|
| 1106 |
+
|
| 1107 |
+
We are not building it. Prognosis is forbidden by the safety policy this project holds itself to, and a
|
| 1108 |
+
measurement system must be trustworthy before prediction built on top of it means anything. This
|
| 1109 |
+
is recorded as a deliberate decision rather than an oversight, so the temptation is resolved once
|
| 1110 |
+
instead of repeatedly.
|
| 1111 |
+
|
| 1112 |
+
LUMIERE's **expert RANO ratings for 616 timepoints** are likewise used only as a reference
|
| 1113 |
+
standard for evaluation. Never a training target, never surfaced as a MEDTRACE output.
|
| 1114 |
+
|
| 1115 |
+
</details>
|
| 1116 |
+
|
| 1117 |
+
---
|
| 1118 |
+
|
| 1119 |
+
## Verification
|
| 1120 |
+
|
| 1121 |
+
Nothing here is asserted without measurement. **696 automated checks.**
|
| 1122 |
+
|
| 1123 |
+
| Suite | Checks | What it proves |
|
| 1124 |
+
|---|---:|---|
|
| 1125 |
+
| `pytest` API | **155** | Contracts, ingestion, quality control, pipeline output, SafetyGuard |
|
| 1126 |
+
| `pytest` ML | **168** | Measurement, matching, change, meshing, volume windowing, label mappings |
|
| 1127 |
+
| `vitest` web | **17** | Camera conventions, hole detection geometry |
|
| 1128 |
+
| `verify-study-linkage` | **189** | Every pane displays the study it claims, at every timepoint |
|
| 1129 |
+
| `verify-evidence` | **42** | Answers carry evidence, cite only measured numbers, and navigate |
|
| 1130 |
+
| `verify-evolution` | **37** | The 3D view draws the right surfaces, in one coordinate frame |
|
| 1131 |
+
| `verify-workstation` | **19** | The clinical shell, the scrubber, and the intended-use notice |
|
| 1132 |
+
| `verify-brain-shell` | **15** | No holes in the rendered head, at five viewing angles |
|
| 1133 |
+
| `verify-overlay` | **15** | The 2D tumour overlay draws the right mask |
|
| 1134 |
+
| `verify-slice-sync` | **14** | The two panes really scroll together, and stop when unlinked |
|
| 1135 |
+
| `verify-findings` | **8** | The UI shows real measured findings, not seeded numbers |
|
| 1136 |
+
|
| 1137 |
+
<details>
|
| 1138 |
+
<summary><b>Why the browser checks exist at all</b></summary>
|
| 1139 |
+
|
| 1140 |
+
<br />
|
| 1141 |
+
|
| 1142 |
+
Because instrumentation has been wrong more than once, and each time it was wrong in a way that
|
| 1143 |
+
*passed*:
|
| 1144 |
+
|
| 1145 |
+
- A hole detector that counted the gaps between the legend's **text glyphs**, because an element
|
| 1146 |
+
screenshot captures whatever is drawn over the element.
|
| 1147 |
+
- The same check running on the patient the app opens on, which has a single 96-pixel lesion, and
|
| 1148 |
+
and passing with zero holes while the defect was obvious on a patient with twelve.
|
| 1149 |
+
- A watertightness test that only ever ran at stride 1, while the shipped meshes used stride 2.
|
| 1150 |
+
- A "fit view" check that measured the brain instead of the lesion.
|
| 1151 |
+
|
| 1152 |
+
Expected values are taken **from the API**, not from the page. The hole detector now lives in its
|
| 1153 |
+
own module with its own unit tests, because three wrong versions of a check is enough.
|
| 1154 |
+
|
| 1155 |
+
</details>
|
| 1156 |
+
|
| 1157 |
+
---
|
| 1158 |
+
|
| 1159 |
+
## Getting started
|
| 1160 |
+
|
| 1161 |
+
### Prerequisites
|
| 1162 |
+
|
| 1163 |
+
| Requirement | Needed |
|
| 1164 |
+
|---|---|
|
| 1165 |
+
| Docker Desktop | Running, with the Linux engine |
|
| 1166 |
+
| Python | ≥ 3.11 *(resolved against 3.14)* |
|
| 1167 |
+
| Node.js | ≥ 20 *(tested on 24)* |
|
| 1168 |
+
| Disk | ~50 GB for both datasets |
|
| 1169 |
+
|
| 1170 |
+
### First-time setup
|
| 1171 |
+
|
| 1172 |
+
```bash
|
| 1173 |
+
cp .env.example .env
|
| 1174 |
+
|
| 1175 |
+
make up # PostgreSQL, Redis, MinIO, Orthanc, MLflow
|
| 1176 |
+
make install # API venv + ML venv + web dependencies
|
| 1177 |
+
make migrate # create the schema
|
| 1178 |
+
make seed # synthetic demonstration patient (labelled synthetic in the UI)
|
| 1179 |
+
```
|
| 1180 |
+
|
| 1181 |
+
Then ingest and analyse. This is the slow part:
|
| 1182 |
+
|
| 1183 |
+
```bash
|
| 1184 |
+
make ingest-lumiere # 91 patients, 638 timepoints
|
| 1185 |
+
make ingest-brats # 118 subjects with two labelled timepoints
|
| 1186 |
+
make analyse # the full measurement pipeline (~20 min)
|
| 1187 |
+
make link-masks # reorient each series' mask for the 2D overlay
|
| 1188 |
+
```
|
| 1189 |
+
|
| 1190 |
+
### Running it
|
| 1191 |
+
|
| 1192 |
+
Three terminals, in this order:
|
| 1193 |
+
|
| 1194 |
+
```bash
|
| 1195 |
+
# 1. services
|
| 1196 |
+
docker compose up -d
|
| 1197 |
+
|
| 1198 |
+
# 2. API
|
| 1199 |
+
cd apps/api
|
| 1200 |
+
.venv/Scripts/python.exe -m uvicorn medtrace.main:app --host 127.0.0.1 --port 8000
|
| 1201 |
+
|
| 1202 |
+
# 3. web (production build, see the note below)
|
| 1203 |
+
cd apps/web
|
| 1204 |
+
npm run start
|
| 1205 |
+
```
|
| 1206 |
+
|
| 1207 |
+
| Service | Address |
|
| 1208 |
+
|---|---|
|
| 1209 |
+
| **Workstation** | **http://localhost:3000** |
|
| 1210 |
+
| API documentation | http://localhost:8000/docs |
|
| 1211 |
+
| Health check | http://localhost:8000/health |
|
| 1212 |
+
| Orthanc | http://localhost:8042 |
|
| 1213 |
+
| MinIO console | http://localhost:9101 |
|
| 1214 |
+
|
| 1215 |
+
**Stop cleanly:** `Ctrl+C` in the web and API terminals, then `docker compose stop`.
|
| 1216 |
+
|
| 1217 |
+
> [!TIP]
|
| 1218 |
+
> Use `npm run start`, not `npm run dev`. Volume rendering is the heaviest part of MEDTRACE, and
|
| 1219 |
+
> development mode's HMR, source maps and double-invoked effects consume memory the renderer
|
| 1220 |
+
> needs. Never run `npm run build` while a dev server is running, because they share `.next`.
|
| 1221 |
+
>
|
| 1222 |
+
> The viewer caps its cache at **500 MB and six resident volumes**, shows its footprint in the
|
| 1223 |
+
> toolbar, and offers a `reset viewer` control if the graphics context is ever lost.
|
| 1224 |
+
|
| 1225 |
+
> [!WARNING]
|
| 1226 |
+
> **No authentication.** The API is unauthenticated and binds to `127.0.0.1` only, with
|
| 1227 |
+
> development credentials from `.env`. It is for local use. Do not expose it.
|
| 1228 |
+
|
| 1229 |
+
<details>
|
| 1230 |
+
<summary><b>Optional commands</b></summary>
|
| 1231 |
+
|
| 1232 |
+
<br />
|
| 1233 |
+
|
| 1234 |
+
```bash
|
| 1235 |
+
# Tests
|
| 1236 |
+
cd apps/api && .venv/Scripts/python.exe -m pytest -q # 155
|
| 1237 |
+
cd ml && ../apps/api/.venv/Scripts/python.exe -m pytest -q # 168
|
| 1238 |
+
cd apps/web && npm run test # 22 unit
|
| 1239 |
+
|
| 1240 |
+
# Browser verification (needs both servers running)
|
| 1241 |
+
make verify # every browser suite
|
| 1242 |
+
make verify-evidence # the AI evidence engine
|
| 1243 |
+
make verify-brain-shell # no holes in the rendered head
|
| 1244 |
+
make benchmark-3d # 3D frame rate on the real GPU
|
| 1245 |
+
|
| 1246 |
+
# Quality
|
| 1247 |
+
make lint # Ruff + ESLint
|
| 1248 |
+
make typecheck # tsc --noEmit
|
| 1249 |
+
|
| 1250 |
+
# Data and model inspection
|
| 1251 |
+
make inspect p=Patient-006 # per-lesion trajectories
|
| 1252 |
+
make quality-report # what quality control found
|
| 1253 |
+
make check-mesh-integrity # normals, winding, watertightness of shipped meshes
|
| 1254 |
+
make check-volume-pockets # no enclosed pockets of air inside the 3D display volumes
|
| 1255 |
+
make verify-meshes # every stored mesh against its observation
|
| 1256 |
+
make check-readme-diagrams # render this README's diagrams, check no label overflows
|
| 1257 |
+
```
|
| 1258 |
+
|
| 1259 |
+
Ports are shifted off the defaults, **PostgreSQL on 5433 and MinIO console on 9101**, so the stack
|
| 1260 |
+
does not collide with locally installed services.
|
| 1261 |
+
|
| 1262 |
+
</details>
|
| 1263 |
+
|
| 1264 |
+
---
|
| 1265 |
+
|
| 1266 |
+
## Build status
|
| 1267 |
+
|
| 1268 |
+
| Stage | Scope | State |
|
| 1269 |
+
|---|---|:---:|
|
| 1270 |
+
| **0** | Clinical problem, scope, safety boundaries, evaluation plan, architecture, dataset cards | Done |
|
| 1271 |
+
| **1** | Docker Compose infrastructure, FastAPI, 14-table observation model, clinical workstation | Done |
|
| 1272 |
+
| **2** | Dataset ingestion, quality control, Cornerstone3D medical viewer, slice synchronisation | Done |
|
| 1273 |
+
| **3** | Measurement, lesion extraction, registration, cross-time matching, change detection, trajectory | Done |
|
| 1274 |
+
| **4** | 3D disease evolution, volume rendering, surface extraction tied to the time scrubber | Done |
|
| 1275 |
+
| **5** | Evidence engine, SafetyGuard, five clinical questions, audit trail | Done |
|
| 1276 |
+
|
| 1277 |
+
**Deliberately not built:** MLOps automation (DVC, MLflow registry integration, Great
|
| 1278 |
+
Expectations), authentication and role-based access, deformable registration, progression
|
| 1279 |
+
classification, prognosis. The first is a project decision; the rest are scoped to later versions.
|
| 1280 |
+
|
| 1281 |
+
---
|
| 1282 |
+
|
| 1283 |
+
## Honesty as a design principle
|
| 1284 |
+
|
| 1285 |
+
MEDTRACE is presented as a prototype, because that is what it is. Understanding why clinical
|
| 1286 |
+
validation matters, and saying so plainly, is a strength when talking to clinicians, not a
|
| 1287 |
+
weakness to hide behind confident language.
|
| 1288 |
+
|
| 1289 |
+
Every number in this README is measured and reproducible from the repository. Where a result is
|
| 1290 |
+
unflattering, it is stated: the model's enhancing-tumour agreement on post-treatment data is
|
| 1291 |
+
poor, and that is why it is not in the measurement pipeline.
|
| 1292 |
+
|
| 1293 |
+
---
|
| 1294 |
+
|
| 1295 |
+
<div align="center">
|
| 1296 |
+
|
| 1297 |
+
**Datasets:** BraTS 2023 GLI and LUMIERE, used under their respective research licences.
|
| 1298 |
+
LUMIERE is non-commercial.
|
| 1299 |
+
|
| 1300 |
+
<br />
|
| 1301 |
+
|
| 1302 |
+
*MEDTRACE reports measured change. It does not diagnose.*
|
| 1303 |
+
|
| 1304 |
+
</div>
|
| 1305 |
+
|
| 1306 |
+
---
|
| 1307 |
+
|
| 1308 |
+
<div align="center">
|
| 1309 |
+
|
| 1310 |
+
### Omar Rehan
|
| 1311 |
+
|
| 1312 |
+
[](https://omar-rehan.vercel.app/)
|
| 1313 |
+
[](https://github.com/AIOmarRehan)
|
| 1314 |
+
[](https://linkedin.com/in/omar-rehan-47b98636a)
|
| 1315 |
+
[](https://huggingface.co/AIOmarRehan)
|
| 1316 |
+
|
| 1317 |
+
[](https://kaggle.com/aiomarrehan)
|
| 1318 |
+
[](https://medium.com/@ai.omar.rehan)
|
| 1319 |
+
[](https://public.tableau.com/app/profile/omar.rehan)
|
| 1320 |
+
|
| 1321 |
+
</div>
|
api/patients.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"age_at_baseline_years":65,"external_id":"RHUH-0001","id":"eb1d080b-101f-45ce-8455-cf9f4967dd55","is_synthetic":false,"lesion_count":2,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":65,"external_id":"RHUH-0002","id":"4709627e-6d9f-467a-9c36-76e45ed24071","is_synthetic":false,"lesion_count":5,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":75,"external_id":"RHUH-0003","id":"37ab76a9-f8d9-4ccb-8ae7-5b617bc333f8","is_synthetic":false,"lesion_count":4,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":64,"external_id":"RHUH-0004","id":"0e36f014-cba2-4b67-a60a-d5c720f13384","is_synthetic":false,"lesion_count":2,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":52,"external_id":"RHUH-0005","id":"a2cf6c6a-db6d-493a-9f48-ada357b32b83","is_synthetic":false,"lesion_count":2,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":72,"external_id":"RHUH-0006","id":"1644cc40-79e6-47bf-a4e4-51c8248a7ad9","is_synthetic":false,"lesion_count":3,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":51,"external_id":"RHUH-0007","id":"6bc843a9-e804-46d5-9d7d-d387a1f9c1ed","is_synthetic":false,"lesion_count":3,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":69,"external_id":"RHUH-0008","id":"e6fe336a-7b59-43a7-a419-3f01b51196bb","is_synthetic":false,"lesion_count":2,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":68,"external_id":"RHUH-0009","id":"8180e98b-ffa6-4f09-b73b-b35f9937a765","is_synthetic":false,"lesion_count":2,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":66,"external_id":"RHUH-0010","id":"75835d9b-32d4-4c9a-8cf9-8f57b066084b","is_synthetic":false,"lesion_count":2,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":71,"external_id":"RHUH-0011","id":"2043be8c-57f4-4845-88d0-cadadd106f19","is_synthetic":false,"lesion_count":3,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":64,"external_id":"RHUH-0012","id":"041c2146-8e58-4c2d-b5dd-6c888ff2447e","is_synthetic":false,"lesion_count":2,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":54,"external_id":"RHUH-0013","id":"31d3fa6c-d577-4972-9845-fd45c6d03801","is_synthetic":false,"lesion_count":4,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":78,"external_id":"RHUH-0014","id":"65c97660-6722-4a04-8a81-840ce91c1a8d","is_synthetic":false,"lesion_count":11,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":60,"external_id":"RHUH-0015","id":"92f0ea5a-eacc-42a8-a3c6-2345fc9e3722","is_synthetic":false,"lesion_count":4,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":68,"external_id":"RHUH-0016","id":"904c26dc-10ec-4454-9767-9758b9dc9789","is_synthetic":false,"lesion_count":3,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":47,"external_id":"RHUH-0017","id":"2be8a602-8102-4bf4-a6a0-87df5f84f3c1","is_synthetic":false,"lesion_count":4,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":57,"external_id":"RHUH-0018","id":"9bf9a4c8-66f6-470a-a03a-e425c4f5f438","is_synthetic":false,"lesion_count":8,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":61,"external_id":"RHUH-0019","id":"05e8f381-fc6b-4afa-a8ae-e1a8888b7be3","is_synthetic":false,"lesion_count":4,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":55,"external_id":"RHUH-0020","id":"add7899e-4dcb-4587-9733-ec0a4068cfa5","is_synthetic":false,"lesion_count":7,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":56,"external_id":"RHUH-0021","id":"8280c78d-5068-4b5a-ac27-d33c0839b167","is_synthetic":false,"lesion_count":1,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":78,"external_id":"RHUH-0022","id":"4ec108df-781d-4553-b5e0-56aa6c18c94a","is_synthetic":false,"lesion_count":1,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":59,"external_id":"RHUH-0023","id":"69ea08eb-e7e9-4728-b9f5-4eaeac564c4a","is_synthetic":false,"lesion_count":5,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":50,"external_id":"RHUH-0024","id":"5cbd85a6-01b0-40aa-b6ba-eb8a5dd942c3","is_synthetic":false,"lesion_count":3,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":49,"external_id":"RHUH-0025","id":"0a1ba0ca-23d8-46ed-9974-f86a08bf7027","is_synthetic":false,"lesion_count":1,"sex":"male","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":63,"external_id":"RHUH-0026","id":"d84b4c77-418f-4934-8b0b-257a47d73d31","is_synthetic":false,"lesion_count":5,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":77,"external_id":"RHUH-0027","id":"2166fc98-aac6-45a6-855a-bad61c98e806","is_synthetic":false,"lesion_count":4,"sex":"female","source_dataset":"RHUH_GBM","study_count":3},{"age_at_baseline_years":75,"external_id":"RHUH-0029","id":"cd9672c3-d41d-42ee-8245-d0579bcafe2b","is_synthetic":false,"lesion_count":8,"sex":"male","source_dataset":"RHUH_GBM","study_count":3}]
|
api/segmentations/009ae3f3-123e-4211-a6b9-019aef159d79.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"dc80cacc-f224-41a9-9922-3013494be267","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0003/1/RHUH-0003_1_segmentations.nii.gz","mesh_uri":"brain/dc80cacc-f224-41a9-9922-3013494be267.ply","source":"RHUH_GBM","study_id":"009ae3f3-123e-4211-a6b9-019aef159d79"}]
|
api/segmentations/02502ba4-5660-47b5-bffa-2670a34dfe7b.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"77293132-ff78-4a35-9870-de45465211b7","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0014/0/RHUH-0014_0_segmentations.nii.gz","mesh_uri":"brain/77293132-ff78-4a35-9870-de45465211b7.ply","source":"RHUH_GBM","study_id":"02502ba4-5660-47b5-bffa-2670a34dfe7b"}]
|
api/segmentations/026d5991-8bf0-4f22-9be3-a427cd017cdb.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"e4b5c98b-8b76-4f8d-b751-24cb9f9649ef","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0026/2/RHUH-0026_2_segmentations.nii.gz","mesh_uri":"brain/e4b5c98b-8b76-4f8d-b751-24cb9f9649ef.ply","source":"RHUH_GBM","study_id":"026d5991-8bf0-4f22-9be3-a427cd017cdb"}]
|
api/segmentations/059c5744-67cd-40d2-8d09-a44ccb563c16.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"4299b34b-d8ed-41fb-b20b-7b7b84377e47","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0021/0/RHUH-0021_0_segmentations.nii.gz","mesh_uri":"brain/4299b34b-d8ed-41fb-b20b-7b7b84377e47.ply","source":"RHUH_GBM","study_id":"059c5744-67cd-40d2-8d09-a44ccb563c16"}]
|
api/segmentations/0664cdeb-7fac-4da4-a80a-a55c6723ae2e.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"6c59d7cc-89de-4253-901e-c84dc994ba9b","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0011/0/RHUH-0011_0_segmentations.nii.gz","mesh_uri":"brain/6c59d7cc-89de-4253-901e-c84dc994ba9b.ply","source":"RHUH_GBM","study_id":"0664cdeb-7fac-4da4-a80a-a55c6723ae2e"}]
|
api/segmentations/06f843bf-75d5-45a7-8630-56ea34a8acc0.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"2a50800e-6e2c-40de-a8d9-9d709a165904","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0010/2/RHUH-0010_2_segmentations.nii.gz","mesh_uri":"brain/2a50800e-6e2c-40de-a8d9-9d709a165904.ply","source":"RHUH_GBM","study_id":"06f843bf-75d5-45a7-8630-56ea34a8acc0"}]
|
api/segmentations/071261ee-f4de-4fd3-a188-c9c1111010c3.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"f99740a9-da84-4aae-b754-43dd4e1e4c09","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0013/0/RHUH-0013_0_segmentations.nii.gz","mesh_uri":"brain/f99740a9-da84-4aae-b754-43dd4e1e4c09.ply","source":"RHUH_GBM","study_id":"071261ee-f4de-4fd3-a188-c9c1111010c3"}]
|
api/segmentations/0a15d8e4-145f-4cab-9167-8b13a9ff444f.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"36ac490e-b72b-46a1-ad06-ed6294dd8955","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0019/1/RHUH-0019_1_segmentations.nii.gz","mesh_uri":"brain/36ac490e-b72b-46a1-ad06-ed6294dd8955.ply","source":"RHUH_GBM","study_id":"0a15d8e4-145f-4cab-9167-8b13a9ff444f"}]
|
api/segmentations/0cd019eb-859f-4687-8c74-fa0b7a36e4e4.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"4a5ac6a0-ff86-47a7-bb3d-b4aa1254c00b","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0009/2/RHUH-0009_2_segmentations.nii.gz","mesh_uri":"brain/4a5ac6a0-ff86-47a7-bb3d-b4aa1254c00b.ply","source":"RHUH_GBM","study_id":"0cd019eb-859f-4687-8c74-fa0b7a36e4e4"}]
|
api/segmentations/0f6cdab3-55b8-48fa-82ee-f8b17515cab4.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"05001dda-3f4c-4e8f-95e3-cade43dba021","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0029/0/RHUH-0029_0_segmentations.nii.gz","mesh_uri":"brain/05001dda-3f4c-4e8f-95e3-cade43dba021.ply","source":"RHUH_GBM","study_id":"0f6cdab3-55b8-48fa-82ee-f8b17515cab4"}]
|
api/segmentations/11d9fd96-2cf5-4a80-8a39-8ad4ce075a86.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"c575a24f-ff31-4d44-8173-17ca676ab173","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0025/0/RHUH-0025_0_segmentations.nii.gz","mesh_uri":"brain/c575a24f-ff31-4d44-8173-17ca676ab173.ply","source":"RHUH_GBM","study_id":"11d9fd96-2cf5-4a80-8a39-8ad4ce075a86"}]
|
api/segmentations/1348e608-daf3-4473-a3f4-a8fb177d8534.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"f28d1a88-a293-4b37-81e8-5290d9b215c9","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0016/1/RHUH-0016_1_segmentations.nii.gz","mesh_uri":"brain/f28d1a88-a293-4b37-81e8-5290d9b215c9.ply","source":"RHUH_GBM","study_id":"1348e608-daf3-4473-a3f4-a8fb177d8534"}]
|
api/segmentations/164949bd-e96c-4a5f-9003-18a43fe53ec6.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"14eb0518-14d2-4f04-8335-ed449444b8bc","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0002/0/RHUH-0002_0_segmentations.nii.gz","mesh_uri":"brain/14eb0518-14d2-4f04-8335-ed449444b8bc.ply","source":"RHUH_GBM","study_id":"164949bd-e96c-4a5f-9003-18a43fe53ec6"}]
|
api/segmentations/169e9c49-2ef2-4c22-9245-3dc3ad32b545.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"87ee3851-7126-40ca-8e0c-447e099be7d7","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0001/0/RHUH-0001_0_segmentations.nii.gz","mesh_uri":"brain/87ee3851-7126-40ca-8e0c-447e099be7d7.ply","source":"RHUH_GBM","study_id":"169e9c49-2ef2-4c22-9245-3dc3ad32b545"}]
|
api/segmentations/1922d1cd-b1f5-441f-a0c2-ac2249440084.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"cfef0ad5-2a44-4d97-814d-b3dfe567339d","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0002/1/RHUH-0002_1_segmentations.nii.gz","mesh_uri":"brain/cfef0ad5-2a44-4d97-814d-b3dfe567339d.ply","source":"RHUH_GBM","study_id":"1922d1cd-b1f5-441f-a0c2-ac2249440084"}]
|
api/segmentations/1fec1237-f8c1-4712-a3d8-32d9a334f24e.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"a578b6e3-93b6-4b6f-9635-d99fcb942476","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0020/0/RHUH-0020_0_segmentations.nii.gz","mesh_uri":"brain/a578b6e3-93b6-4b6f-9635-d99fcb942476.ply","source":"RHUH_GBM","study_id":"1fec1237-f8c1-4712-a3d8-32d9a334f24e"}]
|
api/segmentations/2463dc35-d8b6-4998-b056-c2e12d7271dd.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"c1a1cba9-79d8-4c60-8c98-d2695545de18","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0023/0/RHUH-0023_0_segmentations.nii.gz","mesh_uri":"brain/c1a1cba9-79d8-4c60-8c98-d2695545de18.ply","source":"RHUH_GBM","study_id":"2463dc35-d8b6-4998-b056-c2e12d7271dd"}]
|
api/segmentations/25404115-694f-41e1-8308-c135a6327bb1.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"561474ec-d496-434c-9ed2-a59b32614966","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0005/1/RHUH-0005_1_segmentations.nii.gz","mesh_uri":"brain/561474ec-d496-434c-9ed2-a59b32614966.ply","source":"RHUH_GBM","study_id":"25404115-694f-41e1-8308-c135a6327bb1"}]
|
api/segmentations/25a4be4c-f837-4009-be28-7f070e8e5dd4.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"da250565-ef2d-4e4e-8743-688c26c7b343","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0018/0/RHUH-0018_0_segmentations.nii.gz","mesh_uri":"brain/da250565-ef2d-4e4e-8743-688c26c7b343.ply","source":"RHUH_GBM","study_id":"25a4be4c-f837-4009-be28-7f070e8e5dd4"}]
|
api/segmentations/26166fcc-7850-41ae-bd87-de6d814a5c30.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"2d176bc6-07ac-4857-8892-650d0db26a99","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0022/1/RHUH-0022_1_segmentations.nii.gz","mesh_uri":"brain/2d176bc6-07ac-4857-8892-650d0db26a99.ply","source":"RHUH_GBM","study_id":"26166fcc-7850-41ae-bd87-de6d814a5c30"}]
|
api/segmentations/26272b5b-80dd-4201-8001-3de172691935.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"6732aaa8-1337-4ae5-9109-1c649c92f9e0","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0025/2/RHUH-0025_2_segmentations.nii.gz","mesh_uri":"brain/6732aaa8-1337-4ae5-9109-1c649c92f9e0.ply","source":"RHUH_GBM","study_id":"26272b5b-80dd-4201-8001-3de172691935"}]
|
api/segmentations/27e46bbd-9461-4c3d-8fde-414291f5bc72.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"e8899c64-4be7-40cd-80cc-45b2fa5a8763","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0019/0/RHUH-0019_0_segmentations.nii.gz","mesh_uri":"brain/e8899c64-4be7-40cd-80cc-45b2fa5a8763.ply","source":"RHUH_GBM","study_id":"27e46bbd-9461-4c3d-8fde-414291f5bc72"}]
|
api/segmentations/2a721993-2746-49fd-ab08-363c33187bc1.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"cd37e9ea-50e5-44df-b8ad-ea15e9a3d796","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0025/1/RHUH-0025_1_segmentations.nii.gz","mesh_uri":"brain/cd37e9ea-50e5-44df-b8ad-ea15e9a3d796.ply","source":"RHUH_GBM","study_id":"2a721993-2746-49fd-ab08-363c33187bc1"}]
|
api/segmentations/2db7f389-59e8-4f51-892a-45aa64724a4d.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"a71b3592-bfe9-4e0b-9068-bb631384002b","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0024/1/RHUH-0024_1_segmentations.nii.gz","mesh_uri":"brain/a71b3592-bfe9-4e0b-9068-bb631384002b.ply","source":"RHUH_GBM","study_id":"2db7f389-59e8-4f51-892a-45aa64724a4d"}]
|
api/segmentations/2f4c8574-9ea0-4334-b96f-29f82fd556d5.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"2821cef8-9b27-4b9a-84fa-7f915fa4a612","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0011/1/RHUH-0011_1_segmentations.nii.gz","mesh_uri":"brain/2821cef8-9b27-4b9a-84fa-7f915fa4a612.ply","source":"RHUH_GBM","study_id":"2f4c8574-9ea0-4334-b96f-29f82fd556d5"}]
|
api/segmentations/311dcc60-732d-49ef-98e8-146429b35fa8.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"4f07bf81-09ac-4ac2-b4c8-37304675e8fc","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0004/1/RHUH-0004_1_segmentations.nii.gz","mesh_uri":"brain/4f07bf81-09ac-4ac2-b4c8-37304675e8fc.ply","source":"RHUH_GBM","study_id":"311dcc60-732d-49ef-98e8-146429b35fa8"}]
|
api/segmentations/33bf232d-ced9-495b-9152-eb6d149877d3.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"d702746d-798e-406c-8902-464971c4c159","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0014/1/RHUH-0014_1_segmentations.nii.gz","mesh_uri":"brain/d702746d-798e-406c-8902-464971c4c159.ply","source":"RHUH_GBM","study_id":"33bf232d-ced9-495b-9152-eb6d149877d3"}]
|
api/segmentations/3c71eff6-5c5c-4e23-a140-eeb7fc48c2aa.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"2287d11f-6502-4012-a204-40ef3210bb0f","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0005/0/RHUH-0005_0_segmentations.nii.gz","mesh_uri":"brain/2287d11f-6502-4012-a204-40ef3210bb0f.ply","source":"RHUH_GBM","study_id":"3c71eff6-5c5c-4e23-a140-eeb7fc48c2aa"}]
|
api/segmentations/3d314d09-a3c4-4ca8-be24-8d35bfcab419.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"ecab29f4-6f5c-496c-94ec-c8401bcf4447","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0029/2/RHUH-0029_2_segmentations.nii.gz","mesh_uri":"brain/ecab29f4-6f5c-496c-94ec-c8401bcf4447.ply","source":"RHUH_GBM","study_id":"3d314d09-a3c4-4ca8-be24-8d35bfcab419"}]
|
api/segmentations/4815a12f-0b7c-467f-ba55-afcd5064394a.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"54ba54f9-c979-42ef-9fc1-761ad8a30ceb","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0008/0/RHUH-0008_0_segmentations.nii.gz","mesh_uri":"brain/54ba54f9-c979-42ef-9fc1-761ad8a30ceb.ply","source":"RHUH_GBM","study_id":"4815a12f-0b7c-467f-ba55-afcd5064394a"}]
|
api/segmentations/495eb8a6-8859-4a12-9c11-b0b88f665c76.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"33609cc1-994c-471f-9eeb-dab5e914b109","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0006/1/RHUH-0006_1_segmentations.nii.gz","mesh_uri":"brain/33609cc1-994c-471f-9eeb-dab5e914b109.ply","source":"RHUH_GBM","study_id":"495eb8a6-8859-4a12-9c11-b0b88f665c76"}]
|
api/segmentations/49fff636-3d0c-4bbc-866d-fd34845e8803.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"719f4e7e-d5c3-44f6-a3de-aac16d9958e1","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0002/2/RHUH-0002_2_segmentations.nii.gz","mesh_uri":"brain/719f4e7e-d5c3-44f6-a3de-aac16d9958e1.ply","source":"RHUH_GBM","study_id":"49fff636-3d0c-4bbc-866d-fd34845e8803"}]
|
api/segmentations/4f456804-060e-47f1-a54b-c714bfcf58dc.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"767168e0-94f5-45c9-a271-3990b020975a","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0016/2/RHUH-0016_2_segmentations.nii.gz","mesh_uri":"brain/767168e0-94f5-45c9-a271-3990b020975a.ply","source":"RHUH_GBM","study_id":"4f456804-060e-47f1-a54b-c714bfcf58dc"}]
|
api/segmentations/50c7e8b0-067e-4c68-b476-5f78dfcd7a62.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"ae882a7f-ab0b-486c-b363-3a626b4a0e2b","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0003/0/RHUH-0003_0_segmentations.nii.gz","mesh_uri":"brain/ae882a7f-ab0b-486c-b363-3a626b4a0e2b.ply","source":"RHUH_GBM","study_id":"50c7e8b0-067e-4c68-b476-5f78dfcd7a62"}]
|
api/segmentations/53b1674c-bbf3-4bfd-aa8c-61517d151eb1.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"9979af6c-73a5-4951-b6da-280581cc4d42","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0013/1/RHUH-0013_1_segmentations.nii.gz","mesh_uri":"brain/9979af6c-73a5-4951-b6da-280581cc4d42.ply","source":"RHUH_GBM","study_id":"53b1674c-bbf3-4bfd-aa8c-61517d151eb1"}]
|
api/segmentations/5b364a21-a771-4f24-b34f-648c56159bb3.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"0f5451b0-55c8-430c-9ef8-132956220c72","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0008/2/RHUH-0008_2_segmentations.nii.gz","mesh_uri":"brain/0f5451b0-55c8-430c-9ef8-132956220c72.ply","source":"RHUH_GBM","study_id":"5b364a21-a771-4f24-b34f-648c56159bb3"}]
|
api/segmentations/62ec3a94-03fb-4477-860a-e4068c3f2bad.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"8f9ce7cd-dcf7-4d44-bd78-39cbfb7420f4","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0013/2/RHUH-0013_2_segmentations.nii.gz","mesh_uri":"brain/8f9ce7cd-dcf7-4d44-bd78-39cbfb7420f4.ply","source":"RHUH_GBM","study_id":"62ec3a94-03fb-4477-860a-e4068c3f2bad"}]
|
api/segmentations/6327e26a-4d95-4c6c-855d-f24ae5f91ce5.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"7c9f5b8d-3525-4057-8c8b-951ed932b830","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0007/0/RHUH-0007_0_segmentations.nii.gz","mesh_uri":"brain/7c9f5b8d-3525-4057-8c8b-951ed932b830.ply","source":"RHUH_GBM","study_id":"6327e26a-4d95-4c6c-855d-f24ae5f91ce5"}]
|
api/segmentations/63ad442f-86f8-4a05-abb1-2aa4b1f9f5d5.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"283eba5f-5195-4775-89e7-b5d69a161841","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0029/1/RHUH-0029_1_segmentations.nii.gz","mesh_uri":"brain/283eba5f-5195-4775-89e7-b5d69a161841.ply","source":"RHUH_GBM","study_id":"63ad442f-86f8-4a05-abb1-2aa4b1f9f5d5"}]
|
api/segmentations/6b329524-c37b-4581-9859-84a55b0f7d84.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"4fcde278-7d47-47bd-869a-1227360a9af0","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0006/2/RHUH-0006_2_segmentations.nii.gz","mesh_uri":"brain/4fcde278-7d47-47bd-869a-1227360a9af0.ply","source":"RHUH_GBM","study_id":"6b329524-c37b-4581-9859-84a55b0f7d84"}]
|
api/segmentations/71f83359-3065-4843-8c63-6d27998c4099.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"7df6f57a-064b-4f01-bda5-b832ec861a81","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0017/0/RHUH-0017_0_segmentations.nii.gz","mesh_uri":"brain/7df6f57a-064b-4f01-bda5-b832ec861a81.ply","source":"RHUH_GBM","study_id":"71f83359-3065-4843-8c63-6d27998c4099"}]
|
api/segmentations/7388fa67-3ca2-4c5a-a0bb-2b7dd8374ed5.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"9e572f36-bb89-4fab-b3b1-e59d03c32f1f","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0027/1/RHUH-0027_1_segmentations.nii.gz","mesh_uri":"brain/9e572f36-bb89-4fab-b3b1-e59d03c32f1f.ply","source":"RHUH_GBM","study_id":"7388fa67-3ca2-4c5a-a0bb-2b7dd8374ed5"}]
|
api/segmentations/76fe4d95-b610-42dc-8276-4caa3700a292.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"confidence":null,"has_label_map":true,"has_mesh":true,"has_volume":true,"id":"220f0d84-1fdb-47f3-990b-ec35187caec9","label_definitions":{"ENHANCING":3,"NECROTIC_CORE":1,"OEDEMA":2},"label_map_uri":"RHUH-GBM/RHUH-0005/2/RHUH-0005_2_segmentations.nii.gz","mesh_uri":"brain/220f0d84-1fdb-47f3-990b-ec35187caec9.ply","source":"RHUH_GBM","study_id":"76fe4d95-b610-42dc-8276-4caa3700a292"}]
|