Compare commits
60 Commits
11d1dc4339
...
1674254a3c
| Author | SHA1 | Date | |
|---|---|---|---|
| 1674254a3c | |||
| aeb04bcf2f | |||
| e29bce42e2 | |||
| d34772a3fa | |||
| add45567d8 | |||
| 3294151a92 | |||
| 776b5faffe | |||
| c5a58da6a3 | |||
| be16e3554f | |||
| 6cd9e33e65 | |||
| e161377245 | |||
| 1efd81bb0b | |||
| c0c5cd6195 | |||
| 0c4db1904c | |||
| 52f4be1a96 | |||
| 8faa7e9be4 | |||
| adff7caffd | |||
| 8dc4bb0aaa | |||
| ef62ffac9f | |||
| 72462b49ef | |||
| 181f471806 | |||
| 8a0a08000e | |||
| 6ee9489a5f | |||
| 6e70a32ffe | |||
| 5b3e47edea | |||
| 8d67078a90 | |||
| 9c3c3573bc | |||
| f695fcccd0 | |||
| 2f14631ad0 | |||
| 0ba752c28e | |||
| 82d4c94255 | |||
| f4497ef59d | |||
| e685b8b11d | |||
| 67329d0468 | |||
| 6ab976a82b | |||
| 4bff9b39f3 | |||
| e6a6128aca | |||
| 654abaaaf4 | |||
| 1a8fb26a29 | |||
| b66352f9b9 | |||
| 2c9b0345c4 | |||
| 9080370bd1 | |||
| 044057675e | |||
| 8de4efc3e2 | |||
| c3ea2b4478 | |||
| e2f1afe225 | |||
| efc91f8b19 | |||
| de9cf60adc | |||
| c6f8b271a9 | |||
| b917ca72c9 | |||
| e6f64a6a97 | |||
| 3a92f5afba | |||
| 86a7bd143f | |||
| 865bc956c7 | |||
| c063a3a6f3 | |||
| d5046659fd | |||
| 28884670e4 | |||
| 98c71e73e2 | |||
| c27a52243f | |||
| ce13f9cad2 |
+80
-2
@@ -5,6 +5,19 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
overflow: unset!important;
|
||||
margin: 0;
|
||||
}
|
||||
/* Make viewer roots flow normally on the page */
|
||||
.dicom-viewer-root {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div id="root" class="dicom-viewer-test-root" style="max-height:500px; height:500px; overflow:auto;"></div> -->
|
||||
@@ -12,9 +25,74 @@
|
||||
data-images="["http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm", "http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm", "http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm", "http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm", "http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm", "http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm", "http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm", "http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm", "http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm", "http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm", "http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm", "http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm", "http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm", "http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm", "http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm", "http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm", "http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm", "http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm", "http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm", "http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm", "http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm", "http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm"]"
|
||||
|
||||
data-auto-cache-stack="true"
|
||||
data-viewerstate='{"grid":{"rows":1,"cols":1},"modes":["stack"],"stacks":[{"i":["wadouri:http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm","wadouri:http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm","wadouri:http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm","wadouri:http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm","wadouri:http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm","wadouri:http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm","wadouri:http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm","wadouri:http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm","wadouri:http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm","wadouri:http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm","wadouri:http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm"],"u":"2.1163931708166249655895918785713305273005"}],"stackIdx":[0],"stackPos":[11],"props":[{"voiRange":{"lower":-707.7203723576732,"upper":1313.7029625973},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"}],"cam":[{"viewUp":[0.012033252051397651,-0.42083716478070027,-0.9070563687942972],"viewPlaneNormal":[0.9998766662057034,-0.004091402933210223,0.015162875697125407],"position":[215.72314005628635,-63.00598963917986,24.208010679564232],"focalPoint":[-0.06152398404300907,-62.12301873163119,20.935691053779152],"parallelProjection":true,"parallelScale":117.33333333333333,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":148.19404114160696}]}
|
||||
'
|
||||
data-viewerstate='{"grid":{"rows":1,"cols":3},"modes":["stack","volume","volume"],"stacks":[{"i":["wadouri:http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm","wadouri:http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm","wadouri:http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm","wadouri:http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm","wadouri:http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm","wadouri:http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm","wadouri:http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm","wadouri:http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm","wadouri:http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm","wadouri:http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm","wadouri:http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm"],"u":"2.1163931708166249655895918785713305273005"}],"stackIdx":[0,0,0],"stackPos":[0,173,10],"props":[{"voiRange":{"lower":-958.5,"upper":705.5},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"},{"voiRange":{"lower":-1798,"upper":1641},"invert":false,"colormap":{"name":"Grayscale","opacity":1,"threshold":null}},{"voiRange":{"lower":302,"upper":2069},"invert":false,"colormap":{"name":"Grayscale","opacity":1,"threshold":null}}],"cam":[{"viewUp":[-0.015545368194580078,-0.12045262008905411,0.9925973415374756],"viewPlaneNormal":[0.9998766662057034,-0.00409140293321022,0.015162875697125405],"position":[276.090926593084,-63.25300826357402,25.12347315106698],"focalPoint":[59.93107667300279,-62.36850213007119,21.845463926704127],"parallelProjection":true,"parallelScale":170.33890849893743,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[0,0,1],"viewPlaneNormal":[0,-1,0],"position":[-6.060784295600456,-273.9517816053456,20.844714115543866],"focalPoint":[-6.060784295600456,-46.51629576550185,20.844714115543866],"parallelProjection":true,"parallelScale":150.85189284207627,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[0,0,1],"viewPlaneNormal":[1,0,0],"position":[218.5103649207617,-62.09846953239139,20.844714115543866],"focalPoint":[-8.925120919082055,-62.09846953239139,20.844714115543866],"parallelProjection":true,"parallelScale":157.88941096127814,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0}],"orientations":[null,"coronal","sagittal"],"volumeSliceIndices":[null,173,10]}'
|
||||
|
||||
></div>
|
||||
|
||||
|
||||
<!--
|
||||
<div
|
||||
class="dicom-viewer-root"
|
||||
id="viewer_named_simple"
|
||||
data-named-stacks='[
|
||||
{
|
||||
"name": "CT Head (Study A)",
|
||||
"imageIds": [
|
||||
"http://localhost:8000/media/atlas/dicom/head_0001.dcm",
|
||||
"http://localhost:8000/media/atlas/dicom/head_0002.dcm",
|
||||
"http://localhost:8000/media/atlas/dicom/head_0003.dcm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CT Chest (Study B)",
|
||||
"imageIds": [
|
||||
"wadouri:http://localhost:8000/media/atlas/dicom/chest_0001.dcm",
|
||||
"wadouri:http://localhost:8000/media/atlas/dicom/chest_0002.dcm"
|
||||
]
|
||||
}
|
||||
]'
|
||||
style="width:600px;height:400px;"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="dicom-viewer-root"
|
||||
id="viewer_named_cases"
|
||||
data-named-stacks='[
|
||||
{
|
||||
"caseId": "CASE-001",
|
||||
"studyId": "STUDY-001",
|
||||
"stacks": [
|
||||
{
|
||||
"name": "Abdomen - Portal Venous",
|
||||
"imageIds": [
|
||||
"http://localhost:8000/media/atlas/dicom/abdomen_pv_0001.dcm",
|
||||
"http://localhost:8000/media/atlas/dicom/abdomen_pv_0002.dcm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Abdomen - Non-contrast",
|
||||
"imageIds": [
|
||||
"http://localhost:8000/media/atlas/dicom/abdomen_nc_0001.dcm"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"caseId": "CASE-002",
|
||||
"studyId": "STUDY-002",
|
||||
"stacks": [
|
||||
{
|
||||
"name": "Chest - CTA",
|
||||
"imageIds": [
|
||||
"http://localhost:8000/media/atlas/dicom/cta_0001.dcm",
|
||||
"http://localhost:8000/media/atlas/dicom/cta_0002.dcm"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]'
|
||||
style="width:600px;height:400px;margin-top:12px;"
|
||||
></div> -->
|
||||
<!-- <div id="root2" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;"
|
||||
data-images="["http://localhost:8000/media/atlas/dicom/4596ea7654b7a5074d2c3f40039c556b11fb01ae.dcm", "http://localhost:8000/media/atlas/dicom/b2fa301bc731d15be3e000402c9a79c114d17aa5.dcm", "http://localhost:8000/media/atlas/dicom/66310ef801fbecb37c5ced160c450dfd5ecc6d2d.dcm", "http://localhost:8000/media/atlas/dicom/63d2d117bf256a76a61190066f91eda6c4d67c8f.dcm", "http://localhost:8000/media/atlas/dicom/181a3575ea7fb8bf8f6449ff61e6e369a3247454.dcm", "http://localhost:8000/media/atlas/dicom/7c5c0538339266ac8897fa21c481b2f61172068e.dcm", "http://localhost:8000/media/atlas/dicom/1681410681308af7111af6644fed598fde304b96.dcm", "http://localhost:8000/media/atlas/dicom/4adcd3d7a64a9c59b530f730ce0d991bf7f27813.dcm", "http://localhost:8000/media/atlas/dicom/671e6004c7fb402a627a2282bbf304d7b4dcc12b.dcm", "http://localhost:8000/media/atlas/dicom/8d18ac56914b9de72b8aecced09654ec4b2c9bb7.dcm", "http://localhost:8000/media/atlas/dicom/74ce708a90dff0636fc4bc914fe47e8b6d9320bd.dcm", "http://localhost:8000/media/atlas/dicom/a96b6c9900570e076edf849a0ae3b52734ed2650.dcm", "http://localhost:8000/media/atlas/dicom/0183718d9336458478ec42130395d78e01492bd6.dcm", "http://localhost:8000/media/atlas/dicom/52eee968bbce5590274346eed43f1837f9daba11.dcm", "http://localhost:8000/media/atlas/dicom/961449f88197566c4643fcfd20f48ea4e810490b.dcm", "http://localhost:8000/media/atlas/dicom/1a16ad5b25e11739eadbcb8ea461072b100113a0.dcm", "http://localhost:8000/media/atlas/dicom/b89ddcc3f499059d2dedb0028a2bb1ce4abf5574.dcm", "http://localhost:8000/media/atlas/dicom/c1379c03a4e449c2fa88784d601cb766af907739.dcm", "http://localhost:8000/media/atlas/dicom/4fb532d5003c59a73f7e4248f365b1a8660ea710.dcm", "http://localhost:8000/media/atlas/dicom/e861b0de042a1f213cbccc995679aacd29c7ef53.dcm", "http://localhost:8000/media/atlas/dicom/1a854282afe264c3afff6a6e287cd646b2193a0c.dcm", "http://localhost:8000/media/atlas/dicom/f59d6d9bc33160abad3af8e2b246179b3574d884.dcm", "http://localhost:8000/media/atlas/dicom/04fadab0ed8b3f1925e601c4f9cfb792c95626c2.dcm", "http://localhost:8000/media/atlas/dicom/d45d0b12fb650e65daeeee4e5fa3b21c988597f6.dcm", "http://localhost:8000/media/atlas/dicom/e19c47b7787ac45dbc225319dfa143dc2c1fdf1f.dcm", "http://localhost:8000/media/atlas/dicom/dccabf3103a5be254a45457ce2d8d9ce9cb19a6a.dcm"]"
|
||||
data-auto-cache-stack="true"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build && cp dist/index.js /home/ross/rad/rad/rad/static/dv3d/index.js",
|
||||
"build": "tsc && vite build && cp dist/index.js /home/ross/penracourses/rad/rad/static/dv3d/index.js",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Embedded Viewer — Test Page</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header{background:#111;color:#fff;padding:12px 20px}
|
||||
header h1{margin:0;font-size:18px}
|
||||
.page{display:flex;gap:20px;padding:20px}
|
||||
.main{flex:1}
|
||||
.sidebar{width:260px;background:#f6f6f6;padding:12px;border-radius:6px}
|
||||
.dicom-viewer-root{width:100%;height:72vh;background:#222;color:#fff;border-radius:6px;overflow:hidden}
|
||||
.controls{margin:12px 0}
|
||||
.note{background:#eef;padding:10px;border-radius:4px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Embedded Viewer Test</h1>
|
||||
</header>
|
||||
|
||||
<div class="page">
|
||||
<main class="main">
|
||||
<p class="note">This page demonstrates the DICOM viewer embedded alongside other content.</p>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="document.getElementById('main_viewer').scrollIntoView({behavior:'smooth'})">Scroll to viewer</button>
|
||||
<button onclick="console.log('Example action')">Example action</button>
|
||||
</div>
|
||||
|
||||
<div id="main_viewer" class="dicom-viewer-root" data-auto-cache-stack="false"></div>
|
||||
|
||||
<section style="margin-top:16px">
|
||||
<h2>Related information</h2>
|
||||
<p>Here you can place text, controls, or other UI components while the viewer stays mounted within the page layout.</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<aside class="sidebar">
|
||||
<h3>Sidebar</h3>
|
||||
<p>Extra controls, metadata, or notes can live here.</p>
|
||||
<ul>
|
||||
<li>Patient: Anonymous</li>
|
||||
<li>Study: Demo stack</li>
|
||||
<li>Notes: Test page</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Provide noop React Refresh preamble to avoid plugin-react runtime errors -->
|
||||
<script>
|
||||
window.$RefreshReg$ = window.$RefreshReg$ || function() {};
|
||||
window.$RefreshSig$ = window.$RefreshSig$ || function() { return function(type) { return type; }; };
|
||||
</script>
|
||||
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<script>
|
||||
// Reuse the same viewer state as the penra test for convenience
|
||||
const state = {"grid":{"rows":1,"cols":3},"modes":["stack","stack","stack"],"stacks":[{"i":["wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_573_PN7j4gd.dcm","wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_580_kBgyDxS.dcm","wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_572_DYQisSg.dcm"]},{"i":["wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_588_tGfMjqb.dcm","wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_592_Js4efiI.dcm","wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_584_U8WCsNY.dcm"]},{"i":["wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_631_NxRBhMn.dcm","wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_619_2cVhUyC.dcm","wadouri:https://www.penracourses.org.uk/media/atlas/dicom/IMG_607_VvL52Af.dcm"]}],"stackIdx":[0,2,1],"stackPos":[0,0,2],"props":[{"voiRange":{"lower":39.15,"upper":559.17},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR","colormap":{"name":"Grayscale","opacity":[]}}],"cam":[null,null,null],"orientations":[null,null,null],"volumeSliceIndices":[null,null,null]};
|
||||
|
||||
function callWhenReady(fnName, payload) {
|
||||
const maxWait = 5000;
|
||||
const start = Date.now();
|
||||
(function check() {
|
||||
if (window[fnName] && typeof window[fnName] === 'function') {
|
||||
try {
|
||||
window[fnName](payload);
|
||||
console.log('imported viewer state via', fnName);
|
||||
} catch (e) {
|
||||
console.error('importViewerState call failed', e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Date.now() - start > maxWait) {
|
||||
console.warn('Timed out waiting for', fnName);
|
||||
return;
|
||||
}
|
||||
setTimeout(check, 2000);
|
||||
})();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
(function loadStacksThenImport() {
|
||||
if (!state.stacks || !Array.isArray(state.stacks)) {
|
||||
callWhenReady('importViewerState_main_viewer', state);
|
||||
return;
|
||||
}
|
||||
|
||||
function pushStacks() {
|
||||
const fnName = 'loadAdditionalStack_main_viewer';
|
||||
if (window[fnName] && typeof window[fnName] === 'function') {
|
||||
try {
|
||||
state.stacks.forEach(s => {
|
||||
if (s && Array.isArray(s.i) && s.i.length) {
|
||||
window[fnName](s.i);
|
||||
}
|
||||
});
|
||||
console.log('Requested viewer to load', state.stacks.length, 'stacks');
|
||||
} catch (e) {
|
||||
console.warn('loadAdditionalStack call failed', e);
|
||||
}
|
||||
setTimeout(() => { callWhenReady('importViewerState_main_viewer', state); }, 2000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const started = pushStacks();
|
||||
if (!started) {
|
||||
const maxWait = 5000;
|
||||
const start = Date.now();
|
||||
(function waitForApi() {
|
||||
if (pushStacks()) return;
|
||||
if (Date.now() - start > maxWait) {
|
||||
console.warn('Timed out waiting for loadAdditionalStack API; importing state directly');
|
||||
callWhenReady('importViewerState_main_viewer', state);
|
||||
return;
|
||||
}
|
||||
setTimeout(waitForApi, 2000);
|
||||
})();
|
||||
}
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Empty Viewer Test</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0}
|
||||
.dicom-viewer-root{width:100%;height:100vh;background:#222;color:#fff}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="dicom-viewer-root" style="width:100%;height:100vh;"
|
||||
data-auto-cache-stack="false"
|
||||
data-empty="true"
|
||||
></div>
|
||||
|
||||
<!-- Provide a small noop React Refresh preamble to avoid plugin-react preamble errors -->
|
||||
<script>
|
||||
// If plugin-react's preamble wasn't injected for some reason, provide no-op
|
||||
// implementations so the runtime does not throw. This disables fast-refresh
|
||||
// semantics for this page but keeps HMR from crashing.
|
||||
window.$RefreshReg$ = window.$RefreshReg$ || function() {};
|
||||
window.$RefreshSig$ = window.$RefreshSig$ || function() { return function(type) { return type; }; };
|
||||
</script>
|
||||
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,196 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Load Directory Test</title>
|
||||
<style>
|
||||
body { font-family: system-ui, Arial; padding: 18px; background: #f6f6f8; color: #111 }
|
||||
.controls { margin: 12px 0 }
|
||||
.file-row { display:flex; gap:8px; align-items:center; padding:6px 8px; border-bottom:1px solid #eee }
|
||||
.path { font-family: monospace; color:#444; flex:1 }
|
||||
button { padding:6px 10px }
|
||||
#fileList { border:1px solid #ddd; background:#fff; border-radius:6px; max-height:60vh; overflow:auto }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Directory Loader (recursive)</h2>
|
||||
<p>Select a folder (uses `webkitdirectory`) — files will be read recursively.
|
||||
This page will call <code>window.loadDicomStackFromFiles(files)</code> if available in the viewer.</p>
|
||||
|
||||
<div class="controls">
|
||||
<input id="dirInput" type="file" webkitdirectory directory multiple />
|
||||
<label style="margin-left:12px"><input id="onlyDcm" type="checkbox" checked /> Show only <code>.dcm</code></label>
|
||||
<button id="loadSelected">Load Selected</button>
|
||||
<button id="loadAll">Load All</button>
|
||||
<button id="openViewerAndSend">Open Viewer & Send</button>
|
||||
<button id="clear">Clear</button>
|
||||
</div>
|
||||
|
||||
<div id="fileList"></div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const input = document.getElementById('dirInput');
|
||||
const fileListDiv = document.getElementById('fileList');
|
||||
const onlyDcmChk = document.getElementById('onlyDcm');
|
||||
|
||||
function asArray(fileList){ return Array.prototype.slice.call(fileList || []); }
|
||||
|
||||
function renderFiles(files){
|
||||
fileListDiv.innerHTML = '';
|
||||
if (!files || files.length === 0) {
|
||||
fileListDiv.innerHTML = '<div style="padding:12px;color:#666">No files selected</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
files.forEach((f, idx) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'file-row';
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.checked = true;
|
||||
cb.dataset.idx = idx;
|
||||
|
||||
const pathEl = document.createElement('div');
|
||||
pathEl.className = 'path';
|
||||
// webkitRelativePath shows the folder structure when directory input used
|
||||
pathEl.textContent = f.webkitRelativePath || f.name;
|
||||
|
||||
const sizeEl = document.createElement('div');
|
||||
sizeEl.style.width = '90px';
|
||||
sizeEl.style.textAlign = 'right';
|
||||
sizeEl.style.color = '#666';
|
||||
sizeEl.textContent = (f.size/1024).toFixed(1)+' kB';
|
||||
|
||||
row.appendChild(cb);
|
||||
row.appendChild(pathEl);
|
||||
row.appendChild(sizeEl);
|
||||
|
||||
frag.appendChild(row);
|
||||
});
|
||||
fileListDiv.appendChild(frag);
|
||||
}
|
||||
|
||||
function filterFiles(files){
|
||||
const onlyDcm = onlyDcmChk.checked;
|
||||
if (!onlyDcm) return files;
|
||||
return files.filter(f => f.name.toLowerCase().endsWith('.dcm'));
|
||||
}
|
||||
|
||||
input.addEventListener('change', ()=>{
|
||||
const all = asArray(input.files);
|
||||
const filtered = filterFiles(all);
|
||||
// Save filtered list on the element for retrieval
|
||||
input._currentFiles = filtered;
|
||||
renderFiles(filtered);
|
||||
});
|
||||
|
||||
document.getElementById('loadSelected').addEventListener('click', ()=>{
|
||||
const current = input._currentFiles || [];
|
||||
if (!current.length) return alert('No files selected. Choose a folder first.');
|
||||
// collect checked indices
|
||||
const checked = Array.from(fileListDiv.querySelectorAll('input[type=checkbox]'))
|
||||
.filter(cb=>cb.checked)
|
||||
.map(cb=>parseInt(cb.dataset.idx,10))
|
||||
.filter(n=>!Number.isNaN(n));
|
||||
const chosen = checked.map(i=>current[i]).filter(Boolean);
|
||||
if (!chosen.length) return alert('No files checked.');
|
||||
sendToViewer(chosen);
|
||||
});
|
||||
|
||||
document.getElementById('loadAll').addEventListener('click', ()=>{
|
||||
const current = input._currentFiles || [];
|
||||
if (!current.length) return alert('No files selected. Choose a folder first.');
|
||||
sendToViewer(current);
|
||||
});
|
||||
|
||||
document.getElementById('openViewerAndSend').addEventListener('click', async ()=>{
|
||||
const current = input._currentFiles || [];
|
||||
if (!current.length) return alert('No files selected. Choose a folder first.');
|
||||
// Open the viewer root in a new tab/window
|
||||
const viewerWin = window.open('/', '_blank');
|
||||
if (!viewerWin) return alert('Failed to open viewer window (popup blocked).');
|
||||
|
||||
// Wait for the viewer window to expose loadDicomStackFromFiles
|
||||
const timeoutMs = 15000;
|
||||
const start = Date.now();
|
||||
const poll = () => new Promise((resolve, reject) => {
|
||||
const iv = setInterval(() => {
|
||||
try {
|
||||
if (viewerWin && (viewerWin.loadDicomStackFromFiles || viewerWin.loadDicomStackFromWadouriList)) {
|
||||
clearInterval(iv);
|
||||
resolve(true);
|
||||
}
|
||||
if (Date.now() - start > timeoutMs) {
|
||||
clearInterval(iv);
|
||||
reject(new Error('Timed out waiting for viewer API'));
|
||||
}
|
||||
} catch (e) {
|
||||
// cross-origin or not ready
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
|
||||
try {
|
||||
await poll();
|
||||
// call the function on the opened viewer window
|
||||
if (viewerWin.loadDicomStackFromFiles) {
|
||||
viewerWin.loadDicomStackFromFiles(current);
|
||||
alert('Sent '+current.length+' file(s) to the newly opened viewer.');
|
||||
} else if (viewerWin.loadDicomStackFromWadouriList) {
|
||||
// fallback for remote URLs
|
||||
const wadouriList = current.map(f => 'wadouri:' + URL.createObjectURL(f));
|
||||
viewerWin.loadDicomStackFromWadouriList(wadouriList);
|
||||
alert('Sent '+current.length+' wadouri image(s) to the newly opened viewer.');
|
||||
} else {
|
||||
alert('Viewer opened but API not available.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('openViewerAndSend failed', e);
|
||||
alert('Timed out waiting for viewer to be ready. Check the viewer tab.');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
input.value = null;
|
||||
input._currentFiles = [];
|
||||
renderFiles([]);
|
||||
});
|
||||
|
||||
function sendToViewer(files){
|
||||
// Prefer calling the viewer global if present
|
||||
if (window.loadDicomStackFromFiles && typeof window.loadDicomStackFromFiles === 'function') {
|
||||
try {
|
||||
// The viewer accepts FileList or File[] per typing; pass array
|
||||
window.loadDicomStackFromFiles(files);
|
||||
alert('Sent '+files.length+' file(s) to viewer (window.loadDicomStackFromFiles).');
|
||||
return;
|
||||
} catch (e) {
|
||||
console.error('loadDicomStackFromFiles failed', e);
|
||||
alert('Viewer call failed (see console).');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try posting to parent (if embedded)
|
||||
if (window.parent && window.parent !== window) {
|
||||
try {
|
||||
window.parent.postMessage({ type: 'loadFiles', filesCount: files.length }, '*');
|
||||
alert('Posted message to parent with '+files.length+' files.');
|
||||
return;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
alert('Viewer integration function `window.loadDicomStackFromFiles` not found on this page.');
|
||||
}
|
||||
|
||||
// Initial empty render
|
||||
renderFiles([]);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DV3D — Non-DICOM Test</title>
|
||||
<style>
|
||||
html,body { height:100%; margin:0; background:#111 }
|
||||
#non-dicom-root { width: 100vw; height: 100vh; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="non-dicom-root" class="dicom-viewer-root"></div>
|
||||
<!-- Provide a small noop React Refresh preamble to avoid plugin-react preamble errors -->
|
||||
<script>
|
||||
// If plugin-react's preamble wasn't injected for some reason, provide no-op
|
||||
// implementations so the runtime does not throw. This disables fast-refresh
|
||||
// semantics for this page but keeps HMR from crashing.
|
||||
window.$RefreshReg$ = window.$RefreshReg$ || function() {};
|
||||
window.$RefreshSig$ = window.$RefreshSig$ || function() { return function(type) { return type; }; };
|
||||
</script>
|
||||
<!-- Vite can load TSX modules directly in dev mode -->
|
||||
<script type="module" src="/src/nonDicomTest.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Test Pages — Links</title>
|
||||
<style>
|
||||
body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial;margin:24px;color:#111}
|
||||
h1{margin-top:0}
|
||||
ul{line-height:1.6}
|
||||
a{color:#0366d6}
|
||||
.note{background:#f6f8fa;padding:10px;border-radius:6px;margin-bottom:12px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Available Test Pages</h1>
|
||||
<p class="note">Open any of these pages directly in the dev server to view the corresponding test.</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="/penra-test.html">penra-test.html</a> — original Penra viewer test</li>
|
||||
<li><a href="/embedded-viewer-test.html">embedded-viewer-test.html</a> — viewer embedded with surrounding content</li>
|
||||
<li><a href="/empty-viewer.html">empty-viewer.html</a> — empty viewer placeholder</li>
|
||||
<li><a href="/load-dir-test.html">load-dir-test.html</a> — directory loading test</li>
|
||||
<li><a href="/non-dicom-test.html">non-dicom-test.html</a> — non-DICOM handling test</li>
|
||||
<li><a href="/index.html">index.html</a> — project index</li>
|
||||
</ul>
|
||||
|
||||
<p>If you want these links added to the README, tell me and I will update it.</p>
|
||||
</body>
|
||||
</html>
|
||||
+2041
-260
File diff suppressed because it is too large
Load Diff
@@ -50,3 +50,39 @@ html, body, #root {
|
||||
.cornerstone-viewport, .cornerstone-canvas {
|
||||
cursor: inherit !important;
|
||||
}
|
||||
|
||||
/* Make annotation displays smaller and more compact inside viewports */
|
||||
.cornerstone-viewport {
|
||||
/* Target SVG text used by tools */
|
||||
}
|
||||
.cornerstone-viewport svg text,
|
||||
.cornerstone-viewport .annotation-label,
|
||||
.cornerstone-viewport .annotation-text,
|
||||
.cornerstone-viewport .cornerstone-annotation,
|
||||
.cornerstone-viewport .ct-annotation,
|
||||
.cornerstone-viewport .annotation {
|
||||
font-size: 11px !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
/* Smaller text boxes / labels */
|
||||
.cornerstone-viewport .cornerstone-annotation-label,
|
||||
.cornerstone-viewport .annotation-text {
|
||||
padding: 2px 4px !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
/* Reduce handle and marker sizes for compactness */
|
||||
.cornerstone-viewport .handle,
|
||||
.cornerstone-viewport .annotation-handle,
|
||||
.cornerstone-viewport .ct-handle,
|
||||
.cornerstone-viewport .cornerstone-handle {
|
||||
width: 8px !important;
|
||||
height: 8px !important;
|
||||
}
|
||||
|
||||
/* If tools render HTML overlays, make them compact */
|
||||
.cornerstone-viewport .ct-annotation-overlay {
|
||||
font-size: 11px !important;
|
||||
padding: 2px !important;
|
||||
}
|
||||
+121
-6
@@ -6,9 +6,34 @@ import Nifti from './Nifti.tsx'
|
||||
import './index.css'
|
||||
import { createImageIds, availableImageIds } from "./lib/createImageIds.ts"
|
||||
|
||||
// Helper to convert URLs to wadouri
|
||||
const toWadouri = (arr: string[]) =>
|
||||
arr.map(url => url.startsWith("wadouri:") ? url : `wadouri:${url}`);
|
||||
// Normalize incoming image ids/URLs and ensure DICOM entries use a valid wadouri: prefix.
|
||||
const normalizeImageId = (value: string) => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return trimmed;
|
||||
|
||||
// Keep non-wadouri schemes untouched.
|
||||
if (trimmed.startsWith("external:") || trimmed.startsWith("wadors:")) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
// Fix malformed values like "wadouri::https://..." by removing extra leading colons.
|
||||
if (trimmed.startsWith("wadouri:")) {
|
||||
const rest = trimmed.slice("wadouri:".length).replace(/^:+/, "");
|
||||
return `wadouri:${rest}`;
|
||||
}
|
||||
|
||||
// Also handle malformed absolute URLs such as ":https://...".
|
||||
const sanitized = trimmed.replace(/^:+/, "");
|
||||
return `wadouri:${sanitized}`;
|
||||
};
|
||||
|
||||
const toWadouri = (arr: string[]) => arr.map(normalizeImageId);
|
||||
|
||||
// Normalize a simple array -> descriptor object
|
||||
const toDescriptor = (arr: string[] | undefined) => {
|
||||
if (!arr) return { imageIds: [] as string[] };
|
||||
return { imageIds: toWadouri(arr) };
|
||||
};
|
||||
|
||||
export function mountDicomViewers() {
|
||||
// Test containers
|
||||
@@ -28,10 +53,98 @@ export function mountDicomViewers() {
|
||||
const containers = document.querySelectorAll(".dicom-viewer-root");
|
||||
containers.forEach((container) => {
|
||||
const id = container.id || '';
|
||||
let imageStacks;
|
||||
let imageStacks: () => Promise<any[]>;
|
||||
const isEmpty = container.getAttribute('data-empty') === 'true';
|
||||
|
||||
// New: support data-named-stacks (richer format) and fallback to data-images
|
||||
const dataNamed = container.getAttribute('data-named-stacks');
|
||||
const dataImages = container.getAttribute('data-images');
|
||||
if (dataImages) {
|
||||
|
||||
const parseToDescriptors = (parsed: any): Array<any> => {
|
||||
// Return array of descriptors: either simple string[] => { imageIds: string[] }
|
||||
// or objects: { imageIds: string[], name?, caseId?, studyId? }
|
||||
if (!parsed) return [];
|
||||
|
||||
// If top-level is an array of strings -> single unnamed stack
|
||||
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "string") {
|
||||
return [toDescriptor(parsed)];
|
||||
}
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
const out: any[] = [];
|
||||
parsed.forEach((item, itemIdx) => {
|
||||
// Case object with nested stacks: { caseId, studyId, stacks: [ { name, imageIds } ] }
|
||||
if (item && typeof item === "object" && Array.isArray(item.stacks)) {
|
||||
const caseId = item.caseId || item.caseUID || item.case || undefined;
|
||||
const studyId = item.studyId || item.studyUID || item.studyInstanceUID || undefined;
|
||||
item.stacks.forEach((s: any, sIdx: number) => {
|
||||
let imageIds: string[] = [];
|
||||
if (Array.isArray(s)) imageIds = s;
|
||||
else if (Array.isArray(s.imageIds)) imageIds = s.imageIds;
|
||||
else if (Array.isArray(s.i)) imageIds = s.i;
|
||||
|
||||
// Validation
|
||||
if (!Array.isArray(imageIds) || imageIds.length === 0 || !imageIds.every(x => typeof x === "string")) {
|
||||
console.warn(`data-named-stacks: skipping invalid nested stack at parsed[${itemIdx}].stacks[${sIdx}]`, s);
|
||||
return;
|
||||
}
|
||||
|
||||
out.push({
|
||||
imageIds: toWadouri(imageIds),
|
||||
name: s.name || s.label || undefined,
|
||||
caseId,
|
||||
studyId: s.studyId || studyId,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Stack object: { imageIds, i, name, caseId, studyId }
|
||||
if (item && typeof item === 'object' && (Array.isArray(item.imageIds) || Array.isArray(item.i))) {
|
||||
const imageIds = Array.isArray(item.imageIds) ? item.imageIds : item.i;
|
||||
|
||||
if (!Array.isArray(imageIds) || imageIds.length === 0 || !imageIds.every(x => typeof x === "string")) {
|
||||
console.warn(`data-named-stacks: skipping invalid stack object at parsed[${itemIdx}]`, item);
|
||||
return;
|
||||
}
|
||||
|
||||
out.push({
|
||||
imageIds: toWadouri(imageIds),
|
||||
name: item.name || item.label || undefined,
|
||||
caseId: item.caseId || item.caseUID || undefined,
|
||||
studyId: item.studyId || item.studyUID || item.studyInstanceUID || undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Plain array (stack)
|
||||
if (Array.isArray(item) && item.length > 0 && typeof item[0] === "string") {
|
||||
out.push(toDescriptor(item));
|
||||
return;
|
||||
}
|
||||
|
||||
// Unknown / invalid entry
|
||||
console.warn(`data-named-stacks: unknown or invalid entry at parsed[${itemIdx}] — skipping`, item);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
if (isEmpty) {
|
||||
imageStacks = () => Promise.resolve([] as any[]);
|
||||
} else if (dataNamed) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(dataNamed);
|
||||
} catch (e) {
|
||||
console.error("Invalid data-named-stacks JSON:", e);
|
||||
parsed = [];
|
||||
}
|
||||
const descriptors = parseToDescriptors(parsed);
|
||||
imageStacks = () => Promise.resolve(descriptors);
|
||||
} else if (dataImages) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(dataImages);
|
||||
@@ -39,6 +152,7 @@ export function mountDicomViewers() {
|
||||
console.error("Invalid data-images JSON:", e);
|
||||
parsed = [];
|
||||
}
|
||||
// Keep old behavior for data-images
|
||||
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "string") {
|
||||
imageStacks = () => Promise.resolve([toWadouri(parsed)]);
|
||||
} else if (Array.isArray(parsed)) {
|
||||
@@ -49,7 +163,8 @@ export function mountDicomViewers() {
|
||||
imageStacks = () => Promise.resolve([[]]);
|
||||
}
|
||||
} else {
|
||||
imageStacks = () => Promise.resolve([[]]);
|
||||
// No data provided: return no stacks (empty array) so viewer starts empty
|
||||
imageStacks = () => Promise.resolve([] as any[]);
|
||||
}
|
||||
|
||||
const autoCacheStackAttr = container.getAttribute('data-auto-cache-stack');
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
// Sample external images (use CORS-friendly URLs). These are Wikimedia Commons images.
|
||||
const sampleImages = [
|
||||
'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png',
|
||||
'https://upload.wikimedia.org/wikipedia/commons/6/6a/PNG_Test.png',
|
||||
]
|
||||
|
||||
// Helper to prefix with `external:` scheme used by the app's loader
|
||||
const toExternal = (urls: string[]) => urls.map(u => u.startsWith('external:') ? u : `external:${u}`)
|
||||
|
||||
function mount() {
|
||||
const container = document.getElementById('non-dicom-root')
|
||||
if (!container) return
|
||||
|
||||
const imageStacks = () => {
|
||||
// Return a single stack with our sample images
|
||||
return Promise.resolve([
|
||||
{
|
||||
imageIds: toExternal(sampleImages),
|
||||
name: 'Non-DICOM Sample Stack',
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
// Ensure React Refresh globals exist before loading App so plugin-react
|
||||
// preamble checks won't fail during HMR. We then dynamically import App.
|
||||
// This guarantees the globals are set before App is evaluated.
|
||||
// Provide no-op implementations when not present.
|
||||
;(window as any).$RefreshReg$ = (window as any).$RefreshReg$ || function() {}
|
||||
;(window as any).$RefreshSig$ = (window as any).$RefreshSig$ || function() { return function(type: any) { return type; }; }
|
||||
|
||||
import('./App').then(({ default: App }) => {
|
||||
createRoot(container).render(
|
||||
<React.StrictMode>
|
||||
<App container_id={'nondicom'} imageStacks={imageStacks} />
|
||||
</React.StrictMode>
|
||||
)
|
||||
}).catch(err => {
|
||||
console.error('Failed to load App for non-dicom test', err)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for DOM
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', mount)
|
||||
} else {
|
||||
mount()
|
||||
}
|
||||
Reference in New Issue
Block a user