Skip to content

Commit

Permalink
fix: recompute ecg time (#387)
Browse files Browse the repository at this point in the history
* fix: recompute ecg time

* Update docs

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
daron1337 and github-actions[bot] authored Sep 12, 2024
1 parent f598512 commit cbf66d0
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 27 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

## Dicom Image Toolkit for CornerstoneJS

### Current version: 2.6.2
### Current version: 2.6.3

### Latest Published Release: 2.6.2
### Latest Published Release: 2.6.3

This library provides common DICOM functionalities to be used in web-applications: it's wrapper that simplifies the use of cornerstone-js environment.

Expand Down
2 changes: 1 addition & 1 deletion dist/imaging/tools/segmentation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export declare function clearSegmentationState(): void;
* Anyway, the activated tool name is returned
* @param {Object} options - An object containing configuration values (eg radius, thresholds, etc...)
*/
export declare function enableBrushTool(viewports: string[], options: BrushProperties): "Brush" | "ThresholdsBrush";
export declare function enableBrushTool(viewports: string[], options: BrushProperties): "ThresholdsBrush" | "Brush";
/**
* Disable brushing
* This function disables both brush tools, if found active on `viewports`
Expand Down
17 changes: 14 additions & 3 deletions dist/imaging/waveforms/ecg.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,23 @@ export declare const renderECG: (data: number[], divId: string, colorMarker: str
*/
export declare const syncECGFrame: (traceData: Partial<Plotly.PlotData>[], seriesId: string, canvasId: string, numberOfFrames: number, divId: string) => void;
/**
* Sync ECG waveform with rendered image on click
* Update the ECG waveform on the plot according to new frame time
* @instance
* @function updateECGTotalTime
* @param {Object} traceData - Plotly trace data
* @param {number} frameId - FrameId of the image
* @param {number} numberOfFrames - Number of frames in the image
* @param {string} frameTime - Time interval of each frame in the image
* @param {string} divId - DivId to render waveform in
*/
export declare const updateECGTotalTime: (traceData: Partial<Plotly.PlotData>[], frameId: number, numberOfFrames: number, frameTime: number, divId: string) => void;
/**
* Update the ECG waveform dot on the plot
* @instance
* @function updateECGFrame
* @function updateECGMarker
* @param {Object} traceData - Plotly trace data
* @param {number} frameId - FrameId of the image
* @param {number} numberOfFrames - Number of frames in the image
* @param {string} divId - DivId to render waveform in
*/
export declare const updateECGFrame: (traceData: Partial<Plotly.PlotData>[], frameId: number, numberOfFrames: number, divId: string) => void;
export declare const updateECGMarker: (traceData: Partial<Plotly.PlotData>[], frameId: number, numberOfFrames: number, divId: string) => void;
4 changes: 2 additions & 2 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { getPerformanceMonitor, activatePerformanceMonitor, deactivatePerformanc
import store from "./imaging/imageStore";
import { parseContours } from "./imaging/imageContours";
import { parseECG } from "./imaging/parsers/ecg";
import { renderECG, unrenderECG, syncECGFrame, updateECGFrame, getDefaultECGLayout } from "./imaging/waveforms/ecg";
import { renderECG, unrenderECG, syncECGFrame, updateECGMarker, updateECGTotalTime, getDefaultECGLayout } from "./imaging/waveforms/ecg";
import { getImagePresets, setImagePreset, setImageCustomPreset } from "./imaging/imagePresets";
import { getNormalOrientation, getMinPixelValue, getMaxPixelValue, getPixelRepresentation, getTypedArrayFromDataType, getSortedStack, randomId, getMeanValue, getReslicedMetadata, getReslicedPixeldata, getDistanceBetweenSlices, getImageMetadata } from "./imaging/imageUtils";
import { buildHeader, getCachedPixelData, buildData, buildDataAsync, importNRRDImage, exportImageToBase64, exportImageToBase64OriginalSizes } from "./imaging/imageIo";
Expand All @@ -37,4 +37,4 @@ import { getColormapsList, applyColorMap, addColorMap, fillPixelData, HSVToRGB }
import { applyDSAShift } from "./imaging/postProcessing/applyDSA";
import { saveAnnotations, loadAnnotations } from "./imaging/tools/io";
import { addMouseKeyHandlers, removeMouseKeyHandlers, toggleMouseToolsListeners } from "./imaging/tools/interaction";
export { VERSION, cornerstone, cornerstoneTools, parseDicom, cornerstoneFileImageLoader, segModule, cornerstoneDICOMImageLoader, checkAndClearMemory, checkMemoryAllocation, getUsedMemory, getAvailableMemory, getPerformanceMonitor, activatePerformanceMonitor, deactivatePerformanceMonitor, store, parseECG, renderECG, unrenderECG, syncECGFrame, updateECGFrame, getDefaultECGLayout, getImagePresets, setImagePreset, setImageCustomPreset, getNormalOrientation, getMinPixelValue, getMaxPixelValue, getPixelRepresentation, getTypedArrayFromDataType, getSortedStack, randomId, getMeanValue, getReslicedMetadata, getReslicedPixeldata, getDistanceBetweenSlices, getImageMetadata, buildHeader, getCachedPixelData, buildData, buildDataAsync, importNRRDImage, exportImageToBase64, exportImageToBase64OriginalSizes, anonymize, customizeByteArray, buildLayer, updateLayer, getActiveLayer, setActiveLayer, initializeImageLoader, initializeWebImageLoader, initializeFileImageLoader, registerNRRDImageLoader, registerResliceLoader, registerMultiFrameImageLoader, registerDsaImageLoader, updateLoadedStack, readFile, readFiles, parseDataSet, clearImageParsing, clearImageCache, loadAndCacheImages, renderFileImage, renderDICOMPDF, renderWebImage, disableViewport, unloadViewport, resizeViewport, renderImage, updateImage, redrawImage, resetViewports, updateViewportData, toggleMouseToolsListeners, storeViewportData, invertImage, flipImageHorizontal, flipImageVertical, rotateImageLeft, rotateImageRight, resliceSeries, getColormapsList, applyColorMap, addColorMap, fillPixelData, HSVToRGB, parseContours, updateLarvitarManager, populateLarvitarManager, populateInstanceGSPSDict, getLarvitarManager, getInstanceGSPSDict, getLarvitarImageTracker, resetLarvitarManager, resetInstanceGSPSDict, removeSeriesFromLarvitarManager, getSeriesDataFromLarvitarManager, getImageFrame, getSopInstanceUIDFromLarvitarManager, buildNrrdImage, getNrrdImageId, loadNrrdImage, getImageIdFromSlice, getSliceNumberFromImageId, getNrrdSerieDimensions, loadReslicedImage, getDicomImageId, cacheImage, cacheImages, loadAndCacheImageStack, loadAndCacheDsaImageStack, loadMultiFrameImage, buildMultiFrameImage, getMultiFrameImageId, clearMultiFrameCache, populateDsaImageIds, getFileManager, resetFileLoader, resetFileManager, populateFileManager, getFileImageId, applyDSAShift, addDiameterTool, addContoursTool, addMaskEditingTool, getCurrentMaskData, addStackStateToElement, addSeedsTool, clearMeasurements, getToolState, clearToolStateByName, updateDiameterTool, addToolStateSingleSlice, clearCornerstoneElements, syncToolStack, updateStackToolState, setSegmentationConfig, csToolsCreateStack, csToolsUpdateImageIds, csToolsUpdateImageIndex, initializeCSTools, setToolsStyle, addDefaultTools, addTool, setToolActive, setToolDisabled, setToolEnabled, setToolPassive, exportAnnotations, DEFAULT_TOOLS, dvTools, getDefaultToolsByType, setDefaultToolsProps, registerExternalTool, saveAnnotations, loadAnnotations, addMouseKeyHandlers, removeMouseKeyHandlers, initSegmentationModule, addSegmentationMask, setActiveLabelmap, setActiveSegment, undoLastStroke, redoLastStroke, setBrushProps, hexToRgb, rgbToHex, clearSegmentationState, deleteMask, enableBrushTool, disableBrushTool, toggleContourMode, toggleVisibility, getActiveLabelmapBuffer, updateTemporalViewportData };
export { VERSION, cornerstone, cornerstoneTools, parseDicom, cornerstoneFileImageLoader, segModule, cornerstoneDICOMImageLoader, checkAndClearMemory, checkMemoryAllocation, getUsedMemory, getAvailableMemory, getPerformanceMonitor, activatePerformanceMonitor, deactivatePerformanceMonitor, store, parseECG, renderECG, unrenderECG, syncECGFrame, updateECGMarker, updateECGTotalTime, getDefaultECGLayout, getImagePresets, setImagePreset, setImageCustomPreset, getNormalOrientation, getMinPixelValue, getMaxPixelValue, getPixelRepresentation, getTypedArrayFromDataType, getSortedStack, randomId, getMeanValue, getReslicedMetadata, getReslicedPixeldata, getDistanceBetweenSlices, getImageMetadata, buildHeader, getCachedPixelData, buildData, buildDataAsync, importNRRDImage, exportImageToBase64, exportImageToBase64OriginalSizes, anonymize, customizeByteArray, buildLayer, updateLayer, getActiveLayer, setActiveLayer, initializeImageLoader, initializeWebImageLoader, initializeFileImageLoader, registerNRRDImageLoader, registerResliceLoader, registerMultiFrameImageLoader, registerDsaImageLoader, updateLoadedStack, readFile, readFiles, parseDataSet, clearImageParsing, clearImageCache, loadAndCacheImages, renderFileImage, renderDICOMPDF, renderWebImage, disableViewport, unloadViewport, resizeViewport, renderImage, updateImage, redrawImage, resetViewports, updateViewportData, toggleMouseToolsListeners, storeViewportData, invertImage, flipImageHorizontal, flipImageVertical, rotateImageLeft, rotateImageRight, resliceSeries, getColormapsList, applyColorMap, addColorMap, fillPixelData, HSVToRGB, parseContours, updateLarvitarManager, populateLarvitarManager, populateInstanceGSPSDict, getLarvitarManager, getInstanceGSPSDict, getLarvitarImageTracker, resetLarvitarManager, resetInstanceGSPSDict, removeSeriesFromLarvitarManager, getSeriesDataFromLarvitarManager, getImageFrame, getSopInstanceUIDFromLarvitarManager, buildNrrdImage, getNrrdImageId, loadNrrdImage, getImageIdFromSlice, getSliceNumberFromImageId, getNrrdSerieDimensions, loadReslicedImage, getDicomImageId, cacheImage, cacheImages, loadAndCacheImageStack, loadAndCacheDsaImageStack, loadMultiFrameImage, buildMultiFrameImage, getMultiFrameImageId, clearMultiFrameCache, populateDsaImageIds, getFileManager, resetFileLoader, resetFileManager, populateFileManager, getFileImageId, applyDSAShift, addDiameterTool, addContoursTool, addMaskEditingTool, getCurrentMaskData, addStackStateToElement, addSeedsTool, clearMeasurements, getToolState, clearToolStateByName, updateDiameterTool, addToolStateSingleSlice, clearCornerstoneElements, syncToolStack, updateStackToolState, setSegmentationConfig, csToolsCreateStack, csToolsUpdateImageIds, csToolsUpdateImageIndex, initializeCSTools, setToolsStyle, addDefaultTools, addTool, setToolActive, setToolDisabled, setToolEnabled, setToolPassive, exportAnnotations, DEFAULT_TOOLS, dvTools, getDefaultToolsByType, setDefaultToolsProps, registerExternalTool, saveAnnotations, loadAnnotations, addMouseKeyHandlers, removeMouseKeyHandlers, initSegmentationModule, addSegmentationMask, setActiveLabelmap, setActiveSegment, undoLastStroke, redoLastStroke, setBrushProps, hexToRgb, rgbToHex, clearSegmentationState, deleteMask, enableBrushTool, disableBrushTool, toggleContourMode, toggleVisibility, getActiveLabelmapBuffer, updateTemporalViewportData };
2 changes: 1 addition & 1 deletion dist/larvitar.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/larvitar.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/documentation/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ <h3> </h3>
<h1 id="larvitar">Larvitar</h1>
<p><a href="https://github.com/dvisionlab/Larvitar"><img src="https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&amp;prefix=%E2%89%A5&amp;suffix=%25&amp;query=$.typeCoverage.atLeast&amp;uri=https%3A%2F%2Fraw.githubusercontent.com%2Fplantain-00%2Ftype-coverage%2Fmaster%2Fpackage.json" alt="type-coverage"></a></p>
<h2 id="dicom-image-toolkit-for-cornerstonejs">Dicom Image Toolkit for CornerstoneJS</h2>
<h3 id="current-version%3A-2.6.2">Current version: 2.6.2</h3>
<h3 id="latest-published-release%3A-2.6.2">Latest Published Release: 2.6.2</h3>
<h3 id="current-version%3A-2.6.3">Current version: 2.6.3</h3>
<h3 id="latest-published-release%3A-2.6.3">Latest Published Release: 2.6.3</h3>
<p>This library provides common DICOM functionalities to be used in web-applications: it's wrapper that simplifies the use of cornerstone-js environment.</p>
<h2 id="features%3A">Features:</h2>
<ul>
Expand Down
55 changes: 49 additions & 6 deletions docs/examples/ecg.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,20 @@
style="position: absolute; top: 40px; color: white"
></p>
<p id="fps" style="position: absolute; top: 60px; color: white"></p>
<p style="position: absolute; top: 80px; color: white">
Press "+/-" to change frame rate
</p>
<p
id="loop-time"
style="position: absolute; top: 80px; color: white"
style="position: absolute; top: 100px; color: white"
></p>
<p
id="last-loop"
style="position: absolute; top: 100px; color: white"
style="position: absolute; top: 120px; color: white"
></p>
<p
id="image-time"
style="position: absolute; top: 120px; color: white"
style="position: absolute; top: 140px; color: white"
></p>
</div>

Expand Down Expand Up @@ -126,7 +129,7 @@
" of " +
numberOfFrames
);
larvitar.updateECGFrame(
larvitar.updateECGMarker(
trace_data,
frameId,
numberOfFrames,
Expand Down Expand Up @@ -231,7 +234,12 @@
"Current Frame: " + parseInt(frameId + 1) + " of " + numberOfFrames
);
if (updateECG) {
larvitar.updateECGFrame(trace_data, frameId, numberOfFrames, "ecg");
larvitar.updateECGMarker(
trace_data,
frameId,
numberOfFrames,
"ecg"
);
}
if (endLoop) {
let tEnd = performance.now();
Expand Down Expand Up @@ -302,6 +310,41 @@
if (e.keyCode == 101) {
updateECG = !updateECG;
}
if (e.keyCode == 43) {
animation = false;
clearInterval(animationId);
frameRate += frameRate * 0.1;
larvitar.updateECGTotalTime(
trace_data,
frameId,
numberOfFrames,
frameRate,
"ecg"
);
$("#frame-rate").html("Frame Rate: " + frameRate.toFixed(3) + "ms");
$("#fps").html("FPS: " + (1000 / frameRate).toFixed(3));
$("#loop-time").html(
"Loop Time: " + (numberOfFrames * frameRate).toFixed(3) + "ms"
);
}
if (e.keyCode == 45) {
animation = false;
clearInterval(animationId);
frameRate -= frameRate * 0.1;
larvitar.updateECGTotalTime(
trace_data,
frameId,
numberOfFrames,
frameRate,
"ecg"
);
$("#frame-rate").html("Frame Rate: " + frameRate.toFixed(3) + "ms");
$("#fps").html("FPS: " + (1000 / frameRate).toFixed(3));
$("#loop-time").html(
"Loop Time: " + (numberOfFrames * frameRate).toFixed(3) + "ms"
);
}

if (e.keyCode == 112) {
animation = !animation;
if (animation) {
Expand All @@ -318,7 +361,7 @@
" of " +
numberOfFrames
);
larvitar.updateECGFrame(
larvitar.updateECGMarker(
trace_data,
frameId,
numberOfFrames,
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/larvitar.js

Large diffs are not rendered by default.

97 changes: 92 additions & 5 deletions imaging/waveforms/ecg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export const renderECG = function (
// fix the range of the x-axis
LAYOUT.xaxis!.range = [0, totalTime];
// fix the grid of x-axis using a line for each frame
LAYOUT.xaxis!.dtick = LAYOUT.xaxis!.dtick = totalTime / (numberOfFrames - 1);
LAYOUT.xaxis!.dtick = totalTime / (numberOfFrames - 1);
Plotly.newPlot(divId, traceData, LAYOUT, {
responsive: true,
displayModeBar: false
Expand Down Expand Up @@ -190,21 +190,73 @@ export const syncECGFrame = function (
const canvasElement: any = document.getElementById(canvasId);
canvasElement.addEventListener("wheel", function (e: WheelEvent) {
const viewport = store.get(["viewports", canvasId]);
updateECGFrame(traceData, viewport.sliceId, numberOfFrames, divId);
updateECGMarker(traceData, viewport.sliceId, numberOfFrames, divId);
updateStackToolState(canvasId, viewport.sliceId);
});
};

/**
* Sync ECG waveform with rendered image on click
* Update the ECG waveform on the plot according to new frame time
* @instance
* @function updateECGFrame
* @function updateECGTotalTime
* @param {Object} traceData - Plotly trace data
* @param {number} frameId - FrameId of the image
* @param {number} numberOfFrames - Number of frames in the image
* @param {string} frameTime - Time interval of each frame in the image
* @param {string} divId - DivId to render waveform in
*/
export const updateECGFrame = function (
export const updateECGTotalTime = function (
traceData: Partial<Plotly.PlotData>[],
frameId: number,
numberOfFrames: number,
frameTime: number,
divId: string
) {
const totalTime = (numberOfFrames - 1) * (frameTime * 1e-3);

// update the x-axis range and dtick
const update: Partial<Plotly.Layout> = {
xaxis: {
range: [0, totalTime],
dtick: totalTime / (numberOfFrames - 1),
rangemode: "tozero",
showgrid: true,
gridcolor: "rgba(238,135,51,0.5)",
tickwidth: 2,
tickcolor: "#f5f5f5",
tickformat: ".2f",
tickfont: {
color: "#f5f5f5"
}
}
};
Plotly.relayout(divId, update);

// @ts-ignore
traceData[0].x = traceData[0].x!.map(
(_, i) => (i * totalTime) / traceData[0].x!.length
);

const dotX: number = (frameId * totalTime) / (numberOfFrames - 1);
const index: number = (traceData[0].x as number[]).findIndex(
(x: number) => x >= dotX
);
const dotY: Datum | Datum[] = traceData[0].y![index];
traceData[1].x = [dotX];
traceData[1].y = Array.isArray(dotY) ? dotY : [dotY];
Plotly.extendTraces(divId, {}, [0]);
};

/**
* Update the ECG waveform dot on the plot
* @instance
* @function updateECGMarker
* @param {Object} traceData - Plotly trace data
* @param {number} frameId - FrameId of the image
* @param {number} numberOfFrames - Number of frames in the image
* @param {string} divId - DivId to render waveform in
*/
export const updateECGMarker = function (
traceData: Partial<Plotly.PlotData>[],
frameId: number,
numberOfFrames: number,
Expand All @@ -220,5 +272,40 @@ export const updateECGFrame = function (
const dotY: Datum | Datum[] = traceData[0].y![index];
traceData[1].x = [dotX];
traceData[1].y = Array.isArray(dotY) ? dotY : [dotY];

// OLD METHOD WORKING FINE BUT SLOW
Plotly.extendTraces(divId, {}, [0]);

// const updatedMarker: Partial<Plotly.PlotData> = {
// x: [dotX], // New X position
// y: Array.isArray(dotY) ? dotY : [dotY], // New Y position
// mode: "markers",
// type: "scattergl",
// marker: {
// size: 12,
// color: "red",
// symbol: "line-ns-open",
// line: {
// width: 3
// }
// }
// };

// // @ts-ignore Plotly.animate is not in the types
// Plotly.animate(
// divId,
// {
// data: [updatedMarker], // Pass the updated marker trace
// traces: [1] // Ensure this is the correct index for your marker trace
// },
// {
// transition: {
// duration: 0 // Duration for the animation
// },
// frame: {
// duration: 0, // Frame duration
// redraw: false // Redraw the plot, keep this false if you don't need a full redraw
// }
// }
// );
};
6 changes: 4 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
renderECG,
unrenderECG,
syncECGFrame,
updateECGFrame,
updateECGMarker,
updateECGTotalTime,
getDefaultECGLayout
} from "./imaging/waveforms/ecg";

Expand Down Expand Up @@ -283,7 +284,8 @@ export {
renderECG,
unrenderECG,
syncECGFrame,
updateECGFrame,
updateECGMarker,
updateECGTotalTime,
getDefaultECGLayout,
// imagePresets
getImagePresets,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"medical",
"cornerstone"
],
"version": "2.6.2",
"version": "2.6.3",
"description": "typescript library for parsing, loading, rendering and interacting with DICOM images",
"repository": {
"url": "https://github.com/dvisionlab/Larvitar.git",
Expand Down

0 comments on commit cbf66d0

Please sign in to comment.