feat(debug): Add timing information about time to first image/all images, and query time (#3681)
This commit is contained in:
parent
1b207f14d2
commit
108383b9ef
@ -28,6 +28,7 @@ import interleaveTopToBottom from './utils/interleaveTopToBottom';
|
|||||||
import initContextMenu from './initContextMenu';
|
import initContextMenu from './initContextMenu';
|
||||||
import initDoubleClick from './initDoubleClick';
|
import initDoubleClick from './initDoubleClick';
|
||||||
import { CornerstoneServices } from './types';
|
import { CornerstoneServices } from './types';
|
||||||
|
import initViewTiming from './utils/initViewTiming';
|
||||||
|
|
||||||
// TODO: Cypress tests are currently grabbing this from the window?
|
// TODO: Cypress tests are currently grabbing this from the window?
|
||||||
window.cornerstone = cornerstone;
|
window.cornerstone = cornerstone;
|
||||||
@ -284,6 +285,8 @@ export default async function init({
|
|||||||
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
||||||
|
|
||||||
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener);
|
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener);
|
||||||
|
|
||||||
|
initViewTiming({ element, eventTarget });
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementDisabledHandler(evt) {
|
function elementDisabledHandler(evt) {
|
||||||
@ -305,8 +308,8 @@ export default async function init({
|
|||||||
viewportGridService.subscribe(
|
viewportGridService.subscribe(
|
||||||
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
|
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
|
||||||
activeViewportEventListener
|
activeViewportEventListener
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CPUModal() {
|
function CPUModal() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
45
extensions/cornerstone/src/utils/initViewTiming.ts
Normal file
45
extensions/cornerstone/src/utils/initViewTiming.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { log, Types } from '@ohif/core';
|
||||||
|
import { EVENTS } from '@cornerstonejs/core';
|
||||||
|
|
||||||
|
const { TimingEnum } = Types;
|
||||||
|
|
||||||
|
const IMAGE_TIMING_KEYS = [
|
||||||
|
TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES,
|
||||||
|
TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE,
|
||||||
|
TimingEnum.STUDY_TO_FIRST_IMAGE,
|
||||||
|
];
|
||||||
|
|
||||||
|
const imageTiming = {
|
||||||
|
viewportsWaiting: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the initial view timing reporting.
|
||||||
|
* This allows knowing how many viewports are waiting for initial views and
|
||||||
|
* when the IMAGE_RENDERED gets sent out.
|
||||||
|
* The first image rendered will fire the FIRST_IMAGE timeEnd logs, while
|
||||||
|
* the last of the enabled viewport will fire the ALL_IMAGES timeEnd logs.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function initViewTiming({ element }) {
|
||||||
|
if (!IMAGE_TIMING_KEYS.find(key => log.timingKeys[key])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
imageTiming.viewportsWaiting += 1;
|
||||||
|
element.addEventListener(EVENTS.IMAGE_RENDERED, imageRenderedListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageRenderedListener(evt) {
|
||||||
|
if (evt.detail.viewportStatus === 'preRender') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.timeEnd(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
|
||||||
|
log.timeEnd(TimingEnum.STUDY_TO_FIRST_IMAGE);
|
||||||
|
log.timeEnd(TimingEnum.SCRIPT_TO_VIEW);
|
||||||
|
imageTiming.viewportsWaiting -= 1;
|
||||||
|
evt.detail.element.removeEventListener(EVENTS.IMAGE_RENDERED, imageRenderedListener);
|
||||||
|
if (!imageTiming.viewportsWaiting) {
|
||||||
|
log.timeEnd(TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,22 +13,23 @@ export default function getDisplaySetMessages(
|
|||||||
isReconstructable: boolean
|
isReconstructable: boolean
|
||||||
): DisplaySetMessageList {
|
): DisplaySetMessageList {
|
||||||
const messages = new DisplaySetMessageList();
|
const messages = new DisplaySetMessageList();
|
||||||
if (!instances.length) {
|
const firstInstance = instances[0];
|
||||||
|
if (!instances.length || firstInstance) {
|
||||||
messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES);
|
messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstInstance = instances[0];
|
const { Modality, ImageType, NumberOfFrames } = firstInstance;
|
||||||
// Due to current requirements, LOCALIZER series doesn't have any messages
|
// Due to current requirements, LOCALIZER series doesn't have any messages
|
||||||
if (firstInstance?.ImageType?.includes('LOCALIZER')) {
|
if (ImageType?.includes('LOCALIZER')) {
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modality = firstInstance.Modality;
|
|
||||||
if (!constructableModalities.includes(Modality)) {
|
if (!constructableModalities.includes(Modality)) {
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMultiframe = firstInstance.NumberOfFrames > 1;
|
const isMultiframe = NumberOfFrames > 1;
|
||||||
// Can't reconstruct if all instances don't have the ImagePositionPatient.
|
// Can't reconstruct if all instances don't have the ImagePositionPatient.
|
||||||
if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) {
|
if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) {
|
||||||
messages.addMessage(DisplaySetMessage.CODES.NO_POSITION_INFORMATION);
|
messages.addMessage(DisplaySetMessage.CODES.NO_POSITION_INFORMATION);
|
||||||
|
|||||||
@ -42,6 +42,10 @@
|
|||||||
name="msapplication-config"
|
name="msapplication-config"
|
||||||
content="<%= PUBLIC_URL %>assets/browserconfig.xml"
|
content="<%= PUBLIC_URL %>assets/browserconfig.xml"
|
||||||
/>
|
/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
console.time('scriptToView');
|
||||||
|
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||||
|
</script>
|
||||||
<link
|
<link
|
||||||
rel="manifest"
|
rel="manifest"
|
||||||
href="<%= PUBLIC_URL %>manifest.json"
|
href="<%= PUBLIC_URL %>manifest.json"
|
||||||
@ -183,9 +187,6 @@
|
|||||||
<!-- Targets IE11 -->
|
<!-- Targets IE11 -->
|
||||||
<!-- <script
|
<!-- <script
|
||||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>!-->
|
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>!-->
|
||||||
<script type="text/javascript">
|
|
||||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
|
||||||
</script>
|
|
||||||
<script
|
<script
|
||||||
rel="preload"
|
rel="preload"
|
||||||
as="script"
|
as="script"
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
/* eslint-disable react/jsx-props-no-spreading */
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ExtensionManager, MODULE_TYPES } from '@ohif/core';
|
import { ExtensionManager, MODULE_TYPES, Types, log } from '@ohif/core';
|
||||||
//
|
//
|
||||||
import { extensionManager } from '../App.tsx';
|
import { extensionManager } from '../App.tsx';
|
||||||
import { useParams, useLocation } from 'react-router';
|
import { useParams, useLocation } from 'react-router';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import useSearchParams from '../hooks/useSearchParams.ts';
|
import useSearchParams from '../hooks/useSearchParams.ts';
|
||||||
|
|
||||||
|
const { TimingEnum } = Types;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if two React Router location objects are the same.
|
* Determines if two React Router location objects are the same.
|
||||||
*/
|
*/
|
||||||
@ -149,7 +151,7 @@ function DataSourceWrapper(props) {
|
|||||||
// 204: no content
|
// 204: no content
|
||||||
async function getData() {
|
async function getData() {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
log.time(TimingEnum.SEARCH_TO_LIST);
|
||||||
const studies = await dataSource.query.studies.search(queryFilterValues);
|
const studies = await dataSource.query.studies.search(queryFilterValues);
|
||||||
|
|
||||||
setData({
|
setData({
|
||||||
@ -159,6 +161,8 @@ function DataSourceWrapper(props) {
|
|||||||
pageNumber: queryFilterValues.pageNumber,
|
pageNumber: queryFilterValues.pageNumber,
|
||||||
location,
|
location,
|
||||||
});
|
});
|
||||||
|
log.timeEnd(TimingEnum.SCRIPT_TO_VIEW);
|
||||||
|
log.timeEnd(TimingEnum.SEARCH_TO_LIST);
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react';
|
|||||||
import { useParams, useLocation, useNavigate } from 'react-router';
|
import { useParams, useLocation, useNavigate } from 'react-router';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
// TODO: DicomMetadataStore should be injected?
|
// TODO: DicomMetadataStore should be injected?
|
||||||
import { DicomMetadataStore, ServicesManager, utils } from '@ohif/core';
|
import { DicomMetadataStore, ServicesManager, utils, Types, log } from '@ohif/core';
|
||||||
import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
|
import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
|
||||||
import { useSearchParams } from '@hooks';
|
import { useSearchParams } from '@hooks';
|
||||||
import { useAppConfig } from '@state';
|
import { useAppConfig } from '@state';
|
||||||
@ -14,6 +14,7 @@ import loadModules from '../../pluginImports';
|
|||||||
import isSeriesFilterUsed from '../../utils/isSeriesFilterUsed';
|
import isSeriesFilterUsed from '../../utils/isSeriesFilterUsed';
|
||||||
|
|
||||||
const { getSplitParam } = utils;
|
const { getSplitParam } = utils;
|
||||||
|
const { TimingEnum } = Types;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the route.
|
* Initialize the route.
|
||||||
@ -60,6 +61,8 @@ function defaultRouteInit(
|
|||||||
|
|
||||||
unsubscriptions.push(instanceAddedUnsubscribe);
|
unsubscriptions.push(instanceAddedUnsubscribe);
|
||||||
|
|
||||||
|
log.time(TimingEnum.STUDY_TO_DISPLAY_SETS);
|
||||||
|
log.time(TimingEnum.STUDY_TO_FIRST_IMAGE);
|
||||||
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
||||||
dataSource.retrieve.series.metadata({
|
dataSource.retrieve.series.metadata({
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
@ -81,6 +84,9 @@ function defaultRouteInit(
|
|||||||
// until we run the hanging protocol matching service.
|
// until we run the hanging protocol matching service.
|
||||||
|
|
||||||
Promise.allSettled(allRetrieves).then(() => {
|
Promise.allSettled(allRetrieves).then(() => {
|
||||||
|
log.timeEnd(TimingEnum.STUDY_TO_DISPLAY_SETS);
|
||||||
|
log.time(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
|
||||||
|
log.time(TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
|
||||||
const displaySets = displaySetService.getActiveDisplaySets();
|
const displaySets = displaySetService.getActiveDisplaySets();
|
||||||
|
|
||||||
if (!displaySets || !displaySets.length) {
|
if (!displaySets || !displaySets.length) {
|
||||||
|
|||||||
@ -4,8 +4,25 @@ const log = {
|
|||||||
info: console.log,
|
info: console.log,
|
||||||
trace: console.trace,
|
trace: console.trace,
|
||||||
debug: console.debug,
|
debug: console.debug,
|
||||||
time: console.time,
|
time: key => {
|
||||||
timeEnd: console.timeEnd,
|
log.timingKeys[key] = true;
|
||||||
|
console.time(key);
|
||||||
|
},
|
||||||
|
timeEnd: key => {
|
||||||
|
if (!log.timingKeys[key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.timingKeys[key] = false;
|
||||||
|
console.timeEnd(key);
|
||||||
|
},
|
||||||
|
// Store the timing keys to allow knowing whether or not to log events
|
||||||
|
timingKeys: {
|
||||||
|
// script time values are added during the index.html initial load,
|
||||||
|
// before log (this file) is loaded, and the log
|
||||||
|
// can't depend on the enums, so for this case recreate the string.
|
||||||
|
// See TimingEnum for details
|
||||||
|
scriptToView: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default log;
|
export default log;
|
||||||
|
|||||||
24
platform/core/src/types/TimingEnum.ts
Normal file
24
platform/core/src/types/TimingEnum.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export enum TimingEnum {
|
||||||
|
// The time from when the users selects a study until the study metadata
|
||||||
|
// is loaded (and the display sets are ready)
|
||||||
|
STUDY_TO_DISPLAY_SETS = 'studyToDisplaySetsLoaded',
|
||||||
|
|
||||||
|
// The time from when the user selects a study until any viewport renders
|
||||||
|
STUDY_TO_FIRST_IMAGE = 'studyToFirstImage',
|
||||||
|
|
||||||
|
// The time from when display sets are loaded until any viewport renders
|
||||||
|
// an image.
|
||||||
|
DISPLAY_SETS_TO_FIRST_IMAGE = 'displaySetsToFirstImage',
|
||||||
|
|
||||||
|
// The time from when display sets are loaded until all viewports have images
|
||||||
|
DISPLAY_SETS_TO_ALL_IMAGES = 'displaySetsToAllImages',
|
||||||
|
|
||||||
|
// The time from when the user hits search until the worklist is displayed
|
||||||
|
SEARCH_TO_LIST = 'searchToList',
|
||||||
|
|
||||||
|
// The time from when the html script first starts being evaluated (before
|
||||||
|
// any other scripts or CSS is loaded), until the time that the first image
|
||||||
|
// is viewed for viewer endpoints, or the time that the first search for studies
|
||||||
|
// completes.
|
||||||
|
SCRIPT_TO_VIEW = 'scriptToView',
|
||||||
|
}
|
||||||
@ -19,6 +19,9 @@ export type * from './PanelModule';
|
|||||||
export type * from './IPubSub';
|
export type * from './IPubSub';
|
||||||
export type * from './Color';
|
export type * from './Color';
|
||||||
|
|
||||||
|
// Enum exports
|
||||||
|
export * from './TimingEnum';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the types used within the various services and managers, but
|
* Export the types used within the various services and managers, but
|
||||||
* not the services/managers themselves, which are exported at the top level.
|
* not the services/managers themselves, which are exported at the top level.
|
||||||
|
|||||||
@ -78,9 +78,12 @@ export default function getPixelSpacingInformation(instance) {
|
|||||||
pixelSpacing => pixelSpacing / EstimatedRadiographicMagnificationFactor
|
pixelSpacing => pixelSpacing / EstimatedRadiographicMagnificationFactor
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log.info(
|
if (!instance._loggedSpacingMessage) {
|
||||||
'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.'
|
log.info(
|
||||||
);
|
'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.'
|
||||||
|
);
|
||||||
|
instance._loggedSpacingMessage = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user