Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/aics-image-viewer/components/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const defaultVisibleControls: ControlVisibilityFlags = {
showAxesButton: true,
showBoundingBoxButton: true,
metadataViewer: true,
scaleLevelControl: true,
};

const defaultProps: AppProps = {
Expand Down Expand Up @@ -134,6 +135,7 @@ const App: React.FC<AppProps> = (props) => {
viewMode,
channelSettings,
changeChannelSetting,
changeViewerSetting,
applyColorPresets,
setSavedViewerChannelSettings,
getCurrentViewerChannelSettings,
Expand All @@ -159,6 +161,10 @@ const App: React.FC<AppProps> = (props) => {
const [errorAlert, _showError] = useErrorAlert();
const showError = props.showError ?? _showError;

// TODO temporary hack to get scale level controls with the scale level specification tools that vole-core currently
// offers. Replace with something better thought-out, ideally via some changes to vole-core.
const [defaultScaleLevel, setDefaultScaleLevel] = useState(0);

useEffect(() => {
// Get notifications of loading errors which occur after the initial load, e.g. on time change or new channel load
view3d.setLoadErrorHandler((_vol, e) => showError(e));
Expand Down Expand Up @@ -242,10 +248,20 @@ const App: React.FC<AppProps> = (props) => {
}),
});

changeViewerSetting("scaleLevelRange", [0, newImage.imageInfo.imageInfo.multiscaleLevelDims.length - 1]);
changeViewerSetting("exactScaleLevel", newImage.imageInfo.multiscaleLevel);
setDefaultScaleLevel(newImage.imageInfo.multiscaleLevel);
onImageTitleChange?.(newImage.imageInfo.imageInfo.name);
view3d.updateActiveChannels(newImage);
},
[view3d, viewerState, onImageTitleChange, changeChannelSetting, getCurrentViewerChannelSettings]
[
view3d,
viewerState,
changeViewerSetting,
onImageTitleChange,
changeChannelSetting,
getCurrentViewerChannelSettings,
]
);

const onChannelLoaded = useCallback(
Expand Down Expand Up @@ -566,6 +582,24 @@ const App: React.FC<AppProps> = (props) => {
[props.transform?.rotation, view3d]
);

const { scaleLevelRange, exactScaleLevel, useExactScaleLevel } = viewerSettings;
useImageEffect(
(currentImage) => {
const [levelMin, levelMax] = scaleLevelRange;

if (useExactScaleLevel) {
currentImage.updateRequiredData({ useExplicitLevel: true, multiscaleLevel: exactScaleLevel });
} else if (levelMin > defaultScaleLevel) {
currentImage.updateRequiredData({ useExplicitLevel: true, multiscaleLevel: levelMin });
} else if (defaultScaleLevel > levelMax) {
currentImage.updateRequiredData({ useExplicitLevel: true, multiscaleLevel: levelMax });
} else {
currentImage.updateRequiredData({ useExplicitLevel: false });
}
},
[scaleLevelRange, exactScaleLevel, useExactScaleLevel, defaultScaleLevel]
);

const usePerAxisClippingUpdater = (
axis: AxisName,
[minval, maxval]: [number, number],
Expand Down Expand Up @@ -648,6 +682,8 @@ const App: React.FC<AppProps> = (props) => {
collapsed={controlPanelClosed}
// image state
imageName={image?.name}
multiscaleDims={image?.imageInfo.imageInfo.multiscaleLevelDims}
multiscaleLevel={image?.imageInfo.multiscaleLevel}
hasImage={!!image}
pixelSize={pixelSize}
channelDataChannels={image?.channels}
Expand Down
3 changes: 2 additions & 1 deletion src/aics-image-viewer/components/App/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ type ControlNames =
| "resetCameraButton"
| "showAxesButton"
| "showBoundingBoxButton"
| "metadataViewer";
| "metadataViewer"
| "scaleLevelControl";
/** Show/hide different elements of the UI */
export type ControlVisibilityFlags = { [K in ControlNames]: boolean };

Expand Down
16 changes: 15 additions & 1 deletion src/aics-image-viewer/components/ControlPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// TODO properly export this type from @aics/vole-core
import { VolumeDims } from "@aics/vole-core/es/types/VolumeDims";
import { Button, Collapse, CollapseProps, Dropdown, Flex, MenuProps, Tooltip } from "antd";
import { MenuInfo } from "rc-menu/lib/interface";
import React from "react";
Expand All @@ -9,6 +11,7 @@ import ChannelsWidget from "../ChannelsWidget";
import CustomizeWidget, { CustomizeWidgetProps } from "../CustomizeWidget";
import GlobalVolumeControls, { GlobalVolumeControlsProps } from "../GlobalVolumeControls";
import MetadataViewer from "../MetadataViewer";
import ScaleLevelControls from "../ScaleLevelControls";
import ViewerIcon from "../shared/ViewerIcon";
import { connectToViewerState } from "../ViewerStateProvider";

Expand All @@ -25,11 +28,14 @@ interface ControlPanelProps
CustomizeWidgetProps["visibleControls"] & {
colorPresetsDropdown: boolean;
metadataViewer: boolean;
scaleLevelControl: boolean;
};
getMetadata: () => MetadataRecord;
collapsed: boolean;
setCollapsed: (value: boolean) => void;
resetToDefaultViewerState: () => void;
multiscaleDims?: VolumeDims[];
multiscaleLevel?: number;
}

const enum ControlTab {
Expand Down Expand Up @@ -108,8 +114,8 @@ function ControlPanel(props: ControlPanelProps): React.ReactElement {
),
},
];
const showCustomize = visibleControls.backgroundColorPicker || visibleControls.boundingBoxColorPicker;

const showCustomize = visibleControls.backgroundColorPicker || visibleControls.boundingBoxColorPicker;
if (showCustomize) {
items.push({
key: 1,
Expand All @@ -118,6 +124,14 @@ function ControlPanel(props: ControlPanelProps): React.ReactElement {
});
}

if (visibleControls.scaleLevelControl && Array.isArray(props.multiscaleDims) && props.multiscaleDims.length > 1) {
items.push({
key: 2,
label: "Scale Level",
children: <ScaleLevelControls multiscaleDims={props.multiscaleDims} multiscaleLevel={props.multiscaleLevel} />,
});
}

return (
<Flex gap={10} vertical>
<Collapse bordered={false} defaultActiveKey={showCustomize ? [0, 1] : 0} items={items} />
Expand Down
66 changes: 66 additions & 0 deletions src/aics-image-viewer/components/ScaleLevelControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { VolumeDims } from "@aics/vole-core/es/types/VolumeDims";
import Checkbox from "antd/es/checkbox/Checkbox";
import React, { useCallback } from "react";

import { ViewerSettingUpdater } from "./ViewerStateProvider/types";

import SliderRow from "./shared/SliderRow";
import { connectToViewerState } from "./ViewerStateProvider";

export type ScaleLevelControlsProps = {
multiscaleDims: VolumeDims[];
multiscaleLevel?: number;

// from viewer state
scaleLevelRange: [number, number];
exactScaleLevel: number;
useExactScaleLevel: boolean;
changeViewerSetting: ViewerSettingUpdater;
};

const ScaleLevelControls: React.FC<ScaleLevelControlsProps> = (props) => {
const { scaleLevelRange, exactScaleLevel, useExactScaleLevel, changeViewerSetting } = props;
const onSliderChange = useCallback(
([min, max]: number[]) => {
if (useExactScaleLevel) {
changeViewerSetting("exactScaleLevel", min);
} else {
changeViewerSetting("scaleLevelRange", [min, max]);
}
},
[useExactScaleLevel, changeViewerSetting]
);
return (
<>
<ul>
{props.multiscaleDims.map(({ shape: [t, c, z, y, x] }, index) => (
<li style={index === props.multiscaleLevel ? { fontWeight: "bold" } : {}} key={index}>
{`level ${index}: ${x}x${y}x${z}, ${t} timesteps, ${c} channels`}
</li>
))}
</ul>
<SliderRow
key={Number(useExactScaleLevel)}
label={useExactScaleLevel ? "Scale level" : "Scale range"}
start={useExactScaleLevel ? exactScaleLevel : scaleLevelRange}
step={1}
max={props.multiscaleDims.length - 1}
formatInteger={true}
onChange={onSliderChange}
/>
<SliderRow label="Exact level">
<Checkbox
checked={useExactScaleLevel}
onChange={({ target }) => props.changeViewerSetting("useExactScaleLevel", target.checked)}
/>
</SliderRow>
</>
);
};

export default connectToViewerState(ScaleLevelControls, [
"changeViewerSetting",
"scaleLevelRange",
"exactScaleLevel",
"useExactScaleLevel",
]);
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export interface ViewerState {
time: number;
scene: number;
cameraState: Partial<CameraState> | undefined;
scaleLevelRange: [number, number];
exactScaleLevel: number;
useExactScaleLevel: boolean;
}

export type ViewerStateKey = keyof ViewerState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const SliderRow: React.FC<SliderRowProps> = (props) => (
<SmarterSlider
range={{ min: props.min ?? 0, max: props.max }}
start={props.start}
step={props.step}
connect={true}
tooltips={true}
behaviour="drag"
Expand Down
3 changes: 3 additions & 0 deletions src/aics-image-viewer/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ export const getDefaultViewerState = (): ViewerState => ({
// This prevents a bug where the camera's position and view mode are set to
// incompatible states and the viewport becomes blank.
cameraState: USE_VIEW_MODE_DEFAULT_CAMERA,
scaleLevelRange: [0, Infinity],
exactScaleLevel: 0,
useExactScaleLevel: false,
});

const INIT_COLORS = PRESET_COLORS_0;
Expand Down
6 changes: 6 additions & 0 deletions src/aics-image-viewer/shared/utils/test/urlParsing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,9 @@ describe("Viewer state", () => {
time: 0,
scene: 0,
cameraState: undefined,
scaleLevelRange: [0, 3],
exactScaleLevel: 0,
useExactScaleLevel: false,
};
const SERIALIZED_DEFAULT_VIEWER_STATE: ViewerStateParams = {
mode: "volumetric",
Expand Down Expand Up @@ -458,6 +461,9 @@ describe("Viewer state", () => {
slice: { x: 0.25, y: 0.75, z: 0.5 },
time: 100,
scene: 3,
scaleLevelRange: [0, 3],
exactScaleLevel: 0,
useExactScaleLevel: false,
cameraState: {
position: [-1.05, -4, 45],
target: [0, 0, 0],
Expand Down
Loading