feat: vite
This commit is contained in:
parent
6b188b5354
commit
b31b0c422c
|
@ -1,42 +1,85 @@
|
|||
import {
|
||||
readImageDicomFileSeries,
|
||||
structuredReportToHtml,
|
||||
} from "@itk-wasm/dicom";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { readImageDicomFileSeries } from "@itk-wasm/dicom";
|
||||
import vtkITKHelper from "@kitware/vtk.js/Common/DataModel/ITKHelper";
|
||||
import vtkVolume from "@kitware/vtk.js/Rendering/Core/Volume";
|
||||
import vtkFullScreenRenderWindow from "@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow";
|
||||
import "@kitware/vtk.js/Rendering/Profiles/Volume";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import vtkRenderer from "@kitware/vtk.js/Rendering/Core/Renderer";
|
||||
import vtkVolumeMapper from "@kitware/vtk.js/Rendering/Core/VolumeMapper";
|
||||
import vtkRenderWindow from "@kitware/vtk.js/Rendering/Core/RenderWindow";
|
||||
|
||||
import "./app.css";
|
||||
import {
|
||||
checkExistenceInIndexedDB,
|
||||
loadImageFromIndexedDB,
|
||||
saveImageToIndexedDB,
|
||||
} from "./util";
|
||||
import { extractAxialSliceAsImage } from "./extract";
|
||||
|
||||
function App() {
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const axialRef = useRef<HTMLElement | null>(null);
|
||||
const sagittalRef = useRef<HTMLElement | null>(null);
|
||||
const coronalRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
const rendererRef = useRef<vtkRenderer>();
|
||||
const mapperRef = useRef<vtkVolumeMapper>();
|
||||
const actorRef = useRef<vtkVolume>();
|
||||
const renderWindowRef = useRef<vtkRenderWindow>();
|
||||
|
||||
const [image, setImage] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.webkitdirectory = true;
|
||||
}
|
||||
if (inputRef.current) inputRef.current.webkitdirectory = true;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkExistenceInIndexedDB("outputImageKey").then((exist) => {
|
||||
if (exist) {
|
||||
console.time("start load from cache");
|
||||
loadImageFromIndexedDB().then((outputImage) => {
|
||||
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
|
||||
container: axialRef.current,
|
||||
});
|
||||
rendererRef.current = fullScreenRenderer.getRenderer();
|
||||
renderWindowRef.current = fullScreenRenderer.getRenderWindow();
|
||||
actorRef.current = vtkVolume.newInstance();
|
||||
mapperRef.current = vtkVolumeMapper.newInstance({
|
||||
sampleDistance: 1.1,
|
||||
});
|
||||
const vtkImage = vtkITKHelper.convertItkToVtkImage(outputImage);
|
||||
mapperRef.current?.setInputData(vtkImage);
|
||||
mapperRef.current && actorRef.current?.setMapper(mapperRef.current);
|
||||
actorRef.current && rendererRef.current?.addVolume(actorRef.current);
|
||||
rendererRef.current?.resetCamera();
|
||||
renderWindowRef.current?.render();
|
||||
console.timeEnd("start load from cache");
|
||||
|
||||
const imgSource = extractAxialSliceAsImage(vtkImage, 1);
|
||||
setImage(imgSource);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (files.length > 0) {
|
||||
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({});
|
||||
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
|
||||
container: axialRef.current,
|
||||
});
|
||||
rendererRef.current = fullScreenRenderer.getRenderer();
|
||||
renderWindowRef.current = fullScreenRenderer.getRenderWindow();
|
||||
actorRef.current = vtkVolume.newInstance();
|
||||
mapperRef.current = vtkVolumeMapper.newInstance({ sampleDistance: 1.1 });
|
||||
|
||||
console.time("itk-wasm parse vtkVolumeImageData");
|
||||
readImageDicomFileSeries(null, {
|
||||
inputImages: files,
|
||||
}).then((image) => {
|
||||
const vtkImage = vtkITKHelper.convertItkToVtkImage(image.outputImage);
|
||||
console.timeEnd("itk-wasm parse vtkVolumeImageData");
|
||||
saveImageToIndexedDB(image.outputImage);
|
||||
mapperRef.current?.setInputData(vtkImage);
|
||||
mapperRef.current && actorRef.current?.setMapper(mapperRef.current);
|
||||
actorRef.current && rendererRef.current?.addVolume(actorRef.current);
|
||||
|
@ -72,6 +115,12 @@ function App() {
|
|||
ref={inputRef}
|
||||
style={{ position: "fixed", zIndex: 100 }}
|
||||
/>
|
||||
<div className="mpr">
|
||||
<div ref={axialRef} style={{ width: 500, height: 500 }}></div>
|
||||
{/* <div ref={sagittalRef} style={{ width: 300, height: 300 }}></div>
|
||||
<div ref={coronalRef} style={{ width: 300, height: 300 }}></div> */}
|
||||
</div>
|
||||
<img src={image} style={{ width: 200, height: 200 }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
8
apps/phoenix/src/app.css
Normal file
8
apps/phoenix/src/app.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.mpr {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
38
apps/phoenix/src/extract.ts
Normal file
38
apps/phoenix/src/extract.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import vtkImageData from "@kitware/vtk.js/Common/DataModel/ImageData";
|
||||
|
||||
export const extractAxialSliceAsImage = (
|
||||
volumeData: vtkImageData,
|
||||
sliceIndex: number
|
||||
): string => {
|
||||
const dimensions = volumeData.getDimensions();
|
||||
|
||||
if (sliceIndex < 0 || sliceIndex >= dimensions[2]) {
|
||||
throw new Error("Invalid slice index");
|
||||
}
|
||||
|
||||
const scalars = volumeData.getPointData().getScalars();
|
||||
const slice = scalars
|
||||
.getData()
|
||||
.slice(
|
||||
sliceIndex * dimensions[0] * dimensions[1],
|
||||
(sliceIndex + 1) * dimensions[0] * dimensions[1]
|
||||
);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = dimensions[0];
|
||||
canvas.height = dimensions[1];
|
||||
const context = canvas.getContext("2d")!;
|
||||
const imageDataObj = context.createImageData(dimensions[0], dimensions[1]);
|
||||
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
const value = slice[i];
|
||||
imageDataObj.data[i * 4] = value;
|
||||
imageDataObj.data[i * 4 + 1] = value;
|
||||
imageDataObj.data[i * 4 + 2] = value;
|
||||
imageDataObj.data[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
context.putImageData(imageDataObj, 0, 0);
|
||||
|
||||
return canvas.toDataURL();
|
||||
};
|
91
apps/phoenix/src/util.ts
Normal file
91
apps/phoenix/src/util.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
const dbName = "myDatabase";
|
||||
const storeName = "images";
|
||||
|
||||
export const openDatabase = async (): Promise<IDBDatabase> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(dbName);
|
||||
|
||||
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(storeName)) {
|
||||
db.createObjectStore(storeName);
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = (event) => reject(request.error);
|
||||
request.onsuccess = (event) => resolve(request.result);
|
||||
});
|
||||
};
|
||||
|
||||
export const saveImageToIndexedDB = async (outputImage: any) => {
|
||||
try {
|
||||
const db = await openDatabase();
|
||||
|
||||
const transaction = db.transaction(storeName, "readwrite");
|
||||
const store = transaction.objectStore(storeName);
|
||||
|
||||
const request = store.put(outputImage, "outputImageKey");
|
||||
request.onerror = (event) =>
|
||||
console.error("Error saving to IndexedDB", request.error);
|
||||
request.onsuccess = (event) => console.log("Image saved to IndexedDB");
|
||||
} catch (error) {
|
||||
console.error("Error with IndexedDB operation", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const loadImageFromIndexedDB = async (): Promise<any> => {
|
||||
try {
|
||||
const db = await openDatabase();
|
||||
|
||||
const transaction = db.transaction(storeName, "readonly");
|
||||
const store = transaction.objectStore(storeName);
|
||||
|
||||
const request = store.get("outputImageKey");
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onerror = (event) => {
|
||||
console.error("Error fetching from IndexedDB", request.error);
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = (event) => {
|
||||
if (request.result) {
|
||||
console.log("Image loaded from IndexedDB");
|
||||
resolve(request.result);
|
||||
} else {
|
||||
console.log("No data found in IndexedDB");
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error with IndexedDB operation", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const checkExistenceInIndexedDB = async (
|
||||
key: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const dbName = "myDatabase";
|
||||
const storeName = "images";
|
||||
const db = await openDatabase();
|
||||
|
||||
const transaction = db.transaction(storeName, "readonly");
|
||||
const store = transaction.objectStore(storeName);
|
||||
|
||||
const request = store.get(key);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onerror = (event) => {
|
||||
console.error("Error fetching from IndexedDB", request.error);
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = (event) => {
|
||||
resolve(request.result !== undefined);
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error with IndexedDB operation", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user