Skip to content

Commit 675262a

Browse files
committed
feat: added download and snapshot
1 parent ff1e90e commit 675262a

File tree

19 files changed

+543
-293
lines changed

19 files changed

+543
-293
lines changed

compose/neurosynth-frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
<link rel="preload" as="image" href="/static/brain-analysis.png" type="image/png">
1919

20-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css" />
20+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css" crossorigin="anonymous" />
2121

2222
<link
2323
rel="stylesheet"

compose/neurosynth-frontend/src/components/Visualizer/NiiVueVisualizer.tsx

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import { Box, Checkbox, Slider, Typography } from '@mui/material';
1+
import { Box, Button, Checkbox, Link, Slider, Typography } from '@mui/material';
22
import { ChangeEvent, useEffect, useRef, useState } from 'react';
33
import { Niivue, SHOW_RENDER } from '@niivue/niivue';
4+
import { Download, OpenInNew } from '@mui/icons-material';
5+
import ImageIcon from '@mui/icons-material/Image';
46

57
let niivue: Niivue;
68

7-
const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
8-
const canvasRef = useRef(null);
9+
const NiiVueVisualizer: React.FC<{ file: string; filename: string; neurovaultLink?: string }> = ({
10+
file,
11+
filename,
12+
neurovaultLink,
13+
}) => {
14+
const canvasRef = useRef<HTMLCanvasElement>(null);
915
const [softThreshold, setSoftThresold] = useState(true);
1016
const [showNegatives, setShowNegatives] = useState(false);
1117
const [showCrosshairs, setShowCrosshairs] = useState(true);
@@ -84,7 +90,7 @@ const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
8490
opacity: 1,
8591
},
8692
{
87-
url: imageURL,
93+
url: file,
8894
// url: 'https://niivue.github.io/niivue/images/fslt.nii.gz',
8995
colorMap: 'warm',
9096
cal_min: 0, // default
@@ -128,12 +134,17 @@ const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
128134
niivue.setInterpolation(true);
129135
niivue.updateGLVolume();
130136
});
131-
}, [imageURL]);
137+
}, [file]);
138+
139+
const handleDownloadImage = () => {
140+
if (!niivue) return;
141+
niivue.saveScene(filename + '.png');
142+
};
132143

133144
return (
134145
<Box>
135146
<Box sx={{ marginBottom: '10px', display: 'flex', justifyContent: 'space-between' }}>
136-
<Box width="300px">
147+
<Box width="250px">
137148
<Typography gutterBottom={false}>Threshold</Typography>
138149
<Slider
139150
valueLabelDisplay="auto"
@@ -144,22 +155,64 @@ const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
144155
value={threshold.value}
145156
></Slider>
146157
</Box>
147-
<Box>
148-
<Typography gutterBottom={false}>Soft Threshold</Typography>
149-
<Checkbox checked={softThreshold} onChange={handleToggleSoftThreshold} />
150-
</Box>
151-
<Box>
152-
<Typography gutterBottom={false}>Show Negatives</Typography>
153-
<Checkbox checked={showNegatives} onChange={handleToggleNegatives} />
154-
</Box>
155-
<Box>
156-
<Typography gutterBottom={false}>Show Crosshairs</Typography>
157-
<Checkbox value={showCrosshairs} checked={showCrosshairs} onChange={handleToggleShowCrosshairs} />
158+
<Box display="flex">
159+
<Box width="100px">
160+
<Typography gutterBottom={false}>Soft Threshold</Typography>
161+
<Checkbox checked={softThreshold} onChange={handleToggleSoftThreshold} />
162+
</Box>
163+
<Box width="100px">
164+
<Typography gutterBottom={false}>Show Negatives</Typography>
165+
<Checkbox checked={showNegatives} onChange={handleToggleNegatives} />
166+
</Box>
167+
<Box width="100px">
168+
<Typography gutterBottom={false}>Show Crosshairs</Typography>
169+
<Checkbox
170+
value={showCrosshairs}
171+
checked={showCrosshairs}
172+
onChange={handleToggleShowCrosshairs}
173+
/>
174+
</Box>
158175
</Box>
159176
</Box>
160177
<Box sx={{ height: '300px' }}>
161178
<canvas ref={canvasRef} />
162179
</Box>
180+
<Box display="flex" alignItems="center" justifyContent="space-between">
181+
<Box>
182+
<Button
183+
size="small"
184+
variant="contained"
185+
endIcon={<Download />}
186+
href={file}
187+
sx={{ marginTop: '0.5rem', marginRight: '0.5rem' }}
188+
>
189+
Download NIfTI
190+
</Button>
191+
<Button
192+
size="small"
193+
variant="contained"
194+
onClick={handleDownloadImage}
195+
endIcon={<ImageIcon />}
196+
sx={{ marginTop: '0.5rem' }}
197+
>
198+
Download image
199+
</Button>
200+
</Box>
201+
{neurovaultLink && (
202+
<Button
203+
component={Link}
204+
sx={{ marginTop: '0.5rem' }}
205+
href={neurovaultLink.includes('/api') ? neurovaultLink.replace(/\/api/, '') : neurovaultLink}
206+
rel="noreferrer"
207+
size="small"
208+
target="_blank"
209+
disableElevation
210+
>
211+
Open in neurovault
212+
<OpenInNew sx={{ marginLeft: '4px' }} fontSize="small" />
213+
</Button>
214+
)}
215+
</Box>
163216
</Box>
164217
);
165218
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const MockNiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
2+
return (
3+
<div>
4+
<h1>Mocked NiiVue Visualizer</h1>
5+
<span data-testid="imageURL">{imageURL}</span>
6+
</div>
7+
);
8+
};
9+
10+
export default MockNiiVueVisualizer;

compose/neurosynth-frontend/src/hooks/__mocks__/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
mockAnnotations,
55
mockBaseStudy,
66
mockConditions,
7+
mockMetaAnalysisResult,
8+
mockNeurovault,
79
mockProject,
810
mockStudy,
911
mockStudysetNested,
@@ -179,6 +181,18 @@ const useIsMounted = () => {
179181

180182
const useUserCanEdit = vi.fn().mockReturnValue(true);
181183

184+
const useGetMetaAnalysisResultById = vi.fn().mockReturnValue({
185+
isLoading: false,
186+
isError: false,
187+
data: mockMetaAnalysisResult(),
188+
});
189+
190+
const useGetNeurovaultImages = vi.fn().mockReturnValue({
191+
isLoading: false,
192+
isError: false,
193+
data: mockNeurovault(),
194+
});
195+
182196
export {
183197
useCreateAnalysis,
184198
useCreateCondition,
@@ -207,4 +221,6 @@ export {
207221
useUpdateStudyset,
208222
useUserCanEdit,
209223
useGetProjectById,
224+
useGetMetaAnalysisResultById,
225+
useGetNeurovaultImages,
210226
};

compose/neurosynth-frontend/src/hooks/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import useCreateAlgorithmSpecification from './metaAnalyses/useCreateAlgorithmSp
99
import useGetMetaAnalysesByIds from './metaAnalyses/useGetMetaAnalysesByIds';
1010
import useGetMetaAnalysisById from './metaAnalyses/useGetMetaAnalysisById';
1111
import useGetMetaAnalysesPublic from './metaAnalyses/useGetMetaAnalysesPublic';
12+
import useGetMetaAnalysisResultById from './metaAnalyses/useGetMetaAnalysisResultById';
1213
import useGetAnnotationsByStudysetId from './analyses/useGetAnnotationsByStudysetId';
1314
import useCreatePoint from './analyses/useCreatePoint';
1415
import useUpdateStudy from './studies/useUpdateStudy';
@@ -38,6 +39,7 @@ import useGetFullText from './external/useGetFullText';
3839
import useUserCanEdit from './useUserCanEdit';
3940
import useGetBaseStudyById from './studies/useGetBaseStudyById';
4041
import useGetProjectById from './projects/useGetProjectById';
42+
import useGetNeurovaultImages from './metaAnalyses/useGetNeurovaultImages';
4143

4244
export {
4345
useGetCurationSummary,
@@ -51,6 +53,7 @@ export {
5153
useGetFullText,
5254
useUserCanEdit,
5355
useGetBaseStudyById,
56+
useGetNeurovaultImages,
5457
// STUDIES
5558
useGetBaseStudies,
5659
useGetStudyById,
@@ -61,6 +64,8 @@ export {
6164
useGetMetaAnalysesByIds,
6265
useGetMetaAnalysisById,
6366
useGetMetaAnalysesPublic,
67+
// META-ANALYSIS RESULTS
68+
useGetMetaAnalysisResultById,
6469
// STUDYSETS
6570
useGetStudysets,
6671
useGetStudysetById,

compose/neurosynth-frontend/src/hooks/metaAnalyses/useGetNeurovault.tsx

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import axios, { AxiosResponse } from 'axios';
2+
import { useQuery } from 'react-query';
3+
4+
export interface INeurovault {
5+
url: string | null;
6+
id: number | null;
7+
file: string | null;
8+
collection: string | null;
9+
collection_id: number | null;
10+
file_size: number | null;
11+
cognitive_paradigm_cogatlas: string | null;
12+
cognitive_paradigm_cogatlas_id: string | null;
13+
cognitive_contrast_cogatlas: string | null;
14+
cognitive_contrast_cogatlas_id: string | null;
15+
map_type: string | null;
16+
analysis_level: string | null;
17+
name: string | null;
18+
description: string | null;
19+
add_date: string | null;
20+
modify_date: string | null;
21+
is_valid: boolean;
22+
surface_left_file: string | null;
23+
surface_right_file: string | null;
24+
data_origin: string | null;
25+
target_template_image: string | null;
26+
subject_species: string | null;
27+
figure: string | null;
28+
handedness: string | null;
29+
age: string | null;
30+
gender: string | null;
31+
race: string | null;
32+
ethnicity: string | null;
33+
BMI: string | null;
34+
fat_percentage: string | null;
35+
waist_hip_ratio: string | null;
36+
mean_PDS_score: string | null;
37+
tanner_stage: string | null;
38+
days_since_menstruation: string | null;
39+
hours_since_last_meal: string | null;
40+
bis_bas_score: string | null;
41+
spsrq_score: string | null;
42+
bis11_score: string | null;
43+
thumbnail: string | null;
44+
reduced_representation: string | null;
45+
is_thresholded: boolean | null;
46+
perc_bad_voxels: number | null;
47+
not_mni: boolean | null;
48+
brain_coverage: number | null;
49+
perc_voxels_outside: number | null;
50+
number_of_subjects: string | null;
51+
modality: string | null;
52+
statistic_parameters: string | null;
53+
smoothness_fwhm: string | null;
54+
contrast_definition: string | null;
55+
contrast_definition_cogatlas: string | null;
56+
cognitive_paradigm_description_url: string | null;
57+
image_type: string | null;
58+
}
59+
60+
function useGetNeurovaultImages(neurovaultImages: string[]) {
61+
return useQuery({
62+
queryKey: ['neurovault-images', ...neurovaultImages],
63+
queryFn: async () => {
64+
const res = await Promise.all<AxiosResponse<INeurovault>>(neurovaultImages.map((url) => axios.get(url)));
65+
return res.map((x) => ({
66+
...x.data,
67+
file: (x.data.file || '').replace(/http/, 'https'), // without this, link will redirect but result in an error
68+
}));
69+
},
70+
enabled: neurovaultImages.length > 0,
71+
});
72+
}
73+
74+
export default useGetNeurovaultImages;

compose/neurosynth-frontend/src/pages/MetaAnalysis/MetaAnalysisPage.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { Box, Chip, Typography } from '@mui/material';
22
import NeurosynthBreadcrumbs from 'components/NeurosynthBreadcrumbs';
33
import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent';
44
import TextEdit from 'components/TextEdit/TextEdit';
5-
import { useGetMetaAnalysisById } from 'hooks';
6-
import useGetMetaAnalysisResultById from 'hooks/metaAnalyses/useGetMetaAnalysisResultById';
5+
import { useGetMetaAnalysisById, useGetMetaAnalysisResultById } from 'hooks';
76
import useGetSpecificationById from 'hooks/metaAnalyses/useGetSpecificationById';
87
import useUpdateMetaAnalysis from 'hooks/metaAnalyses/useUpdateMetaAnalysis';
98
import useUserCanEdit from 'hooks/useUserCanEdit';

0 commit comments

Comments
 (0)