feat: vite

This commit is contained in:
mozzie 2023-12-27 16:21:54 +08:00
parent 6b188b5354
commit b31b0c422c
4 changed files with 196 additions and 10 deletions

View File

@ -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
View File

@ -0,0 +1,8 @@
.mpr {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
}

View 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
View 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;
}
};