diff --git a/applications/visualizer/frontend/src/components/viewers/EM/EMStackTilesViewer.tsx b/applications/visualizer/frontend/src/components/viewers/EM/EMStackTilesViewer.tsx index d0a98742..f07d1a0f 100644 --- a/applications/visualizer/frontend/src/components/viewers/EM/EMStackTilesViewer.tsx +++ b/applications/visualizer/frontend/src/components/viewers/EM/EMStackTilesViewer.tsx @@ -205,7 +205,11 @@ const EMStackViewer = () => { } const [minSlice, maxSlice] = firstActiveDataset.emData.sliceRange; - const startSlice = Math.floor((maxSlice + minSlice) / 2); + // const startSlice = Math.floor((maxSlice + minSlice) / 2); + const startSlice = useMemo(() => { + return currentWorkspace.emViewerSettings.startSlice; + }, [currentWorkspace]); + const [segSlice, segSetSlice] = useState(startSlice); const ringSize = 11; @@ -217,8 +221,8 @@ const EMStackViewer = () => { const ringSeg = useRef>>(); const ringSynSeg = useRef>>(); - const [showNeurons, setShowNeurons] = useState(true); - const [showSynapses, setShowSynapses] = useState(true); + const [showNeurons, setShowNeurons] = useState(currentWorkspace.emViewerSettings.showNeurons); + const [showSynapses, setShowSynapses] = useState(currentWorkspace.emViewerSettings.showSynapses); const extent = useMemo(() => [0, 0, ...getEMResolution(firstActiveDataset)], [firstActiveDataset]); @@ -315,6 +319,9 @@ const EMStackViewer = () => { startAt: startSlice, extent: [minSlice, maxSlice], newLayer: (slice) => newEMLayer(firstActiveDataset, slice, tilegrid, projection), + onSlide: (slice) => { + currentWorkspace.setEmviewerSlice(slice); + }, }); if (hasNeuronSegmentations) { @@ -330,6 +337,7 @@ const EMStackViewer = () => { segSetSlice(slice); }, }); + ringSeg.current?.setVisibility(showNeurons); map.on("click", (e) => onFeatureClickRef.current(e.coordinate)); } @@ -345,6 +353,7 @@ const EMStackViewer = () => { currSynSegLayer.current = layer; }, }); + ringSynSeg.current?.setVisibility(showSynapses); } function handleSliceScroll(e: WheelEvent) { @@ -442,12 +451,18 @@ const EMStackViewer = () => { neurons: { label: "Neurons", checked: showNeurons, - onToggle: setShowNeurons, + onToggle: (show) => { + setShowNeurons(show); + currentWorkspace.emViewerShowNeurons(show); + }, }, synapses: { label: "Synapses", checked: showSynapses, - onToggle: setShowSynapses, + onToggle: (show) => { + setShowSynapses(show); + currentWorkspace.emViewerShowSynapses(show); + }, }, }} /> diff --git a/applications/visualizer/frontend/src/contexts/GlobalContext.tsx b/applications/visualizer/frontend/src/contexts/GlobalContext.tsx index 7fc94174..aec59b52 100644 --- a/applications/visualizer/frontend/src/contexts/GlobalContext.tsx +++ b/applications/visualizer/frontend/src/contexts/GlobalContext.tsx @@ -175,6 +175,8 @@ export const GlobalContextProvider: React.FC = ({ ch ws.contexts, ws.visibilities, ws.neuronGroups, + ws.emViewerSettings, + ws.viewers, ); workspace.viewers = ws.viewers; diff --git a/applications/visualizer/frontend/src/contexts/SerializedContext.tsx b/applications/visualizer/frontend/src/contexts/SerializedContext.tsx index 3fcff481..09b1e5b6 100644 --- a/applications/visualizer/frontend/src/contexts/SerializedContext.tsx +++ b/applications/visualizer/frontend/src/contexts/SerializedContext.tsx @@ -1,5 +1,5 @@ import type { NeuronGroup, ViewMode, ViewerType } from "../models"; -import type { ViewerData, ViewerSynchronizationPair } from "../models/models"; +import type { EMViewerSettings, ViewerData, ViewerSynchronizationPair } from "../models/models"; import type { SynchronizerContext } from "../models/synchronizer"; type SerializedWorkspace = { @@ -13,6 +13,7 @@ type SerializedWorkspace = { contexts: Record; activeSyncs: Record; visibilities: Record; + emViewerSettings: EMViewerSettings; }; export type SerializedGlobalContext = { diff --git a/applications/visualizer/frontend/src/helpers/slidingRing.ts b/applications/visualizer/frontend/src/helpers/slidingRing.ts index 3d43f2de..6380db79 100644 --- a/applications/visualizer/frontend/src/helpers/slidingRing.ts +++ b/applications/visualizer/frontend/src/helpers/slidingRing.ts @@ -91,7 +91,7 @@ export class SlidingRing { if (tailN < min) tailN = min; if (headN > max) { headN = max; - tailN = headN - this.ring.length - 1; + tailN = headN - this.ring.length + 1; } // populate the ring diff --git a/applications/visualizer/frontend/src/models/models.ts b/applications/visualizer/frontend/src/models/models.ts index bbfe667f..1f956bee 100644 --- a/applications/visualizer/frontend/src/models/models.ts +++ b/applications/visualizer/frontend/src/models/models.ts @@ -69,6 +69,12 @@ export interface ViewerData { [ViewerType.EM]?: EMViewerData; } +export interface EMViewerSettings { + showNeurons: boolean; + showSynapses: boolean; + startSlice: number; +} + const buildUrlFromFormat = (s: string, param: string) => { return s.replace(s.match("{[^}]+}")?.[0], param); }; diff --git a/applications/visualizer/frontend/src/models/workspace.ts b/applications/visualizer/frontend/src/models/workspace.ts index 6622c9d6..4f1cc7e9 100644 --- a/applications/visualizer/frontend/src/models/workspace.ts +++ b/applications/visualizer/frontend/src/models/workspace.ts @@ -4,7 +4,15 @@ import { createDraft, finishDraft, immerable, isDraft, produce } from "immer"; import getLayoutManagerAndStore from "../layout-manager/layoutManagerFactory"; import { type Dataset, type Neuron, NeuronsService } from "../rest"; import { GlobalError } from "./Error.ts"; -import { type NeuronGroup, type ViewerData, type ViewerSynchronizationPair, ViewerType, Visibility, getDefaultViewerData } from "./models"; +import { + type EMViewerSettings, + type NeuronGroup, + type ViewerData, + type ViewerSynchronizationPair, + ViewerType, + Visibility, + getDefaultViewerData, +} from "./models"; import { type SynchronizerContext, SynchronizerOrchestrator } from "./synchronizer"; function triggerUpdate(_prototype: any, _key: string, descriptor: PropertyDescriptor) { @@ -51,6 +59,7 @@ export class Workspace { visibilities: Record; viewers: Record; neuronGroups: Record; + emViewerSettings: EMViewerSettings; store: ReturnType; layoutManager: LayoutManager; @@ -68,18 +77,33 @@ export class Workspace { contexts?: Record, visibilities?: Record, neuronGroups?: Record, + emViewerSettings?: EMViewerSettings, + viewers?: Record, ) { this.id = id; this.name = name; this.activeDatasets = activeDatasets; this.availableNeurons = {}; this.activeNeurons = activeNeurons || new Set(); - this.viewers = { + this.viewers = viewers || { [ViewerType.Graph]: true, [ViewerType.ThreeD]: false, [ViewerType.EM]: false, }; this.neuronGroups = neuronGroups || {}; + // Set EM viewer settings + if (!emViewerSettings) { + const firstActiveDataset = Object.values(activeDatasets)?.[0]; + const [minSlice, maxSlice] = firstActiveDataset.emData.sliceRange; + const startSlice = Math.floor((maxSlice + minSlice) / 2); + this.emViewerSettings = { + showNeurons: true, + showSynapses: true, + startSlice: startSlice, + }; + } else { + this.emViewerSettings = { ...emViewerSettings }; + } const { layoutManager, store } = getLayoutManagerAndStore(id); this.layoutManager = layoutManager; @@ -289,4 +313,18 @@ export class Workspace { this.updateContext(updated); } + + // Those methods do not trigger updates as they are only here to store settings for the share function + // We don't want to trigger re-renderings of the full app + setEmviewerSlice(slice: number) { + this.emViewerSettings.startSlice = slice; + } + + emViewerShowNeurons(show: boolean) { + this.emViewerSettings.showNeurons = show; + } + + emViewerShowSynapses(show: boolean) { + this.emViewerSettings.showSynapses = show; + } }