toolManager: fixed annotate tool issues and added Text Marker Tool (importing callback functions from Nucleus) and using Metadata classes for OHIF Viewer
This commit is contained in:
parent
014dc7730f
commit
a8626d02c0
@ -55,9 +55,9 @@ Template.viewer.onCreated(() => {
|
|||||||
ViewerData[contentId].studyInstanceUids = [];
|
ViewerData[contentId].studyInstanceUids = [];
|
||||||
instance.data.studies.forEach(study => {
|
instance.data.studies.forEach(study => {
|
||||||
study.selected = true;
|
study.selected = true;
|
||||||
study.displaySets = OHIF.viewerbase.createStacks(study);
|
// study.displaySets = OHIF.viewerbase.createStacks(study);
|
||||||
// const studyMetadata = new OHIF.metadata.StudyMetadata(study);
|
const studyMetadata = new OHIF.metadata.StudyMetadata(study);
|
||||||
// study.displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
study.displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
||||||
OHIF.viewer.Studies.insert(study);
|
OHIF.viewer.Studies.insert(study);
|
||||||
ViewerData[contentId].studyInstanceUids.push(study.studyInstanceUid);
|
ViewerData[contentId].studyInstanceUids.push(study.studyInstanceUid);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2715,37 +2715,37 @@ var JpegImage = (function jpegImage() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
|
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
|
||||||
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
|
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
|
||||||
// HuffTab[*][*][0-256] is always the only 1st-layer table.
|
// HuffTab[*][*][0-256] is always the only 1st-layer table.
|
||||||
//
|
//
|
||||||
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
|
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
|
||||||
//
|
//
|
||||||
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
||||||
// ):
|
// ):
|
||||||
// return: Huffman Value of table
|
// return: Huffman Value of table
|
||||||
// 0xFF?? if it receives a MARKER
|
// 0xFF?? if it receives a MARKER
|
||||||
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
||||||
// temp temp storage for remainded bits
|
// temp temp storage for remainded bits
|
||||||
// index index to bit of temp
|
// index index to bit of temp
|
||||||
// in FILE pointer
|
// in FILE pointer
|
||||||
// Effect:
|
// Effect:
|
||||||
// temp store new remainded bits
|
// temp store new remainded bits
|
||||||
// index change to new index
|
// index change to new index
|
||||||
// in change to new position
|
// in change to new position
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// Initial by temp=0; index=0;
|
// Initial by temp=0; index=0;
|
||||||
// NOTE: (explain temp and index)
|
// NOTE: (explain temp and index)
|
||||||
// temp: is always in the form at calling time or returning time
|
// temp: is always in the form at calling time or returning time
|
||||||
// | byte 4 | byte 3 | byte 2 | byte 1 |
|
// | byte 4 | byte 3 | byte 2 | byte 1 |
|
||||||
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
|
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
|
||||||
// ^index=3 (from 0 to 15)
|
// ^index=3 (from 0 to 15)
|
||||||
// 321
|
// 321
|
||||||
// NOTE (marker and marker_index):
|
// NOTE (marker and marker_index):
|
||||||
// If get a MARKER from 'in', marker=the low-byte of the MARKER
|
// If get a MARKER from 'in', marker=the low-byte of the MARKER
|
||||||
// and marker_index=9
|
// and marker_index=9
|
||||||
// If marker_index=9 then index is always > 8, or HuffmanValue()
|
// If marker_index=9 then index is always > 8, or HuffmanValue()
|
||||||
// will not be called
|
// will not be called
|
||||||
jpeg.lossless.Decoder.prototype.getHuffmanValue = function (table, temp, index) {
|
jpeg.lossless.Decoder.prototype.getHuffmanValue = function (table, temp, index) {
|
||||||
/*jslint bitwise: true */
|
/*jslint bitwise: true */
|
||||||
|
|
||||||
@ -3302,13 +3302,13 @@ var JpegImage = (function jpegImage() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Build_HuffTab()
|
// Build_HuffTab()
|
||||||
// Parameter: t table ID
|
// Parameter: t table ID
|
||||||
// c table class ( 0 for DC, 1 for AC )
|
// c table class ( 0 for DC, 1 for AC )
|
||||||
// L[i] # of codewords which length is i
|
// L[i] # of codewords which length is i
|
||||||
// V[i][j] Huffman Value (length=i)
|
// V[i][j] Huffman Value (length=i)
|
||||||
// Effect:
|
// Effect:
|
||||||
// build up HuffTab[t][c] using L and V.
|
// build up HuffTab[t][c] using L and V.
|
||||||
jpeg.lossless.HuffmanTable.prototype.buildHuffTable = function(tab, L, V) {
|
jpeg.lossless.HuffmanTable.prototype.buildHuffTable = function(tab, L, V) {
|
||||||
/*jslint bitwise: true */
|
/*jslint bitwise: true */
|
||||||
|
|
||||||
@ -4575,7 +4575,7 @@ var JpegImage = (function jpegImage() {
|
|||||||
// https://groups.google.com/forum/#!searchin/comp.protocols.dicom/Modality$20LUT$20XA/comp.protocols.dicom/UBxhOZ2anJ0/D0R_QP8V2wIJ
|
// https://groups.google.com/forum/#!searchin/comp.protocols.dicom/Modality$20LUT$20XA/comp.protocols.dicom/UBxhOZ2anJ0/D0R_QP8V2wIJ
|
||||||
var sopClassUid = dataSet.string('x00080016');
|
var sopClassUid = dataSet.string('x00080016');
|
||||||
return sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.1' && // XA
|
return sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.1' && // XA
|
||||||
sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.2.1 '; // XRF
|
sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.2.1 '; // XRF
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeGrayscaleImage(imageId, dataSet, frame, sharedCacheKey) {
|
function makeGrayscaleImage(imageId, dataSet, frame, sharedCacheKey) {
|
||||||
|
|||||||
@ -404,7 +404,6 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @TypeSafeStudies
|
// @TypeSafeStudies
|
||||||
|
|
||||||
var alreadyLoaded = OHIF.viewer.Studies.findBy({
|
var alreadyLoaded = OHIF.viewer.Studies.findBy({
|
||||||
studyInstanceUid: priorStudy.studyInstanceUid
|
studyInstanceUid: priorStudy.studyInstanceUid
|
||||||
});
|
});
|
||||||
|
|||||||
@ -30,7 +30,7 @@ OHIF.lesiontracker.openNewTabWithTimepoint = (timepointId, title) => {
|
|||||||
// Update the ViewerData global object
|
// Update the ViewerData global object
|
||||||
ViewerData[contentId] = {
|
ViewerData[contentId] = {
|
||||||
title: title,
|
title: title,
|
||||||
contentid: contentId,
|
contentId: contentId,
|
||||||
studyInstanceUids: data.studyInstanceUids,
|
studyInstanceUids: data.studyInstanceUids,
|
||||||
timepointIds: data.timepointIds,
|
timepointIds: data.timepointIds,
|
||||||
currentTimepointId: timepointId
|
currentTimepointId: timepointId
|
||||||
|
|||||||
@ -42,18 +42,18 @@ function dblClickOnStudy(data) {
|
|||||||
* @param title The title to be used for the tab heading
|
* @param title The title to be used for the tab heading
|
||||||
*/
|
*/
|
||||||
function open(studyInstanceUid, title) {
|
function open(studyInstanceUid, title) {
|
||||||
const contentid = 'viewerTab';
|
const contentId = 'viewerTab';
|
||||||
|
|
||||||
ViewerData = window.ViewerData || ViewerData;
|
ViewerData = window.ViewerData || ViewerData;
|
||||||
|
|
||||||
// Update the ViewerData global object
|
// Update the ViewerData global object
|
||||||
ViewerData[contentid] = {
|
ViewerData[contentId] = {
|
||||||
title: title,
|
title: title,
|
||||||
contentid: contentid,
|
contentId: contentId,
|
||||||
isUnassociatedStudy: true,
|
isUnassociatedStudy: true,
|
||||||
studyInstanceUids: [studyInstanceUid]
|
studyInstanceUids: [studyInstanceUid]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Switch to the new tab
|
// Switch to the new tab
|
||||||
switchToTab(contentid);
|
switchToTab(contentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,17 +135,17 @@ function viewStudies() {
|
|||||||
|
|
||||||
const title = selectedStudies[0].patientName;
|
const title = selectedStudies[0].patientName;
|
||||||
const studyInstanceUids = selectedStudies.map(study => study.studyInstanceUid);
|
const studyInstanceUids = selectedStudies.map(study => study.studyInstanceUid);
|
||||||
const contentid = 'viewerTab';
|
const contentId = 'viewerTab';
|
||||||
|
|
||||||
ViewerData = window.ViewerData || ViewerData;
|
ViewerData = window.ViewerData || ViewerData;
|
||||||
|
|
||||||
// Update the ViewerData global object
|
// Update the ViewerData global object
|
||||||
ViewerData[contentid] = {
|
ViewerData[contentId] = {
|
||||||
title: title,
|
title: title,
|
||||||
contentid: contentid,
|
contentId: contentId,
|
||||||
studyInstanceUids: studyInstanceUids
|
studyInstanceUids: studyInstanceUids
|
||||||
};
|
};
|
||||||
|
|
||||||
// Switch to the new tab
|
// Switch to the new tab
|
||||||
switchToTab(contentid);
|
switchToTab(contentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,6 @@ function findAndRenderDisplaySet(displaySets, viewportIndex, studyInstanceUid, s
|
|||||||
function renderIntoViewport(viewportIndex, studyInstanceUid, seriesInstanceUid, sopInstanceUid, renderedCallback) {
|
function renderIntoViewport(viewportIndex, studyInstanceUid, seriesInstanceUid, sopInstanceUid, renderedCallback) {
|
||||||
|
|
||||||
// @TypeSafeStudies
|
// @TypeSafeStudies
|
||||||
|
|
||||||
// First, check if we already have this study loaded
|
// First, check if we already have this study loaded
|
||||||
const alreadyLoadedStudy = OHIF.viewer.Studies.findBy({ studyInstanceUid });
|
const alreadyLoadedStudy = OHIF.viewer.Studies.findBy({ studyInstanceUid });
|
||||||
|
|
||||||
|
|||||||
@ -11,16 +11,16 @@ openNewTab = function(studyInstanceUid, title) {
|
|||||||
// Generate a unique ID to represent this tab
|
// Generate a unique ID to represent this tab
|
||||||
// We can't just use the Mongo entry ID because
|
// We can't just use the Mongo entry ID because
|
||||||
// then it will change after hot-reloading.
|
// then it will change after hot-reloading.
|
||||||
var contentid = Random.id();
|
var contentId = Random.id();
|
||||||
|
|
||||||
// Update the ViewerData global object
|
// Update the ViewerData global object
|
||||||
ViewerData = window.ViewerData || ViewerData;
|
ViewerData = window.ViewerData || ViewerData;
|
||||||
ViewerData[contentid] = {
|
ViewerData[contentId] = {
|
||||||
title: title,
|
title: title,
|
||||||
contentid: contentid,
|
contentId: contentId,
|
||||||
studyInstanceUids: [studyInstanceUid]
|
studyInstanceUids: [studyInstanceUid]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Switch to the new tab
|
// Switch to the new tab
|
||||||
switchToTab(contentid);
|
switchToTab(contentId);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
import { Blaze } from 'meteor/blaze';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
import { jQuery, $ } from 'meteor/jquery';
|
||||||
|
import { Template } from 'meteor/templating';
|
||||||
|
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,7 +38,7 @@ switchToTab = function(contentId) {
|
|||||||
// If we are switching to the StudyList tab, reset any CSS styles
|
// If we are switching to the StudyList tab, reset any CSS styles
|
||||||
// that have been applied to prevent scrolling in the Viewer.
|
// that have been applied to prevent scrolling in the Viewer.
|
||||||
// Then stop here, since nothing needs to be re-rendered.
|
// Then stop here, since nothing needs to be re-rendered.
|
||||||
var container;
|
let container;
|
||||||
if (contentId === 'studylistTab') {
|
if (contentId === 'studylistTab') {
|
||||||
container = $('.tab-content').find('#studylistTab').get(0);
|
container = $('.tab-content').find('#studylistTab').get(0);
|
||||||
|
|
||||||
@ -41,7 +46,7 @@ switchToTab = function(contentId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var studylistContainer = document.createElement('div');
|
const studylistContainer = document.createElement('div');
|
||||||
studylistContainer.classList.add('studylistContainer');
|
studylistContainer.classList.add('studylistContainer');
|
||||||
container.appendChild(studylistContainer);
|
container.appendChild(studylistContainer);
|
||||||
|
|
||||||
@ -61,14 +66,14 @@ switchToTab = function(contentId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var container = $('.tab-content').find('#viewerTab').get(0);
|
container = $('.tab-content').find('#viewerTab').get(0);
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
// Use Blaze to render the Loading Template into the container
|
// Use Blaze to render the Loading Template into the container
|
||||||
// viewStudiesInTab will clear this container
|
// viewStudiesInTab will clear this container
|
||||||
Blaze.renderWithData(Template.loadingText, {}, container);
|
Blaze.renderWithData(Template.loadingText, {}, container);
|
||||||
|
|
||||||
var studies = ViewerData[contentId].studies;
|
const studies = ViewerData[contentId].studies;
|
||||||
|
|
||||||
if (studies) {
|
if (studies) {
|
||||||
// ViewerData already has the meta data (in cases when studylist is launched externally)
|
// ViewerData already has the meta data (in cases when studylist is launched externally)
|
||||||
@ -76,7 +81,7 @@ switchToTab = function(contentId) {
|
|||||||
} else {
|
} else {
|
||||||
// Use the stored ViewerData global object to retrieve the studyInstanceUid
|
// Use the stored ViewerData global object to retrieve the studyInstanceUid
|
||||||
// related to this tab
|
// related to this tab
|
||||||
var studyInstanceUids = ViewerData[contentId].studyInstanceUids;
|
const studyInstanceUids = ViewerData[contentId].studyInstanceUids;
|
||||||
|
|
||||||
// Attempt to retrieve the meta data (it might be cached)
|
// Attempt to retrieve the meta data (it might be cached)
|
||||||
OHIF.studylist.getStudiesMetadata(studyInstanceUids, function(studies) {
|
OHIF.studylist.getStudiesMetadata(studyInstanceUids, function(studies) {
|
||||||
@ -85,7 +90,7 @@ switchToTab = function(contentId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function viewStudiesInTab(contentId, studies) {
|
const viewStudiesInTab = (contentId, studies) => {
|
||||||
// Tab closed while study data was being retrieved, stop here
|
// Tab closed while study data was being retrieved, stop here
|
||||||
if (!ViewerData[contentId]) {
|
if (!ViewerData[contentId]) {
|
||||||
OHIF.log.warn('Tab closed while study data was being retrieved');
|
OHIF.log.warn('Tab closed while study data was being retrieved');
|
||||||
@ -94,8 +99,11 @@ function viewStudiesInTab(contentId, studies) {
|
|||||||
|
|
||||||
// Once we have the study data, store it in a structure with
|
// Once we have the study data, store it in a structure with
|
||||||
// any other saved data about this tab (e.g. layout structure)
|
// any other saved data about this tab (e.g. layout structure)
|
||||||
var data = jQuery.extend({}, ViewerData[contentId]);
|
const data = jQuery.extend({}, ViewerData[contentId]);
|
||||||
data.studies = studies;
|
data.studies = studies;
|
||||||
|
|
||||||
|
// TODO: check if this is necessary. Since the typo bug
|
||||||
|
// was fixed (it was contentid instead of contentId)
|
||||||
data.contentId = contentId;
|
data.contentId = contentId;
|
||||||
|
|
||||||
if (ViewerData[contentId].studies && ViewerData[contentId].studies.length) {
|
if (ViewerData[contentId].studies && ViewerData[contentId].studies.length) {
|
||||||
@ -103,8 +111,8 @@ function viewStudiesInTab(contentId, studies) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add additional metadata to our study from the studylist
|
// Add additional metadata to our study from the studylist
|
||||||
data.studies.forEach(function(study) {
|
data.studies.forEach(study => {
|
||||||
var studylistStudy = StudyListStudies.findOne({
|
const studylistStudy = StudyListStudies.findOne({
|
||||||
studyInstanceUid: study.studyInstanceUid
|
studyInstanceUid: study.studyInstanceUid
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,10 +125,10 @@ function viewStudiesInTab(contentId, studies) {
|
|||||||
|
|
||||||
// Get tab content container given the contentId string
|
// Get tab content container given the contentId string
|
||||||
// If no such container exists, stop here because something is wrong
|
// If no such container exists, stop here because something is wrong
|
||||||
var container = $('.tab-content').find('#viewerTab').get(0);
|
const container = $('.tab-content').find('#viewerTab').get(0);
|
||||||
|
|
||||||
// Remove the loading text template that is inside the tab container by default
|
// Remove the loading text template that is inside the tab container by default
|
||||||
var viewerContainer = document.createElement('div');
|
const viewerContainer = document.createElement('div');
|
||||||
viewerContainer.classList.add('viewerContainer');
|
viewerContainer.classList.add('viewerContainer');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
container.appendChild(viewerContainer);
|
container.appendChild(viewerContainer);
|
||||||
@ -129,7 +137,7 @@ function viewStudiesInTab(contentId, studies) {
|
|||||||
Blaze.renderWithData(Template.viewer, data, viewerContainer);
|
Blaze.renderWithData(Template.viewer, data, viewerContainer);
|
||||||
|
|
||||||
// Retrieve the DOM element of the viewer
|
// Retrieve the DOM element of the viewer
|
||||||
var imageViewer = $('#viewer');
|
const imageViewer = $('#viewer');
|
||||||
|
|
||||||
// If it is present in the DOM (it should be), then apply
|
// If it is present in the DOM (it should be), then apply
|
||||||
// styles to prevent page scrolling and overscrolling on mobile devices
|
// styles to prevent page scrolling and overscrolling on mobile devices
|
||||||
@ -142,4 +150,4 @@ function viewStudiesInTab(contentId, studies) {
|
|||||||
// Prevent overscroll on mobile devices
|
// Prevent overscroll on mobile devices
|
||||||
document.body.style.position = 'fixed';
|
document.body.style.position = 'fixed';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
@ -127,7 +127,9 @@ function resultDataToStudyMetadata(studyInstanceUid, resultData) {
|
|||||||
derivationDescription: instance[0x00282111],
|
derivationDescription: instance[0x00282111],
|
||||||
lossyImageCompressionRatio: instance[0x00282112],
|
lossyImageCompressionRatio: instance[0x00282112],
|
||||||
lossyImageCompressionMethod: instance[0x00282114],
|
lossyImageCompressionMethod: instance[0x00282114],
|
||||||
spacingBetweenSlices: instance[0x00180088]
|
spacingBetweenSlices: instance[0x00180088],
|
||||||
|
echoNumber: instance[0x00180086],
|
||||||
|
contrastBolusAgent: instance[0x00180010]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Retrieve the actual data over WADO-URI
|
// Retrieve the actual data over WADO-URI
|
||||||
|
|||||||
@ -100,7 +100,9 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
|||||||
numberOfFrames: parseFloat(remoteGetValue(instance['0028,0008'])),
|
numberOfFrames: parseFloat(remoteGetValue(instance['0028,0008'])),
|
||||||
frameIncrementPointer: remoteGetValue(instance['0028,0009']),
|
frameIncrementPointer: remoteGetValue(instance['0028,0009']),
|
||||||
frameTime: parseFloat(remoteGetValue(instance['0018,1063'])),
|
frameTime: parseFloat(remoteGetValue(instance['0018,1063'])),
|
||||||
frameTimeVector: parseFloatArray(remoteGetValue(instance['0018,1065']))
|
frameTimeVector: parseFloatArray(remoteGetValue(instance['0018,1065'])),
|
||||||
|
echoNumber: remoteGetValue(instance['0018,0086']),
|
||||||
|
contrastBolusAgent: remoteGetValue(instance['0018,0010'])
|
||||||
};
|
};
|
||||||
|
|
||||||
var iid = instance['xxxx,0001'].Value;
|
var iid = instance['xxxx,0001'].Value;
|
||||||
|
|||||||
@ -118,6 +118,8 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
|||||||
derivationDescription: DICOMWeb.getString(instance['00282111']),
|
derivationDescription: DICOMWeb.getString(instance['00282111']),
|
||||||
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
|
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
|
||||||
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
|
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
|
||||||
|
echoNumber: DICOMWeb.getString(instance['00180086']),
|
||||||
|
contrastBolusAgent: DICOMWeb.getString(instance['00180010'])
|
||||||
};
|
};
|
||||||
|
|
||||||
if (server.imageRendering === 'wadouri') {
|
if (server.imageRendering === 'wadouri') {
|
||||||
|
|||||||
@ -29,7 +29,6 @@ Template.studySeriesQuickSwitch.onCreated(() => {
|
|||||||
const viewportData = instance.getViewportData(viewportIndex);
|
const viewportData = instance.getViewportData(viewportIndex);
|
||||||
|
|
||||||
// @TypeSafeStudies
|
// @TypeSafeStudies
|
||||||
|
|
||||||
if (viewportData) {
|
if (viewportData) {
|
||||||
// Finds the current study and return it
|
// Finds the current study and return it
|
||||||
instance.study = OHIF.viewer.Studies.findBy({
|
instance.study = OHIF.viewer.Studies.findBy({
|
||||||
|
|||||||
@ -41,7 +41,6 @@ Template.studyTimepoint.events({
|
|||||||
let $studiesTarget = instance.$('.studyTimepoint');
|
let $studiesTarget = instance.$('.studyTimepoint');
|
||||||
|
|
||||||
// @TypeSafeStudies
|
// @TypeSafeStudies
|
||||||
|
|
||||||
if (changed.isQuickSwitch) {
|
if (changed.isQuickSwitch) {
|
||||||
// Changes the current quick switch study
|
// Changes the current quick switch study
|
||||||
const study = OHIF.viewer.Studies.findBy({
|
const study = OHIF.viewer.Studies.findBy({
|
||||||
|
|||||||
@ -139,6 +139,14 @@ Viewerbase.sortingManager = sortingManager;
|
|||||||
import { crosshairsSynchronizers } from './lib/crosshairsSynchronizers';
|
import { crosshairsSynchronizers } from './lib/crosshairsSynchronizers';
|
||||||
Viewerbase.crosshairsSynchronizers = crosshairsSynchronizers;
|
Viewerbase.crosshairsSynchronizers = crosshairsSynchronizers;
|
||||||
|
|
||||||
|
// annotateTextUtils.*
|
||||||
|
import { annotateTextUtils } from './lib/annotateTextUtils';
|
||||||
|
Viewerbase.annotateTextUtils = annotateTextUtils;
|
||||||
|
|
||||||
|
// textMarkerUtils.*
|
||||||
|
import { textMarkerUtils } from './lib/textMarkerUtils';
|
||||||
|
Viewerbase.textMarkerUtils = textMarkerUtils;
|
||||||
|
|
||||||
// createStacks.*
|
// createStacks.*
|
||||||
import { createStacks } from './lib/createStacks';
|
import { createStacks } from './lib/createStacks';
|
||||||
Viewerbase.createStacks = createStacks;
|
Viewerbase.createStacks = createStacks;
|
||||||
|
|||||||
@ -17,41 +17,50 @@ const stackUpdatedCallbacks = [];
|
|||||||
* @return {Array} Array with image IDs
|
* @return {Array} Array with image IDs
|
||||||
*/
|
*/
|
||||||
function createAndAddStack(stackMap, study, displaySet) {
|
function createAndAddStack(stackMap, study, displaySet) {
|
||||||
const numImages = displaySet.images.length;
|
const numImages = displaySet.images.length;
|
||||||
const imageIds = [];
|
const imageIds = [];
|
||||||
let imageId;
|
let imageId;
|
||||||
|
|
||||||
displaySet.images.forEach((image, imageIndex) => {
|
displaySet.images.forEach((image, imageIndex) => {
|
||||||
const metaData = {
|
const metaData = {
|
||||||
instance: image,
|
instance: image,
|
||||||
series: displaySet, // TODO: Check this
|
series: displaySet, // TODO: Check this
|
||||||
study: study,
|
study: study,
|
||||||
numImages: numImages,
|
numImages: numImages,
|
||||||
imageIndex: imageIndex + 1
|
imageIndex: imageIndex + 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const numberOfFrames = image.numberOfFrames;
|
const numberOfFrames = image.numberOfFrames;
|
||||||
if (numberOfFrames > 1) {
|
if (numberOfFrames > 1) {
|
||||||
OHIF.log.info('Multiframe image detected');
|
OHIF.log.info('Multiframe image detected');
|
||||||
for (let i = 0; i < numberOfFrames; i++) {
|
for (let i = 0; i < numberOfFrames; i++) {
|
||||||
metaData.frame = i;
|
metaData.frame = i;
|
||||||
imageId = getImageId(image, i);
|
imageId = getImageId(image, i);
|
||||||
imageIds.push(imageId);
|
imageIds.push(imageId);
|
||||||
addMetaData(imageId, metaData);
|
addMetaData(imageId, metaData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
imageId = getImageId(image);
|
imageId = getImageId(image);
|
||||||
imageIds.push(imageId);
|
imageIds.push(imageId);
|
||||||
addMetaData(imageId, metaData);
|
addMetaData(imageId, metaData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return imageIds;
|
const stack = {
|
||||||
|
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
||||||
|
imageIds: imageIds,
|
||||||
|
frameRate: displaySet.frameRate,
|
||||||
|
isClip: displaySet.isClip
|
||||||
|
};
|
||||||
|
|
||||||
|
stackMap[displaySet.displaySetInstanceUid] = stack;
|
||||||
|
|
||||||
|
return imageIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration = {
|
configuration = {
|
||||||
createAndAddStack: createAndAddStack
|
createAndAddStack: createAndAddStack
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,64 +70,64 @@ configuration = {
|
|||||||
* clearStacks and makeAndAddStack should not used outside of loadStudy and removeStudies.
|
* clearStacks and makeAndAddStack should not used outside of loadStudy and removeStudies.
|
||||||
*/
|
*/
|
||||||
const StackManager = {
|
const StackManager = {
|
||||||
/**
|
/**
|
||||||
* Removes all current stacks
|
* Removes all current stacks
|
||||||
*/
|
*/
|
||||||
clearStacks() {
|
clearStacks() {
|
||||||
stackMap = {};
|
stackMap = {};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Create a stack from an image set, as well as add in the metadata on a per image bases.
|
* Create a stack from an image set, as well as add in the metadata on a per image bases.
|
||||||
* @param study The study who's metadata will be added
|
* @param study The study who's metadata will be added
|
||||||
* @param displaySet The set of images to make the stack from
|
* @param displaySet The set of images to make the stack from
|
||||||
* @return {Array} Array with image IDs
|
* @return {Array} Array with image IDs
|
||||||
*/
|
*/
|
||||||
makeAndAddStack(study, displaySet) {
|
makeAndAddStack(study, displaySet) {
|
||||||
return configuration.createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks);
|
return configuration.createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Find a stack from the currently created stacks.
|
* Find a stack from the currently created stacks.
|
||||||
* @param displaySetInstanceUid The UID of the stack to find.
|
* @param displaySetInstanceUid The UID of the stack to find.
|
||||||
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
||||||
*/
|
*/
|
||||||
findStack(displaySetInstanceUid) {
|
findStack(displaySetInstanceUid) {
|
||||||
return stackMap[displaySetInstanceUid];
|
return stackMap[displaySetInstanceUid];
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Gets the underlying map of displaySetInstanceUid to stack object.
|
* Gets the underlying map of displaySetInstanceUid to stack object.
|
||||||
* WARNING: Do not change this object. It directly affects the manager.
|
* WARNING: Do not change this object. It directly affects the manager.
|
||||||
* @returns {{}} map of displaySetInstanceUid -> stack.
|
* @returns {{}} map of displaySetInstanceUid -> stack.
|
||||||
*/
|
*/
|
||||||
getAllStacks() {
|
getAllStacks() {
|
||||||
return stackMap;
|
return stackMap;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Adds in a callback to be called on a stack being added / updated.
|
* Adds in a callback to be called on a stack being added / updated.
|
||||||
* @param callback must accept at minimum one argument,
|
* @param callback must accept at minimum one argument,
|
||||||
* which is the stack that was added / updated.
|
* which is the stack that was added / updated.
|
||||||
*/
|
*/
|
||||||
addStackUpdatedCallback(callback) {
|
addStackUpdatedCallback(callback) {
|
||||||
if (typeof callback !== 'function') {
|
if (typeof callback !== 'function') {
|
||||||
throw new OHIFError('callback must be provided as a function');
|
throw new OHIFError('callback must be provided as a function');
|
||||||
|
}
|
||||||
|
stackUpdatedCallbacks.push(callback);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Return configuration
|
||||||
|
*/
|
||||||
|
getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set configuration, in order to provide compatibility
|
||||||
|
* with other systems by overriding this functions
|
||||||
|
* @param {Object} config object with functions to be overrided
|
||||||
|
*
|
||||||
|
* For now, only makeAndAddStack can be overrided
|
||||||
|
*/
|
||||||
|
setConfiguration(config) {
|
||||||
|
configuration = config;
|
||||||
}
|
}
|
||||||
stackUpdatedCallbacks.push(callback);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Return configuration
|
|
||||||
*/
|
|
||||||
getConfiguration() {
|
|
||||||
return configuration;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Set configuration, in order to provide compatibility
|
|
||||||
* with other systems by overriding this functions
|
|
||||||
* @param {Object} config object with functions to be overrided
|
|
||||||
*
|
|
||||||
* For now, only makeAndAddStack can be overrided
|
|
||||||
*/
|
|
||||||
setConfiguration(config) {
|
|
||||||
configuration = config;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { StackManager };
|
export { StackManager };
|
||||||
|
|||||||
122
Packages/ohif-viewerbase/client/lib/annotateTextUtils.js
Normal file
122
Packages/ohif-viewerbase/client/lib/annotateTextUtils.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { viewportUtils } from './viewportUtils';
|
||||||
|
|
||||||
|
const getTextCallback = doneChangingTextCallback => {
|
||||||
|
// This handles the text entry for the annotation tool
|
||||||
|
const keyPressHandler = e => {
|
||||||
|
// If Enter or Esc are pressed, close the dialog
|
||||||
|
if (e.which === 13 || e.which === 27) {
|
||||||
|
closeHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
dialog.get(0).close();
|
||||||
|
doneChangingTextCallback(getTextInput.val());
|
||||||
|
// Reset the text value
|
||||||
|
getTextInput.val('');
|
||||||
|
|
||||||
|
// Reset the focus to the active viewport element
|
||||||
|
// This makes the mobile Safari keyboard close
|
||||||
|
const element = viewportUtils.getActiveViewportElement();
|
||||||
|
$(element).focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialog = $('#annotationDialog');
|
||||||
|
if (dialog.get(0).open === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTextInput = $('.annotationTextInput');
|
||||||
|
|
||||||
|
// Focus on the text input to open the Safari keyboard
|
||||||
|
getTextInput.focus();
|
||||||
|
|
||||||
|
dialog.get(0).showModal();
|
||||||
|
|
||||||
|
const confirm = dialog.find('.annotationDialogConfirm');
|
||||||
|
confirm.off('click');
|
||||||
|
confirm.on('click', () => {
|
||||||
|
closeHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use keydown since keypress doesn't handle ESC in Chrome
|
||||||
|
dialog.off('keydown');
|
||||||
|
dialog.on('keydown', keyPressHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTextCallback = (data, eventData, doneChangingTextCallback) => {
|
||||||
|
const dialog = $('#relabelAnnotationDialog');
|
||||||
|
if (dialog.get(0).open === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Viewerbase.helpers.isTouchDevice()) {
|
||||||
|
// Center the dialog on screen on touch devices
|
||||||
|
dialog.css({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
margin: 'auto'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Place the dialog above the tool that is being relabelled
|
||||||
|
dialog.css({
|
||||||
|
top: eventData.currentPoints.page.y - dialog.outerHeight() - 20,
|
||||||
|
left: eventData.currentPoints.page.x - dialog.outerWidth() / 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTextInput = dialog.find('.annotationTextInput');
|
||||||
|
const confirm = dialog.find('.relabelConfirm');
|
||||||
|
const remove = dialog.find('.relabelRemove');
|
||||||
|
|
||||||
|
getTextInput.val(data.text);
|
||||||
|
|
||||||
|
// Focus on the text input to open the Safari keyboard
|
||||||
|
getTextInput.focus();
|
||||||
|
|
||||||
|
dialog.get(0).showModal();
|
||||||
|
|
||||||
|
confirm.off('click');
|
||||||
|
confirm.on('click', () => {
|
||||||
|
dialog.get(0).close();
|
||||||
|
doneChangingTextCallback(data, getTextInput.val());
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the remove button is clicked, delete this marker
|
||||||
|
remove.off('click');
|
||||||
|
remove.on('click', () => {
|
||||||
|
dialog.get(0).close();
|
||||||
|
doneChangingTextCallback(data, undefined, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.off('keydown');
|
||||||
|
dialog.on('keydown', keyPressHandler);
|
||||||
|
|
||||||
|
const keyPressHandler = e => {
|
||||||
|
// If Enter is pressed, close the dialog
|
||||||
|
if (e.which === 13) {
|
||||||
|
closeHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
dialog.get(0).close();
|
||||||
|
doneChangingTextCallback(data, getTextInput.val());
|
||||||
|
// Reset the text value
|
||||||
|
getTextInput.val('');
|
||||||
|
|
||||||
|
// Reset the focus to the active viewport element
|
||||||
|
// This makes the mobile Safari keyboard close
|
||||||
|
const element = viewportUtils.getActiveViewportElement();
|
||||||
|
$(element).focus();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const annotateTextUtils = {
|
||||||
|
getTextCallback,
|
||||||
|
changeTextCallback
|
||||||
|
};
|
||||||
|
|
||||||
|
export { annotateTextUtils };
|
||||||
@ -88,13 +88,18 @@ export class LayoutManager {
|
|||||||
// Generate the additional data based on the appendix
|
// Generate the additional data based on the appendix
|
||||||
const additionalData = [];
|
const additionalData = [];
|
||||||
appendix.forEach((displaySet, index) => {
|
appendix.forEach((displaySet, index) => {
|
||||||
additionalData.push({
|
const { images, studyInstanceUid, seriesInstanceUid, displaySetInstanceUid } = displaySet;
|
||||||
viewportIndex: currentViewportIndex + index,
|
const sopInstanceUid = images[0] && images[0].getSOPInstanceUID ? images[0].getSOPInstanceUID() : '';
|
||||||
studyInstanceUid: displaySet.studyInstanceUid,
|
const viewportIndex = currentViewportIndex + index;
|
||||||
seriesInstanceUid: displaySet.seriesInstanceUid,
|
const data = {
|
||||||
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
viewportIndex,
|
||||||
sopInstanceUid: displaySet.images[0].sopInstanceUid
|
studyInstanceUid,
|
||||||
});
|
seriesInstanceUid,
|
||||||
|
displaySetInstanceUid,
|
||||||
|
sopInstanceUid
|
||||||
|
};
|
||||||
|
|
||||||
|
additionalData.push(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Append the additional data with the viewport data
|
// Append the additional data with the viewport data
|
||||||
|
|||||||
@ -1,40 +1,86 @@
|
|||||||
import { ImageSet } from './classes/ImageSet';
|
import { ImageSet } from './classes/ImageSet';
|
||||||
import { isImage } from './isImage';
|
import { isImage } from './isImage';
|
||||||
|
|
||||||
|
const isMultiFrame = instance => {
|
||||||
|
return instance.getRawValue('NumberOfFrames') > 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeDisplaySet = (series, instances) => {
|
||||||
|
const instance = instances[0];
|
||||||
|
|
||||||
|
const imageSet = new ImageSet(instances);
|
||||||
|
const seriesData = series.getData();
|
||||||
|
|
||||||
|
// set appropriate attributes to image set...
|
||||||
|
imageSet.setAttributes({
|
||||||
|
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
||||||
|
seriesInstanceUid: seriesData.seriesInstanceUid,
|
||||||
|
seriesNumber: seriesData.seriesNumber,
|
||||||
|
seriesDescription: seriesData.seriesDescription,
|
||||||
|
numImageFrames: instances.length,
|
||||||
|
frameRate: instance.getRawValue('x00181063'),
|
||||||
|
modality: seriesData.modality,
|
||||||
|
isMultiFrame: isMultiFrame(instance)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort the images in this series
|
||||||
|
imageSet.sortBy((a, b) => {
|
||||||
|
// Sort by instance Number
|
||||||
|
const aInstanceNumber = a.getRawValue('x00200013');
|
||||||
|
const bInstanceNumber = b.getRawValue('x00200013');
|
||||||
|
if (a.instanceNumber && b.instanceNumber &&
|
||||||
|
a.instanceNumber !== b.instanceNumber) {
|
||||||
|
return a.instanceNumber - b.instanceNumber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Include the first image instance number (after sorted)
|
||||||
|
imageSet.setAttribute('instanceNumber', imageSet.getImage(0).getRawValue('x00200013'));
|
||||||
|
|
||||||
|
return imageSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSingleImageModality = modality => {
|
||||||
|
return (modality === 'CR' ||
|
||||||
|
modality === 'MG' ||
|
||||||
|
modality === 'DX');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a set of series to be placed in the Study Browser
|
* Creates a set of series to be placed in the Study Metadata
|
||||||
* The series that appear in the Study Browser must represent
|
* The series that appear in the Study Metadata must represent
|
||||||
* imaging modalities.
|
* imaging modalities.
|
||||||
*
|
*
|
||||||
* Furthermore, for drag/drop functionality,
|
* Furthermore, for drag/drop functionality,
|
||||||
* it is easiest if the stack objects also contain information about
|
* it is easiest if the stack objects also contain information about
|
||||||
* which study they are linked to.
|
* which study they are linked to.
|
||||||
*
|
*
|
||||||
* @param study The study instance to be used
|
* @param study The study instance metadata to be used
|
||||||
* @returns {Array} An array of series to be placed in the Study Browser
|
* @returns {Array} An array of series to be placed in the Study Metadata
|
||||||
*/
|
*/
|
||||||
function createStacks(study) {
|
const createStacks = study => {
|
||||||
// Define an empty array of display sets
|
// Define an empty array of display sets
|
||||||
var displaySets = [];
|
const displaySets = [];
|
||||||
|
|
||||||
if (!study || !study.seriesList) {
|
if (!study || !study.getSeriesCount()) {
|
||||||
return displaySets;
|
return displaySets;
|
||||||
}
|
}
|
||||||
|
|
||||||
study.seriesList.forEach(series => {
|
// Loop through the series (SeriesMetadata)
|
||||||
|
study.forEachSeries(series => {
|
||||||
// If the series has no instances, skip it
|
// If the series has no instances, skip it
|
||||||
if (!series.instances) {
|
if (!series.getInstanceCount()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search through the instances of this series
|
// Search through the instances (InstanceMedatada object) of this series
|
||||||
// Split Multi-frame instances and Single-image modalities
|
// Split Multi-frame instances and Single-image modalities
|
||||||
// into their own specific display sets. Place the rest of each
|
// into their own specific display sets. Place the rest of each
|
||||||
// series into another display set.
|
// series into another display set.
|
||||||
let stackableInstances = [];
|
const stackableInstances = [];
|
||||||
series.instances.forEach(instance => {
|
series.forEachInstance(instance => {
|
||||||
// All imaging modalities must have a valid value for sopClassUid or rows
|
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
||||||
if (!isImage(instance.sopClassUid) && !instance.rows) {
|
if (!isImage(instance.getRawValue('x00080016')) && !instance.getRawValue('x00280010')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +109,8 @@ function createStacks(study) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (stackableInstances.length) {
|
if (stackableInstances.length) {
|
||||||
let displaySet = makeDisplaySet(series, stackableInstances);
|
const displaySet = makeDisplaySet(series, stackableInstances);
|
||||||
displaySet.setAttribute('studyInstanceUid', study.studyInstanceUid);
|
displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID());
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -72,47 +118,6 @@ function createStacks(study) {
|
|||||||
return displaySets;
|
return displaySets;
|
||||||
};
|
};
|
||||||
|
|
||||||
function makeDisplaySet(series, instances) {
|
|
||||||
const instance = instances[0];
|
|
||||||
|
|
||||||
const imageSet = new ImageSet(instances);
|
|
||||||
|
|
||||||
// set appropriate attributes to image set...
|
|
||||||
imageSet.setAttributes({
|
|
||||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
|
||||||
seriesInstanceUid: series.seriesInstanceUid,
|
|
||||||
seriesNumber: series.seriesNumber,
|
|
||||||
seriesDescription: series.seriesDescription,
|
|
||||||
numImageFrames: instances.length,
|
|
||||||
frameRate: instance.frameTime,
|
|
||||||
modality: series.modality,
|
|
||||||
isMultiFrame: isMultiFrame(instance)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort the images in this series
|
|
||||||
imageSet.sortBy(function(a, b) {
|
|
||||||
if (a.instanceNumber && b.instanceNumber &&
|
|
||||||
a.instanceNumber !== b.instanceNumber) {
|
|
||||||
return a.instanceNumber - b.instanceNumber;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Include the first image instance number (after sorted)
|
|
||||||
imageSet.setAttribute('instanceNumber', imageSet.getImage(0).instanceNumber);
|
|
||||||
|
|
||||||
return imageSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSingleImageModality(modality) {
|
|
||||||
return (modality === 'CR' ||
|
|
||||||
modality === 'MG' ||
|
|
||||||
modality === 'DX');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMultiFrame(instance) {
|
|
||||||
return instance.numberOfFrames > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose "createStacks"...
|
* Expose "createStacks"...
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -7,7 +7,13 @@ import { getWADORSImageId } from './getWADORSImageId';
|
|||||||
* @param instance
|
* @param instance
|
||||||
* @returns {string} The imageId to be used by Cornerstone
|
* @returns {string} The imageId to be used by Cornerstone
|
||||||
*/
|
*/
|
||||||
export function getImageId(instance, frame) {
|
export function getImageId(instanceMetadata, frame) {
|
||||||
|
if (!instanceMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = instanceMetadata.getData();
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,28 +4,30 @@ import { OHIF } from 'meteor/ohif:core';
|
|||||||
/**
|
/**
|
||||||
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
|
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
|
||||||
*
|
*
|
||||||
* @param instance
|
* @param {object} instance metadata object (InstanceMetadata)
|
||||||
* @returns {string} The imageId to be used by Cornerstone
|
* @returns {string} The imageId to be used by Cornerstone
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function getWADORSImageId(instance) {
|
export function getWADORSImageId(instanceMetada) {
|
||||||
var columnPixelSpacing = 1.0;
|
const instance = instanceMetada.getData();
|
||||||
var rowPixelSpacing = 1.0;
|
let columnPixelSpacing = 1.0;
|
||||||
|
let rowPixelSpacing = 1.0;
|
||||||
|
|
||||||
if (instance.pixelSpacing) {
|
if (instance.pixelSpacing) {
|
||||||
var split = instance.pixelSpacing.split('\\');
|
const split = instance.pixelSpacing.split('\\');
|
||||||
rowPixelSpacing = parseFloat(split[0]);
|
rowPixelSpacing = parseFloat(split[0]);
|
||||||
columnPixelSpacing = parseFloat(split[1]);
|
columnPixelSpacing = parseFloat(split[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var windowWidth;
|
let windowWidth;
|
||||||
var windowCenter;
|
let windowCenter;
|
||||||
|
|
||||||
if (instance.windowWidth && instance.windowCenter) {
|
if (instance.windowWidth && instance.windowCenter) {
|
||||||
windowWidth = parseFloat(instance.windowWidth.split('\\')[0]);
|
windowWidth = parseFloat(instance.windowWidth.split('\\')[0]);
|
||||||
windowCenter = parseFloat(instance.windowCenter.split('\\')[0]);
|
windowCenter = parseFloat(instance.windowCenter.split('\\')[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var image = {
|
const image = {
|
||||||
uri: Meteor.absoluteUrl(instance.wadorsuri),
|
uri: Meteor.absoluteUrl(instance.wadorsuri),
|
||||||
//imageId : '',
|
//imageId : '',
|
||||||
//minPixelValue : 0,
|
//minPixelValue : 0,
|
||||||
@ -56,7 +58,7 @@ export function getWADORSImageId(instance) {
|
|||||||
instance: instance
|
instance: instance
|
||||||
};
|
};
|
||||||
|
|
||||||
var imageId = cornerstoneWADOImageLoader.imageManager.add(image);
|
const imageId = cornerstoneWADOImageLoader.imageManager.add(image);
|
||||||
|
|
||||||
OHIF.log.info('WADO-RS ImageID: ' + imageId);
|
OHIF.log.info('WADO-RS ImageID: ' + imageId);
|
||||||
return imageId;
|
return imageId;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { getSortedData } from './getSortedData';
|
import { createStacks } from './createStacks';
|
||||||
|
|
||||||
const getDisplaySets = (studyMetadata, seriesNumber, iteratorFunction) => {
|
const getDisplaySets = (studyMetadata, seriesNumber, iteratorFunction) => {
|
||||||
const iteratorFn = typeof iteratorFunction !== 'function' ? getSortedData : iteratorFunction;
|
const iteratorFn = typeof iteratorFunction !== 'function' ? createStacks : iteratorFunction;
|
||||||
|
|
||||||
return iteratorFn(studyMetadata, seriesNumber);
|
return iteratorFn(studyMetadata, seriesNumber);
|
||||||
};
|
};
|
||||||
|
|||||||
87
Packages/ohif-viewerbase/client/lib/textMarkerUtils.js
Normal file
87
Packages/ohif-viewerbase/client/lib/textMarkerUtils.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Blaze } from 'meteor/blaze';
|
||||||
|
|
||||||
|
import { toolManager } from './toolManager';
|
||||||
|
import { viewportUtils } from './viewportUtils';
|
||||||
|
|
||||||
|
const changeTextCallback = (data, eventData, doneChangingTextCallback) => {
|
||||||
|
// This handles the double-click/long-press event on Spine text marker labels
|
||||||
|
const keyPressHandler = e => {
|
||||||
|
// If Enter or Esc are pressed, close the dialog
|
||||||
|
if (e.which === 13 || e.which === 27) {
|
||||||
|
closeHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deactivate textMarker tool after editing a spine label & if spine is not active tool
|
||||||
|
const deactivateAfterEdit = () => {
|
||||||
|
if (toolManager.getActiveTool() !== 'spine') {
|
||||||
|
const element = viewportUtils.getActiveViewportElement();
|
||||||
|
cornerstoneTools.textMarker.deactivate(element, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
dialog.get(0).close();
|
||||||
|
doneChangingTextCallback(data, select.val());
|
||||||
|
deactivateAfterEdit();
|
||||||
|
// Reset the focus to the active viewport element
|
||||||
|
// This makes the mobile Safari keyboard close
|
||||||
|
const element = viewportUtils.getActiveViewportElement();
|
||||||
|
$(element).focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialog = $('#textMarkerRelabelDialog');
|
||||||
|
|
||||||
|
// Is necessary to use Blaze object to not create
|
||||||
|
// circular depencency with helper object (./helpers)
|
||||||
|
if (Blaze._globalHelpers.isTouchDevice()) {
|
||||||
|
// Center the dialog on screen on touch devices
|
||||||
|
dialog.css({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
margin: 'auto'
|
||||||
|
});
|
||||||
|
dialog.find('.dialog.arrow').hide();
|
||||||
|
} else {
|
||||||
|
// Place the dialog above the tool that is being relabelled
|
||||||
|
// TODO = Switch this to the tool coordinates, but put back into
|
||||||
|
// page coordinates.
|
||||||
|
dialog.css({
|
||||||
|
top: eventData.currentPoints.page.y - dialog.outerHeight() - 20,
|
||||||
|
left: eventData.currentPoints.page.x - dialog.outerWidth() / 2
|
||||||
|
});
|
||||||
|
dialog.find('.dialog.arrow').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
const select = dialog.find('.relabelSelect');
|
||||||
|
const confirm = dialog.find('.relabelConfirm');
|
||||||
|
const remove = dialog.find('.relabelRemove');
|
||||||
|
|
||||||
|
// If the remove button is clicked, delete this marker
|
||||||
|
remove.off('click');
|
||||||
|
remove.on('click', () => {
|
||||||
|
dialog.get(0).close();
|
||||||
|
doneChangingTextCallback(data, undefined, true);
|
||||||
|
deactivateAfterEdit();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.get(0).showModal();
|
||||||
|
$('.relabelSelect').val(data.text).trigger('change'); //Update selector to the current
|
||||||
|
|
||||||
|
confirm.off('click');
|
||||||
|
confirm.on('click', () => {
|
||||||
|
closeHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use keydown since keypress doesn't handle ESC in Chrome
|
||||||
|
dialog.off('keydown');
|
||||||
|
dialog.on('keydown', keyPressHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
const textMarkerUtils = {
|
||||||
|
changeTextCallback
|
||||||
|
};
|
||||||
|
|
||||||
|
export { textMarkerUtils };
|
||||||
@ -4,6 +4,8 @@ import { OHIF } from 'meteor/ohif:core';
|
|||||||
import { getFrameOfReferenceUID } from './getFrameOfReferenceUID';
|
import { getFrameOfReferenceUID } from './getFrameOfReferenceUID';
|
||||||
import { updateCrosshairsSynchronizer } from './updateCrosshairsSynchronizer';
|
import { updateCrosshairsSynchronizer } from './updateCrosshairsSynchronizer';
|
||||||
import { crosshairsSynchronizers } from './crosshairsSynchronizers';
|
import { crosshairsSynchronizers } from './crosshairsSynchronizers';
|
||||||
|
import { annotateTextUtils } from './annotateTextUtils';
|
||||||
|
import { textMarkerUtils } from './textMarkerUtils';
|
||||||
|
|
||||||
let activeTool = 'wwwc';
|
let activeTool = 'wwwc';
|
||||||
let defaultTool = 'wwwc';
|
let defaultTool = 'wwwc';
|
||||||
@ -167,16 +169,34 @@ export const toolManager = {
|
|||||||
...shadowConfig
|
...shadowConfig
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the configuration values for the text annotation (Arrow) tool
|
// Set the configuration values for the Text Marker (Spine Labelling) tool
|
||||||
|
const startFrom = $('#startFrom');
|
||||||
|
const ascending = $('#ascending');
|
||||||
|
const textMarkerConfig = {
|
||||||
|
markers: [ 'L5', 'L4', 'L3', 'L2', 'L1', // Lumbar spine
|
||||||
|
'T12', 'T11', 'T10', 'T9', 'T8', 'T7', // Thoracic spine
|
||||||
|
'T6', 'T5', 'T4', 'T3', 'T2', 'T1',
|
||||||
|
'C7', 'C6', 'C5', 'C4', 'C3', 'C2', 'C1', // Cervical spine
|
||||||
|
],
|
||||||
|
current: startFrom.val(),
|
||||||
|
ascending: ascending.is(':checked'),
|
||||||
|
loop: true,
|
||||||
|
changeTextCallback: textMarkerUtils.changeTextCallback,
|
||||||
|
shadow: shadowConfig.shadow,
|
||||||
|
shadowColor: shadowConfig.shadowColor,
|
||||||
|
shadowOffsetX: shadowConfig.shadowOffsetX,
|
||||||
|
shadowOffsetY: shadowConfig.shadowOffsetY
|
||||||
|
};
|
||||||
|
cornerstoneTools.textMarker.setConfiguration(textMarkerConfig);
|
||||||
|
|
||||||
// @TODO: Fix this, needs to import them from somewhere
|
// Set the configuration values for the text annotation (Arrow) tool
|
||||||
/*const annotateConfig = {
|
const annotateConfig = {
|
||||||
getTextCallback: getAnnotationTextCallback,
|
getTextCallback: annotateTextUtils.getTextCallback,
|
||||||
changeTextCallback: changeAnnotationTextCallback,
|
changeTextCallback: annotateTextUtils.changeTextCallback,
|
||||||
drawHandles: false,
|
drawHandles: false,
|
||||||
arrowFirst: true
|
arrowFirst: true
|
||||||
};
|
};
|
||||||
arrowAnnotate.setConfiguration(annotateConfig);*/
|
arrowAnnotate.setConfiguration(annotateConfig);
|
||||||
|
|
||||||
const zoomConfig = {
|
const zoomConfig = {
|
||||||
minScale: 0.05,
|
minScale: 0.05,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user