Skip to content

Commit

Permalink
Add Anatomical Plane plugin.
Browse files Browse the repository at this point in the history
  • Loading branch information
birdeggb2777 committed Dec 22, 2024
1 parent 7807ebb commit b14e390
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 7 deletions.
11 changes: 6 additions & 5 deletions bluelight/data/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
{"path":"../scripts/plugin/rtss.js", "name": "rtss", "disableCatch": "true"},
{"path":"../scripts/plugin/seg.js", "name": "seg", "disableCatch": "true"},
{"path":"../scripts/plugin/tag.js", "name": "tag", "disableCatch": "true"},
{"path":"../scripts/plugin/xnat.js", "name": "tag", "disableCatch": "true"},
{"path":"../scripts/plugin/hanging_protocols.js", "name": "tag", "disableCatch": "true"},
{"path":"../scripts/plugin/calibration.js", "name": "tag", "disableCatch": "true"},
{"path":"../scripts/plugin/mtss.js", "name": "tag", "disableCatch": "true"},
{"path":"../scripts/plugin/ecg.js", "name": "tag", "disableCatch": "true"}
{"path":"../scripts/plugin/xnat.js", "name": "xnat", "disableCatch": "true"},
{"path":"../scripts/plugin/hanging_protocols.js", "name": "hanging_protocols", "disableCatch": "true"},
{"path":"../scripts/plugin/calibration.js", "name": "calibration", "disableCatch": "true"},
{"path":"../scripts/plugin/mtss.js", "name": "mtss", "disableCatch": "true"},
{"path":"../scripts/plugin/ecg.js", "name": "ecg", "disableCatch": "true"},
{"path":"../scripts/plugin/anatomical_plane.js", "name": "anatomical_plane", "disableCatch": "true"}
]
}
47 changes: 47 additions & 0 deletions bluelight/scripts/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,50 @@ function refleshViewport() {
displayMark();
}
}

class Matrix4x4 {
constructor() {
this.matrix = [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
}

static applyMatrixToCoordinate(matrix, coordinate) {
const [x, y, z] = coordinate;
const w = 1; // 齊次座標的第四個分量

// 計算新座標
const newX = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z + matrix[0][3] * w;
const newY = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z + matrix[1][3] * w;
const newZ = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z + matrix[2][3] * w;
const newW = matrix[3][0] * x + matrix[3][1] * y + matrix[3][2] * z + matrix[3][3] * w;

// 除以 w 確保齊次坐標轉為常規坐標
return [newX / 1, newY / 1, newZ / 1];
}

static multiplyMatrices(matrixA, matrixB) {
const result = Array.from({ length: 4 }, () => Array(4).fill(0));
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
for (let k = 0; k < 4; k++) {
result[i][j] += matrixA[i][k] * matrixB[k][j];
}
}
}
return result;
}

static multiplyMatrices_invert(matrixA, matrixB) {
const result = Array.from({ length: 4 }, () => Array(4).fill(0));
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
for (let k = 0; k < 4; k++) {
result[i][j] -= matrixA[i][k] * matrixB[k][j];
}
}
}
return result;
}
}
12 changes: 11 additions & 1 deletion bluelight/scripts/dicomloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function getDefaultImageObj(dataSet, type, url) {
imageObj.institutionName = dataSet.string('x00080080');
imageObj.PatientAge = dataSet.string('x00101010');
imageObj.PatientID = dataSet.string('x00100020');

//imageObj.PatientName = dataSet.string('x00100010');
imageObj.PatientName = (new TextDecoder('utf-8')).decode(new Uint8Array(dataSet.byteArray.buffer, dataSet.elements[Tag.PatientName].dataOffset, dataSet.elements[Tag.PatientName].length));

Expand Down Expand Up @@ -89,6 +89,7 @@ function getDefaultImageObj(dataSet, type, url) {
imageObj.imagePosition = dataSet.string(Tag.ImagePositionPatient).split("\\");
for (var i in imageObj.imagePosition) imageObj.imagePosition[i] = parseFloat(imageObj.imagePosition[i]) / (imageObj.rowPixelSpacing ? imageObj.rowPixelSpacing : 1);
}

////////////////////////////

imageObj.intercept = dataSet.intString('x00281052');
Expand All @@ -100,6 +101,15 @@ function getDefaultImageObj(dataSet, type, url) {
imageObj.data = dataSet;
imageObj.url = url;

//////////
imageObj.RCS = new Matrix4x4();
imageObj.RCS.matrix = [
[imageObj.Orientation[0] * parseFloat(imageObj.rowPixelSpacing), imageObj.Orientation[3] * parseFloat(imageObj.rowPixelSpacing), 0, imageObj.imagePosition[0]],
[imageObj.Orientation[1] * parseFloat(imageObj.rowPixelSpacing), imageObj.Orientation[4] * parseFloat(imageObj.rowPixelSpacing), 0, imageObj.imagePosition[1]],
[imageObj.Orientation[2] * parseFloat(imageObj.rowPixelSpacing), imageObj.Orientation[5] * parseFloat(imageObj.rowPixelSpacing), 0, imageObj.imagePosition[2]],
[0, 0, 0, 1]
]
////////////////
if (type == "sop")
imageObj.pixelData = getPixelDataFromDataSet(imageObj, dataSet);

Expand Down
2 changes: 1 addition & 1 deletion bluelight/scripts/mark.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//上一個選擇的標記
var Mark_previous_choose= null;
var Mark_previous_choose = null;

//裝標記的物件
var PatientMark = [];
Expand Down
7 changes: 7 additions & 0 deletions bluelight/scripts/patient.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ class BlueLightImageManager {
}
}

getTargetSopByQRLevelsAndInstanceNumber(QRLevel, TargetInstanceNumber) {
var SopList = this.findSeries(QRLevel.series).Sop;
SopList = SortArrayByElem(SopList, "InstanceNumber");
var index = SopList.findIndex((S) => S.InstanceNumber == TargetInstanceNumber);
return SopList[index];
}

getNextSopByQRLevelsAndInstanceNumber(QRLevel, InstanceNumber, invert = false) {
var SopList = this.findSeries(QRLevel.series).Sop;//this.Study[QRLevel.study].Series[QRLevel.series].Sop;

Expand Down
105 changes: 105 additions & 0 deletions bluelight/scripts/plugin/anatomical_plane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

var openAnatomicalPlane = false;

function loadAnatomicalPlane() {
if (location.search.includes("AnatomicalPlaneTest=true")) {
openAnatomicalPlane = true;
PLUGIN.PushMarkList(drawAnatomicalPlane);

VIEWPORT.drawAnatomicalPlane = function (element, image, viewportNum) {
if (!element.Sop || element.Sop.type == "img") return;

removeAnatomicalPlaneMark();
for (var z = 0; z < Viewport_Total; z++) {
if (z == viewportNum) continue;
if (GetViewport(z).tags.FrameOfReferenceUID != element.tags.FrameOfReferenceUID) continue;
if (!GetViewport(z).content.image.AnatomicalPlane) continue;
if (GetViewport(z).content.image.AnatomicalPlane == element.content.image.AnatomicalPlane) continue;
var MeasureMark = new BlueLightMark();
MeasureMark.hideName = MeasureMark.showName = "AnatomicalPlane";
MeasureMark.setQRLevels(GetViewport(z).QRLevels);
MeasureMark.type = "AnatomicalPlane";
PatientMark.push(MeasureMark);
refreshMarkFromSop(GetViewport(z).sop);
displayMark(z);
}
}
VIEWPORT.loadViewportList.push('drawAnatomicalPlane');
}
}

function removeAnatomicalPlaneMark() {
for (var mark of PatientMark) {
if (mark.type == 'AnatomicalPlane') {
PatientMark.splice(PatientMark.indexOf(mark), 1);
}
}
refreshMarkFromSop(GetViewport().sop);
}

function drawAnatomicalPlane(obj) {
var viewport = obj.viewport;
if (!openAnatomicalPlane) return;

var SelectedViewport = GetViewport();
var element = SelectedViewport;
if (viewport == SelectedViewport) return;

try {
if (!SelectedViewport.content.image.AnatomicalPlane) return;
if (!SelectedViewport.tags.FrameOfReferenceUID) return;

var z = viewport.index;
//for (var z = 0; z < Viewport_Total; z++) {
if (z == viewportNumber) return;
if (GetViewport(z).tags.FrameOfReferenceUID != SelectedViewport.tags.FrameOfReferenceUID) return;
if (!GetViewport(z).content.image.AnatomicalPlane) return;
if (GetViewport(z).content.image.AnatomicalPlane == SelectedViewport.content.image.AnatomicalPlane) return;
var corners = [
[0, 0, GetViewport(z).content.image.RCS.matrix[2][3]], // 左上角
[GetViewport(z).content.image.width, 0, GetViewport(z).content.image.RCS.matrix[2][3]], // 右上角
[0, GetViewport(z).content.image.height, GetViewport(z).content.image.RCS.matrix[2][3]], // 左下角
[GetViewport(z).content.image.width, GetViewport(z).content.image.height, GetViewport(z).content.image.RCS.matrix[2][3]] // 右下角
];
var transCor = corners.map(corner => Matrix4x4.applyMatrixToCoordinate(GetViewport(z).content.image.RCS.matrix, corner));
var transCor = transCor.map(corner => Matrix4x4.applyMatrixToCoordinate(GetViewport().content.image.RCS.matrix, corner));

var x = 0, y = 0, offsetX = 0, offsetY = 0, invertX = 1, invertY = 1;
//GetViewport(z).clearRect();

if (element.content.image.AnatomicalPlane === 'Sagittal' && GetViewport(z).content.image.AnatomicalPlane == 'Axial') {
[x, y] = [0, 2];
[offsetX, offsetY] = [GetViewport(z).content.image.width / 2, 0];
}
else if (element.content.image.AnatomicalPlane === 'Sagittal' && GetViewport(z).content.image.AnatomicalPlane == 'Coronal') {
[x, y] = [0, 1];
[offsetX, offsetY] = [GetViewport(z).content.image.width / 2, GetViewport(z).content.image.height];
}
else if (element.content.image.AnatomicalPlane === 'Axial' && GetViewport(z).content.image.AnatomicalPlane == 'Sagittal') {
[x, y] = [1, 2];
[offsetX, offsetY] = [GetViewport(z).content.image.width, GetViewport(z).content.image.height / 2];
invertY = -1;
}
else if (element.content.image.AnatomicalPlane === 'Axial' && GetViewport(z).content.image.AnatomicalPlane == 'Coronal') {
[x, y] = [0, 2];
[offsetX, offsetY] = [GetViewport(z).content.image.width, GetViewport(z).content.image.height / 2];
invertY = -1;
}
else if (element.content.image.AnatomicalPlane === 'Coronal' && GetViewport(z).content.image.AnatomicalPlane == 'Axial') {
[x, y] = [2, 1];
[offsetX, offsetY] = [0, GetViewport(z).content.image.height / 2];
}
else if (element.content.image.AnatomicalPlane === 'Coronal' && GetViewport(z).content.image.AnatomicalPlane == 'Sagittal') {
[x, y] = [1, 2];
[offsetX, offsetY] = [GetViewport(z).content.image.width / 2, 0];
}
else return;
GetViewport(z).drawLine(GetViewportMark(z).getContext('2d'), GetViewport(z), { x: transCor[0][x] * invertX + offsetX, y: transCor[0][y] * invertY + offsetY }, { x: transCor[1][x] * invertX + offsetX, y: transCor[1][y] * invertY + offsetY }, "#00FF00", 1.0);
GetViewport(z).drawLine(GetViewportMark(z).getContext('2d'), GetViewport(z), { x: transCor[1][x] * invertX + offsetX, y: transCor[1][y] * invertY + offsetY }, { x: transCor[2][x] * invertX + offsetX, y: transCor[2][y] * invertY + offsetY }, "#00FF00", 1.0);
GetViewport(z).drawLine(GetViewportMark(z).getContext('2d'), GetViewport(z), { x: transCor[2][x] * invertX + offsetX, y: transCor[2][y] * invertY + offsetY }, { x: transCor[3][x] * invertX + offsetX, y: transCor[3][y] * invertY + offsetY }, "#00FF00", 1.0);
GetViewport(z).drawLine(GetViewportMark(z).getContext('2d'), GetViewport(z), { x: transCor[3][x] * invertX + offsetX, y: transCor[3][y] * invertY + offsetY }, { x: transCor[0][x] * invertX + offsetX, y: transCor[0][y] * invertY + offsetY }, "#00FF00", 1.0);
//}
} catch (ex) { console.log(ex); }
}

loadAnatomicalPlane();
4 changes: 4 additions & 0 deletions bluelight/scripts/viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ class BlueLightViewPort {
ctx.restore();
}

clearRect(){
this.MarkCanvas.getContext('2d').clearRect(0, 0, this.canvas.width, this.canvas.height);
}

drawRect(ctx, viewport, pointArray, colorGroup = null, alphaGroup = null) {
ctx.save();
setMarkSetting(ctx, viewport);
Expand Down

0 comments on commit b14e390

Please sign in to comment.