Skip to content

Commit

Permalink
feat: combine panel physical and pixel resolution in UI
Browse files Browse the repository at this point in the history
  • Loading branch information
seankmartin committed Sep 27, 2024
1 parent 0cb7ef2 commit 6c171df
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 79 deletions.
80 changes: 32 additions & 48 deletions src/ui/screenshot_menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import type {
ScreenshotManager,
} from "#src/util/screenshot_manager.js";
import { ScreenshotMode } from "#src/util/trackable_screenshot_mode.js";
import type { DimensionResolutionStats } from "#src/util/viewer_resolution_stats.js";
import type {
DimensionResolutionStats,
PanelViewport,
} from "#src/util/viewer_resolution_stats.js";
import {
getViewerLayerResolutions,
getViewerPanelResolutions,
Expand Down Expand Up @@ -59,7 +62,7 @@ const layerNamesForUI = {
/**
* Combine the resolution of all dimensions into a single string for UI display
*/
function formatResolution(resolution: DimensionResolutionStats[]) {
function formatPhysicalResolution(resolution: DimensionResolutionStats[]) {
if (resolution.length === 0) {
return {
type: "Loading...",
Expand Down Expand Up @@ -101,7 +104,7 @@ function formatResolution(resolution: DimensionResolutionStats[]) {
* of the voxels loaded for each Image, Volume, and Segmentation layer.
* This is to inform the user about the the physical units of the data and panels,
* and to help them decide on the scale of the screenshot.
*
*
* The screenshot menu supports keeping the slice view FOV fixed when changing the scale of the screenshot.
* This will cause the viewer to zoom in or out to keep the same FOV in the slice view.
* For example, an x2 scale will cause the viewer in slice views to zoom in by a factor of 2
Expand All @@ -115,7 +118,6 @@ export class ScreenshotDialog extends Overlay {
private forceScreenshotButton: HTMLButtonElement;
private statisticsTable: HTMLTableElement;
private panelResolutionTable: HTMLTableElement;
private panelPixelSizeTable: HTMLTableElement;
private layerResolutionTable: HTMLTableElement;
private statisticsContainer: HTMLDivElement;
private filenameAndButtonsContainer: HTMLDivElement;
Expand All @@ -128,7 +130,6 @@ export class ScreenshotDialog extends Overlay {
private throttledUpdateTableStatistics = this.registerCancellable(
throttle(() => {
this.populateLayerResolutionTable();
this.populatePanelPixelSizeTable();
this.handleScreenshotResize();
}, 500),
);
Expand Down Expand Up @@ -177,12 +178,10 @@ export class ScreenshotDialog extends Overlay {
this.content.appendChild(this.filenameAndButtonsContainer);
this.content.appendChild(this.createScaleRadioButtons());
this.content.appendChild(this.createPanelResolutionTable());
this.content.appendChild(this.createPanelPixelSizeTable());
this.content.appendChild(this.createLayerResolutionTable());
this.content.appendChild(this.createStatisticsTable());
this.updateUIBasedOnMode();
this.populatePanelResolutionTable();
this.populatePanelPixelSizeTable();
this.throttledUpdateTableStatistics();
}

Expand Down Expand Up @@ -342,13 +341,23 @@ export class ScreenshotDialog extends Overlay {
const keyHeader = document.createElement("th");
keyHeader.textContent = "Panel type";
headerRow.appendChild(keyHeader);
const valueHeader = document.createElement("th");
valueHeader.textContent = "Physical resolution";
headerRow.appendChild(valueHeader);
const physicalValueHeader = document.createElement("th");
physicalValueHeader.textContent = "Physical resolution";
headerRow.appendChild(physicalValueHeader);
const pixelValueHeader = document.createElement("th");
pixelValueHeader.textContent = "Pixel resolution";
headerRow.appendChild(pixelValueHeader);
return resolutionTable;
}

private populatePanelResolutionTable() {
function formatPixelResolution(panelArea: PanelViewport, scale: number) {
const width = Math.round(panelArea.right - panelArea.left) * scale;
const height = Math.round(panelArea.bottom - panelArea.top) * scale;
const type = panelArea.panelType;
return { width, height, type };
}

// Clear the table before populating it
while (this.panelResolutionTable.rows.length > 1) {
this.panelResolutionTable.deleteRow(1);
Expand All @@ -358,49 +367,24 @@ export class ScreenshotDialog extends Overlay {
this.screenshotManager.viewer.display.panels,
);
for (const resolution of resolutions) {
const resolutionStrings = formatResolution(resolution);
const physicalResolution = formatPhysicalResolution(
resolution.physicalResolution,
);
const pixelResolution = formatPixelResolution(
resolution.pixelResolution,
this.screenshotManager.screenshotScale,
);
const row = resolutionTable.insertRow();
const keyCell = row.insertCell();
const valueCell = row.insertCell();
keyCell.textContent = resolutionStrings.type;
valueCell.textContent = resolutionStrings.resolution;
const physicalValueCell = row.insertCell();
keyCell.textContent = physicalResolution.type;
physicalValueCell.textContent = physicalResolution.resolution;
const pixelValueCell = row.insertCell();
pixelValueCell.textContent = `${pixelResolution.width}x${pixelResolution.height} px`;
}
return resolutionTable;
}

private createPanelPixelSizeTable() {
const resolutionTable = (this.panelPixelSizeTable =
document.createElement("table"));
resolutionTable.classList.add("neuroglancer-screenshot-resolution-table");
resolutionTable.title = "Viewer resolution statistics";

const headerRow = resolutionTable.createTHead().insertRow();
const keyHeader = document.createElement("th");
keyHeader.textContent = "Panel type";
headerRow.appendChild(keyHeader);
const valueHeader = document.createElement("th");
valueHeader.textContent = "Pixel resolution";
headerRow.appendChild(valueHeader);
return resolutionTable;
}

private populatePanelPixelSizeTable() {
// Clear the table before populating it
while (this.panelPixelSizeTable.rows.length > 1) {
this.panelPixelSizeTable.deleteRow(1);
}
const pixelSizeTable = this.panelPixelSizeTable;
const panelPixelSizes =
this.screenshotManager.calculateUniqueScaledPanelViewportSizes();
for (const value of panelPixelSizes) {
const row = pixelSizeTable.insertRow();
const keyCell = row.insertCell();
const valueCell = row.insertCell();
keyCell.textContent = value.type;
valueCell.textContent = `${value.width}x${value.height} px`;
}
}

private createLayerResolutionTable() {
const resolutionTable = (this.layerResolutionTable =
document.createElement("table"));
Expand Down Expand Up @@ -442,7 +426,7 @@ export class ScreenshotDialog extends Overlay {
layerNamesForUI[type as keyof typeof layerNamesForUI];
this.layerResolutionKeyToCellMap.set(stringKey, valueCell);
}
valueCell.textContent = formatResolution(value).resolution;
valueCell.textContent = formatPhysicalResolution(value).resolution;
}
}

Expand Down
19 changes: 1 addition & 18 deletions src/util/screenshot_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async function extractViewportScreenshot(

/**
* Manages the screenshot functionality from the viewer viewer.
*
*
* Responsible for linking up the Python screenshot tool with the viewer, and handling the screenshot process.
* The screenshot manager provides information about updates in the screenshot process, and allows for the screenshot to be taken and saved.
* The screenshot UI menu listens to the signals emitted by the screenshot manager to update the UI.
Expand Down Expand Up @@ -256,23 +256,6 @@ export class ScreenshotManager extends RefCounted {
};
}

calculateUniqueScaledPanelViewportSizes() {
const panelAreas = calculatePanelViewportBounds(
this.viewer.display.panels,
).individualRenderPanelViewports;
const scaledPanelAreas = panelAreas.map((panelArea) => ({
width:
Math.round(panelArea.right - panelArea.left) * this.screenshotScale,
height:
Math.round(panelArea.bottom - panelArea.top) * this.screenshotScale,
type: panelArea.panelType,
}));
const uniquePanelAreas = Array.from(
new Set(scaledPanelAreas.map((area) => JSON.stringify(area))),
).map((area) => JSON.parse(area));
return uniquePanelAreas;
}

private handleScreenshotStarted() {
const { viewer } = this;
const shouldIncreaseCanvasSize = this.screenshotScale !== 1;
Expand Down
65 changes: 52 additions & 13 deletions src/util/viewer_resolution_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export interface PanelViewport {
panelType: string;
}

export interface PanelResolutionStats {
pixelResolution: PanelViewport;
physicalResolution: DimensionResolutionStats[];
}

interface CanvasSizeStatistics {
totalRenderPanelViewport: PanelViewport;
individualRenderPanelViewports: PanelViewport[];
Expand Down Expand Up @@ -164,44 +169,78 @@ export function getViewerLayerResolutions(
* @param onlyUniqueResolutions If true, only return panels with unique resolutions.
* It is quite common for all slice view panels to have the same resolution.
*
* @returns An array of resolutions for each panel.
* @returns An array of resolutions for each panel, both in physical units and pixel units.
*/
export function getViewerPanelResolutions(
panels: ReadonlySet<RenderedPanel>,
onlyUniqueResolutions = true,
): DimensionResolutionStats[][] {
): PanelResolutionStats[] {
function resolutionsEqual(
resolution1: DimensionResolutionStats[],
resolution2: DimensionResolutionStats[],
panelResolution1: PanelResolutionStats,
panelResolution2: PanelResolutionStats,
) {
if (resolution1.length !== resolution2.length) {
const physicalResolution1 = panelResolution1.physicalResolution;
const physicalResolution2 = panelResolution2.physicalResolution;
if (physicalResolution1.length !== physicalResolution2.length) {
return false;
}
for (let i = 0; i < resolution1.length; ++i) {
for (let i = 0; i < physicalResolution1.length; ++i) {
if (
resolution1[i].resolutionWithUnit !== resolution2[i].resolutionWithUnit
physicalResolution1[i].resolutionWithUnit !==
physicalResolution2[i].resolutionWithUnit
) {
return false;
}
if (resolution1[i].parentType !== resolution2[i].parentType) {
if (
physicalResolution1[i].parentType !== physicalResolution2[i].parentType
) {
return false;
}
if (resolution1[i].dimensionName !== resolution2[i].dimensionName) {
if (
physicalResolution1[i].dimensionName !==
physicalResolution2[i].dimensionName
) {
return false;
}
}
const pixelResolution1 = panelResolution1.pixelResolution;
const pixelResolution2 = panelResolution2.pixelResolution;
const width1 = pixelResolution1.right - pixelResolution1.left;
const width2 = pixelResolution2.right - pixelResolution2.left;
const height1 = pixelResolution1.bottom - pixelResolution1.top;
const height2 = pixelResolution2.bottom - pixelResolution2.top;
if (width1 !== width2 || height1 !== height2) {
return false;
}

return true;
}

const resolutions: DimensionResolutionStats[][] = [];
const resolutions: PanelResolutionStats[] = [];
for (const panel of panels) {
if (!(panel instanceof RenderedDataPanel)) continue;
const panel_resolution = [];
const viewport = panel.renderViewport;
const { width, height } = viewport;
const panelLeft = panel.canvasRelativeClippedLeft;
const panelTop = panel.canvasRelativeClippedTop;
const panelRight = panelLeft + width;
const panelBottom = panelTop + height;
const {
panelType,
panelDimensionUnit,
}: { panelType: string; panelDimensionUnit: string } =
determinePanelTypeAndUnit(panel);
const panel_resolution: PanelResolutionStats = {
pixelResolution: {
left: panelLeft,
right: panelRight,
top: panelTop,
bottom: panelBottom,
panelType,
},
physicalResolution: [],
};
const { physicalResolution } = panel_resolution;
const { navigationState } = panel;
const {
displayDimensionIndices,
Expand Down Expand Up @@ -239,7 +278,7 @@ export function getViewerPanelResolutions(
displayDimensionUnits[i],
{ precision: 2, elide1: false },
);
panel_resolution.push({
physicalResolution.push({
parentType: panelType,
resolutionWithUnit: `${formattedScale}/${panelDimensionUnit}`,
dimensionName: singleScale ? "All_" : dimensionName,
Expand All @@ -253,7 +292,7 @@ export function getViewerPanelResolutions(
if (!onlyUniqueResolutions) {
return resolutions;
}
const uniqueResolutions: DimensionResolutionStats[][] = [];
const uniqueResolutions: PanelResolutionStats[] = [];
for (const resolution of resolutions) {
let found = false;
for (const uniqueResolution of uniqueResolutions) {
Expand Down

0 comments on commit 6c171df

Please sign in to comment.