cornerstonejs 3D 網頁醫學影像套件
是很完整的3D醫學影像套件
他預設是 nodejs 套件
預設也是 nodejs 環境
然而 javascript 的通用性
讓 python django 也可以套用到網頁上
套用的重點是需要先安裝 nodejs
安裝完之後就可以使用 npm 了
然後在 django 裡面開一個資料夾
假設是 cornerstonejs
到 cornerstonejs 裡面用 npm 安裝 cornerstonejs
npm install @cornerstonejs/core npm install @cornerstonejs/tools npm install @cornerstonejs/dicom-image-loader npm install @cornerstonejs/nifti-volume-loader npm install --save-dev vite npm install @vitejs/plugin-react npm install @originjs/vite-plugin-commonjs npm install @cornerstonejs/calculate-suv npm install @cornerstonejs/polymorphic-segmentation npm install @cornerstonejs/demo-utils
然後程式參考
https://www.cornerstonejs.org/docs/tutorials/basic-volume
重點是在於 這一行
import { createImageIdsAndCacheMetaData } from '../../../../utils/demo/helpers';
這邊的 helper 必須要自定義
例如改成
import createImageIdsAndCacheMetaDataZ from './my-helper';
my-helper 要自己新增
裡面的程式參考
https://github.com/cornerstonejs/cornerstone3D/blob/main/utils/demo/helpers/createImageIdsAndCacheMetaData.js
也就是說
必須要自訂義自己的 createImageIdsAndCacheMetaData 才可以
可以參考我寫的
import { api } from 'dicomweb-client'; import dcmjs from 'dcmjs'; import { calculateSUVScalingFactors } from '@cornerstonejs/calculate-suv'; import { getPTImageIdInstanceMetadata } from './helpers/getPTImageIdInstanceMetadata'; import { utilities } from '@cornerstonejs/core'; import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import ptScalingMetaDataProvider from './helpers/ptScalingMetaDataProvider'; import { convertMultiframeImageIds } from './helpers/convertMultiframeImageIds'; import removeInvalidTags from './helpers/removeInvalidTags'; const { DicomMetaDictionary } = dcmjs.data; const { calibratedPixelSpacingMetadataProvider, getPixelSpacingInformation } = utilities; /** /** * Uses dicomweb-client to fetch metadata of a study, cache it in cornerstone, * and return a list of imageIds for the frames. * * Uses the app config to choose which study to fetch, and which * dicom-web server to fetch it from. * * @returns {string[]} An array of imageIds for instances in the study. */ export default async function createImageIdsAndCacheMetaDataZ({ modality, imagefs, }) { const SOP_INSTANCE_UID = '00080018'; const SERIES_INSTANCE_UID = '0020000E'; const MODALITY = '00080060'; const convertMultiframe = true; // const modality = modality; let imageIds = imagefs.map((ImageIdUrl) => { const SeriesInstanceUID = '00000'; const SOPInstanceUIDToUse = '00001'; const prefix = 'wadors:'; var instanceMetaData = { '00080018' : {Value : [ '00000' ]}, //SOP_INSTANCE_UID '0020000E' : {Value : [ '00000' ]}, //SERIES_INSTANCE_UID '00080060' : {Value : [ modality ]}, //MODALITY }; const imageId = ImageIdUrl; cornerstoneDICOMImageLoader.wadors.metaDataManager.add( imageId, instanceMetaData ); return imageId; }); // if the image ids represent multiframe information, creates a new list with one image id per frame // if not multiframe data available, just returns the same list given if (convertMultiframe) { imageIds = convertMultiframeImageIds(imageIds); } imageIds.forEach((imageId) => { let instanceMetaData = cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId); if (!instanceMetaData) { return; } // It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower instanceMetaData = removeInvalidTags(instanceMetaData); if (instanceMetaData) { // Add calibrated pixel spacing const metadata = DicomMetaDictionary.naturalizeDataset(instanceMetaData); const pixelSpacingInformation = getPixelSpacingInformation(metadata); const pixelSpacing = pixelSpacingInformation?.PixelSpacing; if (pixelSpacing) { calibratedPixelSpacingMetadataProvider.add(imageId, { rowPixelSpacing: parseFloat(pixelSpacing[0]), columnPixelSpacing: parseFloat(pixelSpacing[1]), type: pixelSpacingInformation.type, }); } } }); return imageIds; }
可以發現裡面很多資料是寫死的
因為在
var ct_imageIds = await createImageIdsAndCacheMetaDataZ({ modality : 'MD', imagefs : ct_load_imagefs, });
這邊 createImageIdsAndCacheMetaDataZ 原本是用來配合 dicomweb 來讀取影像的
類似於你需要另外架設一個 dicom server 來顯示dicom影像
但這對於我們只是單純展示 dicom web 的概念就衝突了
我們只會是一個 web server
所以這邊我們才需要自訂義
然後 ct_load_imagefs 這邊我們可以給例如
['wadouri:/dicomfun/showCornerstonejsDicom/22388', 'wadouri:/dicomfun/showCornerstonejsDicom/22389', 'wadouri:/dic…
這樣的網址
而這些網址也就是 /dicomfun/showCornerstonejsDicom/22388 則直接吐出 dicom 檔案即可
dicom 可以經由 django 產生
例如:
# 建立 DICOM 檔案資料集 ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\x00"*128) # if data_params['Modality'] == 'PT': # ds = pydicom.dcmread('C:/Users/T34808/Downloads/228-PET-43656083.dcm') ds.PatientName = 'NONE' ds.PatientID = '0' ds.Modality = data_params['Modality'] ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID ds.SOPClassUID = file_meta.MediaStorageSOPClassUID …… # 寫入 BytesIO buffer = BytesIO() ds.is_little_endian = True ds.is_implicit_VR = False ds.save_as(buffer) buffer.seek(0) # 回傳檔案 response = FileResponse(buffer, as_attachment=True, filename='generated_dicom.dcm') response['Content-Type'] = 'application/dicom' return response
所以只需要放進去設定好的dicom位置
再用 createAndCacheVolume 新增 Volume
var ct_imageIds = await createImageIdsAndCacheMetaDataZ({ modality : 'MD', imagefs : ct_load_imagefs, }); var ct_volume = await volumeLoader.createAndCacheVolume(ct_volumeId, { imageIds: ct_imageIds, }); ct_volume.load(); view_volumeIds = [{ volumeId: ct_volumeId, }];
然後到 setVolumesForViewports 設定
setVolumesForViewports( renderingEngine, view_volumeIds, [viewportId_axial, viewportId_sagittal, viewportId_coronal], ).then(() => { // 設定初始 }); renderingEngine.renderViewports([viewportId_axial, viewportId_sagittal, viewportId_coronal]);
然後用vite 打包
npx vite build
打包成 bundle.js
設定檔案 vite.config.js 可以參考以下
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { viteCommonjs } from '@originjs/vite-plugin-commonjs'; import { resolve } from 'path'; export default defineConfig({ plugins: [ react(), // for dicom-parser viteCommonjs(), ], // seems like only required in dev mode optimizeDeps: { exclude: ['@cornerstonejs/dicom-image-loader'], include: ['dicom-parser'], }, worker: { format: 'es', }, base: '/static/cornerstonejs/', // Django 靜態資源對應的路徑 build: { rollupOptions: { input: { app: resolve(__dirname, 'main.js'), // 這是你的入口檔案 }, output: { entryFileNames: 'bundle.js', }, }, outDir: '../static/cornerstonejs', // Django static 資料夾的相對路徑 assetsDir: '', // 避免產生 assets 子資料夾 emptyOutDir: true, minify: false, // ❌ 不啟用 minify sourcemap: true, // ✅ 建議同時開啟 sourcemap,方便瀏覽器中 debug }, });
static/cornerstonejs 是對應 django 靜態資源位置
應該
基本上就能夠運作
希望對於有運用 cornerstonejs 的人有幫助喔
感恩
題外話: cornerstonejs 相對冷門,但是你問 GPT如何套用他還是會給你答案,但是通常是 "幻覺",會給一堆不存在的函式,所以問GPT要有技巧,你問 npm , nodejs, javascript 都可以
但是問某個套件的應用方式,函式有哪些,這確實是緣木求魚,所以使用AI也是需要智慧的阿~
留言板
歡迎留下建議與分享!希望一起交流!感恩!