Skip to content

Commit

Permalink
Merge branch 'main' into fix/channel-renames
Browse files Browse the repository at this point in the history
  • Loading branch information
ShrimpCryptid authored Oct 26, 2024
2 parents 12ad739 + 9b57692 commit c1db3be
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 89 deletions.
74 changes: 36 additions & 38 deletions src/aics-image-viewer/components/App/index.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,60 @@
// 3rd Party Imports
import {
CreateLoaderOptions,
IVolumeLoader,
LoadSpec,
VolumeLoaderContext,
PrefetchDirection,
RENDERMODE_PATHTRACE,
RENDERMODE_RAYMARCH,
View3d,
Volume,
IVolumeLoader,
PrefetchDirection,
VolumeFileFormat,
VolumeLoaderContext,
} from "@aics/volume-viewer";
import { Layout } from "antd";
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { debounce } from "lodash";
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { Box3, Vector3 } from "three";

import type { AppProps, ControlVisibilityFlags, UseImageEffectType } from "./types";
import type { ChannelState } from "../ViewerStateProvider/types";

import { useStateWithGetter, useConstructor } from "../../shared/utils/hooks";
import {
AXIS_MARGIN_DEFAULT,
CACHE_MAX_SIZE,
CONTROL_PANEL_CLOSE_WIDTH,
getDefaultChannelColor,
getDefaultViewerState,
QUEUE_MAX_LOW_PRIORITY_SIZE,
QUEUE_MAX_SIZE,
SCALE_BAR_MARGIN_DEFAULT,
} from "../../shared/constants";
import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
import { activeAxisMap, AxisName, IsosurfaceFormat, MetadataRecord, PerAxis } from "../../shared/types";
import { colorArrayToFloats } from "../../shared/utils/colorRepresentations";
import {
controlPointsToRamp,
initializeLut,
rampToControlPoints,
remapControlPointsForChannel,
} from "../../shared/utils/controlPointsToLut";
import { makeChannelIndexGrouping, ChannelGrouping } from "../../shared/utils/viewerChannelSettings";
import { activeAxisMap, AxisName, IsosurfaceFormat, MetadataRecord, PerAxis } from "../../shared/types";
import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
import {
CONTROL_PANEL_CLOSE_WIDTH,
AXIS_MARGIN_DEFAULT,
SCALE_BAR_MARGIN_DEFAULT,
CACHE_MAX_SIZE,
QUEUE_MAX_SIZE,
QUEUE_MAX_LOW_PRIORITY_SIZE,
getDefaultViewerState,
getDefaultChannelColor,
} from "../../shared/constants";
import { useConstructor, useStateWithGetter } from "../../shared/utils/hooks";
import PlayControls from "../../shared/utils/playControls";
import { colorArrayToFloats } from "../../shared/utils/colorRepresentations";
import {
gammaSliderToImageValues,
densitySliderToImageValue,
brightnessSliderToImageValue,
alphaSliderToImageValue,
brightnessSliderToImageValue,
densitySliderToImageValue,
gammaSliderToImageValues,
} from "../../shared/utils/sliderValuesToImageValues";
import { ChannelGrouping, makeChannelIndexGrouping } from "../../shared/utils/viewerChannelSettings";
import { initializeOneChannelSetting } from "../../shared/utils/viewerState";
import type { ChannelState } from "../ViewerStateProvider/types";
import type { AppProps, ControlVisibilityFlags, UseImageEffectType } from "./types";

import { ViewerStateContext } from "../ViewerStateProvider";
import ChannelUpdater from "./ChannelUpdater";
import ControlPanel from "../ControlPanel";
import Toolbar from "../Toolbar";
import CellViewerCanvasWrapper from "../CellViewerCanvasWrapper";
import StyleProvider from "../StyleProvider";
import ControlPanel from "../ControlPanel";
import { useErrorAlert } from "../ErrorAlert";
import StyleProvider from "../StyleProvider";
import Toolbar from "../Toolbar";
import { ViewerStateContext } from "../ViewerStateProvider";
import ChannelUpdater from "./ChannelUpdater";

import "../../assets/styles/globals.css";
import "./styles.css";
Expand Down Expand Up @@ -266,8 +265,7 @@ const App: React.FC<AppProps> = (props) => {
) {
const viewerChannelSettings = getCurrentViewerChannelSettings();
const { ramp, controlPoints } = initializeLut(aimg, channelIndex, viewerChannelSettings);
changeChannelSetting(channelIndex, "controlPoints", controlPoints);
changeChannelSetting(channelIndex, "ramp", controlPointsToRamp(ramp));
changeChannelSetting(channelIndex, { controlPoints: controlPoints, ramp: controlPointsToRamp(ramp) });
onResetChannel(channelIndex);
} else {
// try not to update lut from here if we are in play mode
Expand All @@ -279,19 +277,20 @@ const App: React.FC<AppProps> = (props) => {
const oldRange = channelRangesRef.current[channelIndex];
if (thisChannelsSettings.useControlPoints) {
// control points were just automatically remapped - update in state
changeChannelSetting(channelIndex, "controlPoints", thisChannel.lut.controlPoints);
// now manually remap ramp using the channel's old range
const rampControlPoints = rampToControlPoints(thisChannelsSettings.ramp);
const remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
changeChannelSetting(channelIndex, "ramp", controlPointsToRamp(remappedRampControlPoints));
changeChannelSetting(channelIndex, {
ramp: controlPointsToRamp(remappedRampControlPoints),
controlPoints: thisChannel.lut.controlPoints,
});
} else {
// ramp was just automatically remapped - update in state
const ramp = controlPointsToRamp(thisChannel.lut.controlPoints);
changeChannelSetting(channelIndex, "ramp", ramp);
// now manually remap control points using the channel's old range
const { controlPoints } = thisChannelsSettings;
const remappedControlPoints = remapControlPointsForChannel(controlPoints, oldRange, thisChannel);
changeChannelSetting(channelIndex, "controlPoints", remappedControlPoints);
changeChannelSetting(channelIndex, { controlPoints: remappedControlPoints, ramp: ramp });
}
}
};
Expand Down Expand Up @@ -610,8 +609,7 @@ const App: React.FC<AppProps> = (props) => {
for (let i = 0; i < channelSettings.length; i++) {
if (channelsAwaitingReset.has(i)) {
const { ramp, controlPoints } = initializeLut(image, i, getCurrentViewerChannelSettings());
changeChannelSetting(i, "controlPoints", controlPoints);
changeChannelSetting(i, "ramp", controlPointsToRamp(ramp));
changeChannelSetting(i, { controlPoints: controlPoints, ramp: controlPointsToRamp(ramp) });
onResetChannel(i);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/aics-image-viewer/components/ChannelsWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const ChannelsWidget: React.FC<ChannelsWidgetProps> = (props: ChannelsWidgetProp
const { channelGroupedByType, channelSettings, channelDataChannels, filterFunc, viewerChannelSettings } = props;

const createCheckboxHandler = (key: ChannelStateKey, value: boolean) => (channelArray: number[]) => {
props.changeChannelSetting(channelArray, key, value);
props.changeChannelSetting(channelArray, {[key]: value});
};

const showVolumes = createCheckboxHandler("volumeEnabled", true);
Expand Down
12 changes: 6 additions & 6 deletions src/aics-image-viewer/components/ChannelsWidgetRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,25 @@ const ChannelsWidgetRow: React.FC<ChannelsWidgetRowProps> = (props: ChannelsWidg
const [controlsOpen, setControlsOpen] = useState(false);

const changeSettingForThisChannel = useCallback<SingleChannelSettingUpdater>(
(key, value) => changeChannelSetting(index, key, value),
(value) => changeChannelSetting(index, value),
[changeChannelSetting, index]
);

const volumeCheckHandler = ({ target }: CheckboxChangeEvent): void => {
changeChannelSetting(index, "volumeEnabled", target.checked);
changeChannelSetting(index, {"volumeEnabled": target.checked});
};

const isosurfaceCheckHandler = ({ target }: CheckboxChangeEvent): void => {
changeChannelSetting(index, "isosurfaceEnabled", target.checked);
changeChannelSetting(index, {"isosurfaceEnabled": target.checked});
};

const onIsovalueChange = ([newValue]: number[]): void => changeSettingForThisChannel("isovalue", newValue);
const onIsovalueChange = ([newValue]: number[]): void => changeSettingForThisChannel({"isovalue": newValue});
const onOpacityChange = ([newValue]: number[]): void =>
changeSettingForThisChannel("opacity", newValue / ISOSURFACE_OPACITY_SLIDER_MAX);
changeSettingForThisChannel({"opacity": newValue / ISOSURFACE_OPACITY_SLIDER_MAX});

const onColorChange = (newRGB: ColorObject, _oldRGB?: ColorObject, index?: number): void => {
const color = colorObjectToArray(newRGB);
props.changeChannelSetting(index!, "color", color);
props.changeChannelSetting(index!, {"color": color});
};

const createColorPicker = (): React.ReactNode => (
Expand Down
10 changes: 5 additions & 5 deletions src/aics-image-viewer/components/TfEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ const TfEditor: React.FC<TfEditorProps> = (props) => {
const [selectedPointIdx, setSelectedPointIdx] = useState<number | null>(null);
const [draggedPointIdx, _setDraggedPointIdx] = useState<number | TfEditorRampSliderHandle | null>(null);

const _setCPs = useCallback((p: ControlPoint[]) => changeChannelSetting("controlPoints", p), [changeChannelSetting]);
const setRamp = useCallback((ramp: [number, number]) => changeChannelSetting("ramp", ramp), [changeChannelSetting]);
const _setCPs = useCallback((p: ControlPoint[]) => changeChannelSetting({"controlPoints": p}), [changeChannelSetting]);
const setRamp = useCallback((ramp: [number, number]) => changeChannelSetting({"ramp": ramp}), [changeChannelSetting]);

// these bits of state need their freshest, most up-to-date values available in mouse event handlers. make refs!
const [controlPointsRef, setControlPoints] = useRefWithSetter(_setCPs, props.controlPoints);
Expand Down Expand Up @@ -425,7 +425,7 @@ const TfEditor: React.FC<TfEditorProps> = (props) => {
{createTFGeneratorButton("bestFitXF", "Auto 2", "Ramp over the middle 80% of data.")}
<Checkbox
checked={props.useControlPoints}
onChange={(e) => changeChannelSetting("useControlPoints", e.target.checked)}
onChange={(e) => changeChannelSetting({"useControlPoints": e.target.checked})}
style={{ marginLeft: "auto" }}
>
Advanced
Expand Down Expand Up @@ -521,14 +521,14 @@ const TfEditor: React.FC<TfEditorProps> = (props) => {
label={
<Checkbox
checked={props.colorizeEnabled}
onChange={(e) => changeChannelSetting("colorizeEnabled", e.target.checked)}
onChange={(e) => changeChannelSetting({"colorizeEnabled": e.target.checked})}
>
Colorize
</Checkbox>
}
max={1}
start={props.colorizeAlpha}
onUpdate={(values) => changeChannelSetting("colorizeAlpha", values[0])}
onUpdate={(values) => changeChannelSetting({"colorizeAlpha": values[0]})}
hideSlider={!props.colorizeEnabled}
/>
</div>
Expand Down
90 changes: 54 additions & 36 deletions src/aics-image-viewer/components/ViewerStateProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React, { useCallback, useContext, useEffect, useMemo, useReducer, useRef } from "react";

import { getDefaultViewerChannelSettings, getDefaultViewerState } from "../../shared/constants";
import { RenderMode, ViewMode } from "../../shared/enums";
import { ColorArray } from "../../shared/utils/colorRepresentations";
import { useConstructor } from "../../shared/utils/hooks";
import type {
ViewerStateContextType,
ViewerState,
ViewerSettingChangeHandlers,
ViewerSettingUpdater,
ChannelSettingUpdater,
ChannelState,
ChannelStateKey,
PartialIfObject,
ViewerSettingChangeHandlers,
ViewerSettingUpdater,
ViewerState,
ViewerStateContextType,
} from "./types";
import { RenderMode, ViewMode } from "../../shared/enums";
import { ColorArray } from "../../shared/utils/colorRepresentations";
import { getDefaultViewerChannelSettings, getDefaultViewerState } from "../../shared/constants";

import ResetStateProvider from "./ResetStateProvider";
import { useConstructor } from "../../shared/utils/hooks";

const isObject = <T,>(val: T): val is Extract<T, Record<string, unknown>> =>
typeof val === "object" && val !== null && !Array.isArray(val);
Expand Down Expand Up @@ -76,52 +78,62 @@ const viewerSettingsReducer = <K extends keyof ViewerState>(
}
};

/** Utility type to explicitly assert that one or more properties will *not* be defined on an object */
type WithExplicitlyUndefined<K extends keyof any, T> = T & { [key in K]?: never };
enum ChannelSettingActionType {
UniformUpdate = "UniformUpdate",
ArrayUpdate = "ArrayUpdate",
Init = "Init",
}

/** Set channel setting `key` on one or more channels specified by `index` to value `value`. */
type ChannelSettingUniformUpdateAction<K extends keyof ChannelState> = {
type ChannelSettingUniformUpdateAction<K extends ChannelStateKey> = {
type: ChannelSettingActionType.UniformUpdate;
index: number | number[];
key: K;
value: ChannelState[K];
value: Partial<Record<K, ChannelState[K]>>;
};
/** Set the values of channel setting `key` for all channels from an array of values ordered by channel index */
type ChannelSettingArrayUpdateAction<K extends keyof ChannelState> = {
type ChannelSettingArrayUpdateAction<K extends ChannelStateKey> = {
type: ChannelSettingActionType.ArrayUpdate;
key: K;
value: ChannelState[K][];
};
/** Initialize list of channel states */
type ChannelSettingInitAction = {
type: ChannelSettingActionType.Init;
value: ChannelState[];
};

type ChannelStateAction<K extends keyof ChannelState> =
type ChannelStateAction<K extends ChannelStateKey> =
| ChannelSettingUniformUpdateAction<K>
| WithExplicitlyUndefined<"index", ChannelSettingArrayUpdateAction<K>>
| WithExplicitlyUndefined<"index" | "key", ChannelSettingInitAction>;
| ChannelSettingArrayUpdateAction<K>
| ChannelSettingInitAction;

const channelSettingsReducer = <K extends keyof ChannelState>(
const channelSettingsReducer = <K extends ChannelStateKey>(
channelSettings: ChannelState[],
{ index, key, value }: ChannelStateAction<K>
action: ChannelStateAction<K>
): ChannelState[] => {
if (key === undefined) {
if (action.type === ChannelSettingActionType.Init) {
// ChannelSettingInitAction
return value as ChannelState[];
} else if (index === undefined) {
return action.value;
} else if (action.type === ChannelSettingActionType.ArrayUpdate) {
// ChannelSettingArrayUpdateAction
return channelSettings.map((channel, idx) => {
return value[idx] ? { ...channel, [key]: value[idx] } : channel;
return action.value[idx] ? { ...channel, [action.key]: action.value[idx] } : channel;
});
} else if (Array.isArray(index)) {
// ChannelSettingUniformUpdateAction on potentially multiple channels
return channelSettings.map((channel, idx) => (index.includes(idx) ? { ...channel, [key]: value } : channel));
} else {
// ChannelSettingUniformUpdateAction on a single channel
const newSettings = channelSettings.slice();
if (index >= 0 && index < channelSettings.length) {
newSettings[index] = { ...newSettings[index], [key]: value };
// type is ChannelSettingActionType.UniformUpdate
if (Array.isArray(action.index)) {
// ChannelSettingUniformUpdateAction on potentially multiple channels
return channelSettings.map((channel, idx) =>
(action.index as number[]).includes(idx) ? { ...channel, ...action.value } : channel
);
} else {
// ChannelSettingUniformUpdateAction on a single channel
const newSettings = channelSettings.slice();
if (action.index >= 0 && action.index < channelSettings.length) {
newSettings[action.index] = { ...newSettings[action.index], ...action.value };
}
return newSettings;
}
return newSettings;
}
};

Expand Down Expand Up @@ -167,13 +179,19 @@ const ViewerStateProvider: React.FC<{ viewerSettings?: Partial<ViewerState> }> =

const changeViewerSetting = useCallback<ViewerSettingUpdater>((key, value) => viewerDispatch({ key, value }), []);

const changeChannelSetting = useCallback<ChannelSettingUpdater>((index, key, value) => {
channelDispatch({ index, key, value });
const changeChannelSetting = useCallback<ChannelSettingUpdater>((index, value) => {
channelDispatch({ type: ChannelSettingActionType.UniformUpdate, index, value });
}, []);

const applyColorPresets = useCallback((value: ColorArray[]): void => channelDispatch({ key: "color", value }), []);
const applyColorPresets = useCallback(
(value: ColorArray[]): void => channelDispatch({ type: ChannelSettingActionType.ArrayUpdate, key: "color", value }),
[]
);

const setChannelSettings = useCallback((channels: ChannelState[]) => channelDispatch({ value: channels }), []);
const setChannelSettings = useCallback(
(channels: ChannelState[]) => channelDispatch({ type: ChannelSettingActionType.Init, value: channels }),
[]
);

// Sync viewer settings prop with state
// React docs seem to be fine with syncing state with props directly in the render function, but that caused an
Expand Down Expand Up @@ -233,7 +251,7 @@ const ViewerStateProvider: React.FC<{ viewerSettings?: Partial<ViewerState> }> =
*/
export function connectToViewerState<
Keys extends keyof ViewerStateContextType,
Props extends Pick<ViewerStateContextType, Keys>
Props extends Pick<ViewerStateContextType, Keys>,
>(component: React.ComponentType<Props>, keys: Keys[]): React.FC<Omit<Props, Keys>> {
const MemoedComponent = React.memo(component);

Expand Down
8 changes: 5 additions & 3 deletions src/aics-image-viewer/components/ViewerStateProvider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ export interface ChannelState {
export type ChannelStateKey = keyof ChannelState;
export type ChannelSettingUpdater = <K extends ChannelStateKey>(
index: number | number[],
key: K,
value: ChannelState[K]
value: Partial<Record<K, ChannelState[K]>>
) => void;

export type SingleChannelSettingUpdater = <K extends ChannelStateKey>(
value: Partial<Record<K, ChannelState[K]>>
) => void;
export type SingleChannelSettingUpdater = <K extends ChannelStateKey>(key: K, value: ChannelState[K]) => void;

export type ResetState = {
/**
Expand Down

0 comments on commit c1db3be

Please sign in to comment.