@vitessce/neuroglancer 3.6.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-DmHg0YSX.js → index-C1Dm5JHD.js} +17445 -145
- package/dist/{index-DVtsoLdc.js → index-CfzR0P6j.js} +1 -1
- package/dist/index.js +1 -1
- package/dist-tsc/Neuroglancer.d.ts +14 -1
- package/dist-tsc/Neuroglancer.d.ts.map +1 -1
- package/dist-tsc/Neuroglancer.js +120 -16
- package/dist-tsc/NeuroglancerSubscriber.d.ts.map +1 -1
- package/dist-tsc/NeuroglancerSubscriber.js +120 -5
- package/dist-tsc/styles.js +3 -3
- package/package.json +9 -5
- package/src/Neuroglancer.js +157 -34
- package/src/NeuroglancerSubscriber.js +201 -5
- package/src/styles.js +3 -3
|
@@ -1,21 +1,211 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
3
|
import {
|
|
3
4
|
TitleInfo,
|
|
5
|
+
useCoordination,
|
|
6
|
+
useObsSetsData,
|
|
7
|
+
useLoaders,
|
|
8
|
+
useObsEmbeddingData,
|
|
4
9
|
} from '@vitessce/vit-s';
|
|
5
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ViewHelpMapping,
|
|
12
|
+
ViewType,
|
|
13
|
+
COMPONENT_COORDINATION_TYPES,
|
|
14
|
+
} from '@vitessce/constants-internal';
|
|
15
|
+
import { mergeObsSets, getCellColors, setObsSelection } from '@vitessce/sets-utils';
|
|
6
16
|
import { Neuroglancer } from './Neuroglancer.js';
|
|
17
|
+
import { useStyles } from './styles.js';
|
|
18
|
+
|
|
19
|
+
const NEUROGLANCER_ZOOM_BASIS = 16;
|
|
20
|
+
|
|
21
|
+
function mapVitessceToNeuroglancer(zoom) {
|
|
22
|
+
return NEUROGLANCER_ZOOM_BASIS * (2 ** -zoom);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mapNeuroglancerToVitessce(projectionScale) {
|
|
26
|
+
return -Math.log2(projectionScale / NEUROGLANCER_ZOOM_BASIS);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function quaternionToEuler([x, y, z, w]) {
|
|
30
|
+
// X-axis rotation (Roll)
|
|
31
|
+
const thetaX = Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y));
|
|
32
|
+
|
|
33
|
+
// Y-axis rotation (Pitch)
|
|
34
|
+
const sinp = 2 * (w * y - z * x);
|
|
35
|
+
const thetaY = Math.abs(sinp) >= 1 ? Math.sign(sinp) * (Math.PI / 2) : Math.asin(sinp);
|
|
36
|
+
|
|
37
|
+
// Convert to degrees as Vitessce expects degrees?
|
|
38
|
+
return [thetaX * (180 / Math.PI), thetaY * (180 / Math.PI)];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function eulerToQuaternion(thetaX, thetaY) {
|
|
43
|
+
// Convert Euler angles (X, Y rotations) to quaternion
|
|
44
|
+
const halfThetaX = thetaX / 2;
|
|
45
|
+
const halfThetaY = thetaY / 2;
|
|
46
|
+
|
|
47
|
+
const sinX = Math.sin(halfThetaX);
|
|
48
|
+
const cosX = Math.cos(halfThetaX);
|
|
49
|
+
const sinY = Math.sin(halfThetaY);
|
|
50
|
+
const cosY = Math.cos(halfThetaY);
|
|
51
|
+
|
|
52
|
+
return [
|
|
53
|
+
sinX * cosY,
|
|
54
|
+
cosX * sinY,
|
|
55
|
+
sinX * sinY,
|
|
56
|
+
cosX * cosY,
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeQuaternion(q) {
|
|
61
|
+
const length = Math.sqrt((q[0] ** 2) + (q[1] ** 2) + (q[2] ** 2) + (q[3] ** 2));
|
|
62
|
+
return q.map(value => value / length);
|
|
63
|
+
}
|
|
7
64
|
|
|
8
65
|
export function NeuroglancerSubscriber(props) {
|
|
9
66
|
const {
|
|
67
|
+
coordinationScopes,
|
|
10
68
|
closeButtonVisible,
|
|
11
69
|
downloadButtonVisible,
|
|
12
70
|
removeGridComponent,
|
|
13
71
|
theme,
|
|
14
72
|
title = 'Neuroglancer',
|
|
15
|
-
viewerState: viewerStateInitial = null,
|
|
16
73
|
helpText = ViewHelpMapping.NEUROGLANCER,
|
|
74
|
+
viewerState: initialViewerState,
|
|
17
75
|
} = props;
|
|
18
76
|
|
|
77
|
+
const [{
|
|
78
|
+
dataset,
|
|
79
|
+
obsType,
|
|
80
|
+
spatialZoom,
|
|
81
|
+
spatialTargetX,
|
|
82
|
+
spatialTargetY,
|
|
83
|
+
spatialRotationX,
|
|
84
|
+
spatialRotationY,
|
|
85
|
+
// spatialRotationZ,
|
|
86
|
+
// spatialRotationOrbit,
|
|
87
|
+
// spatialOrbitAxis,
|
|
88
|
+
embeddingType: mapping,
|
|
89
|
+
obsSetSelection: cellSetSelection,
|
|
90
|
+
additionalObsSets: additionalCellSets,
|
|
91
|
+
obsSetColor: cellSetColor,
|
|
92
|
+
}, {
|
|
93
|
+
setAdditionalObsSets: setAdditionalCellSets,
|
|
94
|
+
setObsSetColor: setCellSetColor,
|
|
95
|
+
setObsColorEncoding: setCellColorEncoding,
|
|
96
|
+
setObsSetSelection: setCellSetSelection,
|
|
97
|
+
setObsHighlight: setCellHighlight,
|
|
98
|
+
setSpatialTargetX: setTargetX,
|
|
99
|
+
setSpatialTargetY: setTargetY,
|
|
100
|
+
setSpatialRotationX: setRotationX,
|
|
101
|
+
setSpatialRotationY: setRotationY,
|
|
102
|
+
// setSpatialRotationZ: setRotationZ,
|
|
103
|
+
// setSpatialRotationOrbit: setRotationOrbit,
|
|
104
|
+
|
|
105
|
+
setSpatialZoom: setZoom,
|
|
106
|
+
}] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.NEUROGLANCER], coordinationScopes);
|
|
107
|
+
|
|
108
|
+
const { classes } = useStyles();
|
|
109
|
+
const loaders = useLoaders();
|
|
110
|
+
|
|
111
|
+
const [{ obsSets: cellSets }] = useObsSetsData(
|
|
112
|
+
loaders, dataset, false,
|
|
113
|
+
{ setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor },
|
|
114
|
+
{ cellSetSelection, obsSetColor: cellSetColor },
|
|
115
|
+
{ obsType },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const [{ obsIndex }] = useObsEmbeddingData(
|
|
119
|
+
loaders, dataset, true, {}, {},
|
|
120
|
+
{ obsType, embeddingType: mapping },
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const handleStateUpdate = useCallback((newState) => {
|
|
124
|
+
const { projectionScale, projectionOrientation, position } = newState;
|
|
125
|
+
setZoom(mapNeuroglancerToVitessce(projectionScale));
|
|
126
|
+
const vitessceEularMapping = quaternionToEuler(projectionOrientation);
|
|
127
|
+
|
|
128
|
+
// TODO: support z rotation on SpatialView?
|
|
129
|
+
setRotationX(vitessceEularMapping[0]);
|
|
130
|
+
setRotationY(vitessceEularMapping[1]);
|
|
131
|
+
|
|
132
|
+
// Note: To pan in Neuroglancer, use shift+leftKey+drag
|
|
133
|
+
setTargetX(position[0]);
|
|
134
|
+
setTargetY(position[1]);
|
|
135
|
+
}, [setZoom, setTargetX, setTargetY, setRotationX, setRotationY]);
|
|
136
|
+
|
|
137
|
+
const onSegmentClick = useCallback((value) => {
|
|
138
|
+
if (value) {
|
|
139
|
+
const selectedCellIds = [String(value)];
|
|
140
|
+
setObsSelection(
|
|
141
|
+
selectedCellIds, additionalCellSets, cellSetColor,
|
|
142
|
+
setCellSetSelection, setAdditionalCellSets, setCellSetColor,
|
|
143
|
+
setCellColorEncoding,
|
|
144
|
+
'Selection ',
|
|
145
|
+
`: based on selected segments ${value}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}, [additionalCellSets, cellSetColor, setAdditionalCellSets,
|
|
149
|
+
setCellColorEncoding, setCellSetColor, setCellSetSelection,
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
const mergedCellSets = useMemo(() => mergeObsSets(
|
|
153
|
+
cellSets, additionalCellSets,
|
|
154
|
+
), [cellSets, additionalCellSets]);
|
|
155
|
+
|
|
156
|
+
const cellColors = useMemo(() => getCellColors({
|
|
157
|
+
cellSets: mergedCellSets,
|
|
158
|
+
cellSetSelection,
|
|
159
|
+
cellSetColor,
|
|
160
|
+
obsIndex,
|
|
161
|
+
theme,
|
|
162
|
+
}), [mergedCellSets, theme,
|
|
163
|
+
cellSetColor, cellSetSelection, obsIndex]);
|
|
164
|
+
|
|
165
|
+
const rgbToHex = useCallback(rgb => (typeof rgb === 'string' ? rgb
|
|
166
|
+
: `#${rgb.map(c => c.toString(16).padStart(2, '0')).join('')}`), []);
|
|
167
|
+
|
|
168
|
+
const cellColorMapping = useMemo(() => {
|
|
169
|
+
const colorCellMapping = {};
|
|
170
|
+
cellColors.forEach((color, cell) => {
|
|
171
|
+
colorCellMapping[cell] = rgbToHex(color);
|
|
172
|
+
});
|
|
173
|
+
return colorCellMapping;
|
|
174
|
+
}, [cellColors, rgbToHex]);
|
|
175
|
+
|
|
176
|
+
const derivedViewerState = useMemo(() => ({
|
|
177
|
+
...initialViewerState,
|
|
178
|
+
layers: initialViewerState.layers.map((layer, index) => (index === 0
|
|
179
|
+
? {
|
|
180
|
+
...layer,
|
|
181
|
+
segments: Object.keys(cellColorMapping).map(String),
|
|
182
|
+
segmentColors: cellColorMapping,
|
|
183
|
+
}
|
|
184
|
+
: layer)),
|
|
185
|
+
}), [cellColorMapping, initialViewerState]);
|
|
186
|
+
|
|
187
|
+
const derivedViewerState2 = useMemo(() => {
|
|
188
|
+
if (typeof spatialZoom === 'number' && typeof spatialTargetX === 'number') {
|
|
189
|
+
const projectionScale = mapVitessceToNeuroglancer(spatialZoom);
|
|
190
|
+
const position = [spatialTargetX, spatialTargetY, derivedViewerState.position[2]];
|
|
191
|
+
const projectionOrientation = normalizeQuaternion(
|
|
192
|
+
eulerToQuaternion(spatialRotationX, spatialRotationY),
|
|
193
|
+
);
|
|
194
|
+
return {
|
|
195
|
+
...derivedViewerState,
|
|
196
|
+
projectionScale,
|
|
197
|
+
position,
|
|
198
|
+
projectionOrientation,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return derivedViewerState;
|
|
202
|
+
}, [derivedViewerState, spatialZoom, spatialTargetX,
|
|
203
|
+
spatialTargetY, spatialRotationX, spatialRotationY]);
|
|
204
|
+
|
|
205
|
+
const onSegmentHighlight = useCallback((obsId) => {
|
|
206
|
+
setCellHighlight(String(obsId));
|
|
207
|
+
}, [obsIndex, setCellHighlight]);
|
|
208
|
+
|
|
19
209
|
return (
|
|
20
210
|
<TitleInfo
|
|
21
211
|
title={title}
|
|
@@ -26,9 +216,15 @@ export function NeuroglancerSubscriber(props) {
|
|
|
26
216
|
downloadButtonVisible={downloadButtonVisible}
|
|
27
217
|
removeGridComponent={removeGridComponent}
|
|
28
218
|
isReady
|
|
219
|
+
withPadding={false}
|
|
29
220
|
>
|
|
30
|
-
|
|
221
|
+
<Neuroglancer
|
|
222
|
+
classes={classes}
|
|
223
|
+
onSegmentClick={onSegmentClick}
|
|
224
|
+
onSelectHoveredCoords={onSegmentHighlight}
|
|
225
|
+
viewerState={derivedViewerState2}
|
|
226
|
+
setViewerState={handleStateUpdate}
|
|
227
|
+
/>
|
|
31
228
|
</TitleInfo>
|
|
32
|
-
|
|
33
229
|
);
|
|
34
230
|
}
|
package/src/styles.js
CHANGED
|
@@ -1116,7 +1116,7 @@ const globalNeuroglancerStyles = {
|
|
|
1116
1116
|
overflowY: 'scroll',
|
|
1117
1117
|
wordWrap: 'break-word',
|
|
1118
1118
|
},
|
|
1119
|
-
'.neuroglancer-multiline-autocomplete-completion:nth-
|
|
1119
|
+
'.neuroglancer-multiline-autocomplete-completion:nth-of-type(even):not(.neuroglancer-multiline-autocomplete-completion-active)': {
|
|
1120
1120
|
backgroundColor: '#2b2b2b',
|
|
1121
1121
|
},
|
|
1122
1122
|
'.neuroglancer-multiline-autocomplete-completion:hover': {
|
|
@@ -1418,7 +1418,7 @@ const globalNeuroglancerStyles = {
|
|
|
1418
1418
|
backgroundClip: 'border-box',
|
|
1419
1419
|
backgroundColor: '#8080ff80',
|
|
1420
1420
|
},
|
|
1421
|
-
'.neuroglancer-stack-layout-drop-placeholder:first-
|
|
1421
|
+
'.neuroglancer-stack-layout-drop-placeholder:first-of-type, .neuroglancer-stack-layout-drop-placeholder:last-of-type': {
|
|
1422
1422
|
display: 'none',
|
|
1423
1423
|
},
|
|
1424
1424
|
'.neuroglancer-panel': { flex: 1 },
|
|
@@ -1564,7 +1564,7 @@ const globalNeuroglancerStyles = {
|
|
|
1564
1564
|
alignItems: 'center',
|
|
1565
1565
|
},
|
|
1566
1566
|
'.neuroglancer-position-widget input:disabled': { pointerEvents: 'none' },
|
|
1567
|
-
'.neuroglancer-position-widget .neuroglancer-copy-button:first-
|
|
1567
|
+
'.neuroglancer-position-widget .neuroglancer-copy-button:first-of-type': {
|
|
1568
1568
|
display: 'none',
|
|
1569
1569
|
},
|
|
1570
1570
|
'.neuroglancer-position-dimension-coordinate, .neuroglancer-position-dimension-name, .neuroglancer-position-dimension-scale': {
|