Skip to content

Commit 5e3d363

Browse files
committed
feat: added nimare file parsing
1 parent 7e11b5e commit 5e3d363

File tree

3 files changed

+145
-24
lines changed

3 files changed

+145
-24
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ let niivue: Niivue;
66

77
const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
88
const canvasRef = useRef(null);
9-
const [softThreshold, setSoftThresold] = useState(false);
9+
const [softThreshold, setSoftThresold] = useState(true);
1010
const [showNegatives, setShowNegatives] = useState(false);
1111
const [showCrosshairs, setShowCrosshairs] = useState(true);
1212
const [threshold, setThreshold] = useState<{
@@ -104,8 +104,8 @@ const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
104104

105105
niivue.attachToCanvas(canvasRef.current);
106106
niivue.addVolumesFromUrl(volumes).then(() => {
107-
niivue.volumes[1].alphaThreshold = 0;
108-
niivue.overlayOutlineWidth = 0;
107+
niivue.overlayOutlineWidth = 2;
108+
niivue.volumes[1].alphaThreshold = 5;
109109

110110
niivue.volumes[0].colorbarVisible = false;
111111
niivue.volumes[1].colormapNegative = '';
@@ -119,7 +119,7 @@ const NiiVueVisualizer: React.FC<{ imageURL: string }> = ({ imageURL }) => {
119119

120120
setThreshold({
121121
min: 0,
122-
max: largestAbsoluteValue + 0.1,
122+
max: Math.round((largestAbsoluteValue + 0.1) * 100) / 100,
123123
value: startingValue,
124124
});
125125
niivue.volumes[1].cal_min = startingValue;

compose/neurosynth-frontend/src/pages/MetaAnalysis/components/DisplayMetaAnalysisResults.tsx

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { OpenInNew } from '@mui/icons-material';
1+
import { Download, OpenInNew } from '@mui/icons-material';
22
import { Box, Button, Link, List, ListItemButton, ListItemText, Typography } from '@mui/material';
33
import NiiVueVisualizer from 'components/Visualizer/NiiVueVisualizer';
44
import { MetaAnalysisReturn, NeurovaultFile, ResultReturn } from 'neurosynth-compose-typescript-sdk';
55
import MetaAnalysisResultStatusAlert from './MetaAnalysisResultStatusAlert';
66
import useGetMetaAnalysisResultById from 'hooks/metaAnalyses/useGetMetaAnalysisResultById';
7-
import { useState } from 'react';
7+
import { useEffect, useState } from 'react';
88
import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent';
99
import useGetNeurovaultImages, { INeurovault } from 'hooks/metaAnalyses/useGetNeurovault';
10+
import DisplayParsedNiMareFile from './DisplayParsedNiMareFile';
11+
import ImageIcon from '@mui/icons-material/Image';
1012

1113
const DisplayMetaAnalysisResults: React.FC<{
1214
metaAnalysis: MetaAnalysisReturn | undefined;
@@ -28,17 +30,21 @@ const DisplayMetaAnalysisResults: React.FC<{
2830
isLoading: neurovaultFilesIsLoading,
2931
isError: neurovaultFilesIsError,
3032
} = useGetNeurovaultImages(neurovaultFileURLs);
31-
console.log({ neurovaultFiles });
3233
const [selectedNeurovaultImage, setSelectedNeurovaultImage] = useState<INeurovault>();
3334

35+
useEffect(() => {
36+
if (!neurovaultFiles) return;
37+
setSelectedNeurovaultImage(neurovaultFiles[0]);
38+
}, [neurovaultFiles]);
39+
3440
return (
3541
<StateHandlerComponent
3642
isLoading={isLoading || neurovaultFilesIsLoading}
3743
isError={isError || neurovaultFilesIsError}
3844
>
3945
<MetaAnalysisResultStatusAlert metaAnalysis={props.metaAnalysis} metaAnalysisResult={data} />
40-
<Box display="flex" sx={{ height: '100%' }}>
41-
<Box sx={{ width: '27%', height: '100%', maxHeight: '448px', overflowY: 'auto' }}>
46+
<Box display="flex" sx={{ height: '100%', minHeight: '600px' }}>
47+
<Box sx={{ width: '27%', maxHeight: '600px', overflowY: 'auto' }}>
4248
<List sx={{ padding: 0 }}>
4349
{(neurovaultFiles || []).map((neurovaultFile) => (
4450
<ListItemButton
@@ -54,32 +60,62 @@ const DisplayMetaAnalysisResults: React.FC<{
5460
<Box
5561
sx={{
5662
width: '73%',
57-
backgroundColor: '#f0f0f0',
63+
// backgroundColor: '#0000000a',
5864
padding: '1.5rem',
65+
paddingTop: 0,
5966
borderTopRightRadius: '0px',
6067
borderBottomRightRadius: '8',
61-
maxHeight: '448px',
6268
}}
6369
>
70+
<Box sx={{ marginBottom: '1rem' }}>
71+
<DisplayParsedNiMareFile nimareFileName={selectedNeurovaultImage?.name} />
72+
</Box>
6473
{selectedNeurovaultImage?.file ? (
65-
<NiiVueVisualizer imageURL={selectedNeurovaultImage.file} />
74+
<>
75+
<NiiVueVisualizer imageURL={selectedNeurovaultImage.file} />
76+
<Box display="flex" alignItems="center" justifyContent="space-between">
77+
<Button
78+
component={Link}
79+
sx={{ marginTop: '0.5rem' }}
80+
href={
81+
neurovaultLink.includes('/api')
82+
? neurovaultLink.replace(/\/api/, '')
83+
: neurovaultLink
84+
}
85+
variant="contained"
86+
rel="noreferrer"
87+
size="small"
88+
target="_blank"
89+
disableElevation
90+
>
91+
Open in neurovault
92+
<OpenInNew sx={{ marginLeft: '4px' }} fontSize="small" />
93+
</Button>
94+
<Box>
95+
<Button
96+
size="small"
97+
variant="contained"
98+
endIcon={<Download />}
99+
sx={{ marginTop: '0.5rem', marginRight: '0.5rem' }}
100+
>
101+
Download nifti
102+
</Button>
103+
<Button
104+
size="small"
105+
variant="contained"
106+
endIcon={<ImageIcon />}
107+
sx={{ marginTop: '0.5rem' }}
108+
>
109+
Download image
110+
</Button>
111+
</Box>
112+
</Box>
113+
</>
66114
) : (
67115
<Typography color="warning.dark">No image selected</Typography>
68116
)}
69117
</Box>
70118
</Box>
71-
<Button
72-
component={Link}
73-
sx={{ marginTop: '1rem', marginBottom: '2rem' }}
74-
href={neurovaultLink.includes('/api') ? neurovaultLink.replace(/\/api/, '') : neurovaultLink}
75-
variant="contained"
76-
rel="noreferrer"
77-
target="_blank"
78-
disableElevation
79-
>
80-
Open in neurovault
81-
<OpenInNew sx={{ marginLeft: 'px' }} />
82-
</Button>
83119
</StateHandlerComponent>
84120
);
85121
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { HelpOutline } from '@mui/icons-material';
2+
import { Box, Icon, Paper, Tooltip, Typography } from '@mui/material';
3+
import { useMemo } from 'react';
4+
5+
const nimareOutputs = {
6+
// possible value types
7+
z: 'Z-statistic',
8+
t: 'T-statistic',
9+
p: 'p-value',
10+
logp: 'Negative base-ten logarithm of p-value',
11+
chi2: 'Chi-squared value',
12+
prob: 'Probability value',
13+
stat: 'Test value of meta-analytic algorithm (e.g., ALE values for ALE, OF values for MKDA)',
14+
est: 'Parameter estimate (IBMA only)',
15+
se: 'Standard error of the parameter estimate (IBMA only)',
16+
tau2: 'Estimated between-study variance (IBMA only)',
17+
sigma2: 'Estimated within-study variance (IBMA only)',
18+
label: 'Label map',
19+
// methods of meta analysis
20+
desc: 'Description of the data type. Only used when multiple maps with the same data type are produced by the same method.',
21+
level: 'Level of multiple comparisons correction. Either cluster or voxel.',
22+
corr: 'Type of multiple comparisons correction. Either FWE (familywise error rate) or FDR (false discovery rate).',
23+
method: 'Name of the method used for multiple comparisons correction (e.g., “montecarlo” for a Monte Carlo procedure).',
24+
diag: 'Type of diagnostic. Either Jackknife (jackknife analysis) or FocusCounter (focus-count analysis).',
25+
tab: 'Type of table. Either clust (clusters table) or counts (contribution table).',
26+
tail: 'Sign of the tail for label maps. Either positive or negative.',
27+
};
28+
29+
const parseSegment = (segment: string): { key: string; keyDesc: string; value: string } => {
30+
const [key, value] = segment.split('-');
31+
if (value === undefined) {
32+
// not a method
33+
return {
34+
key: 'type',
35+
keyDesc: 'The type of data in the map.',
36+
value: `${nimareOutputs[key as keyof typeof nimareOutputs]}`,
37+
};
38+
} else {
39+
return {
40+
key: key,
41+
keyDesc: nimareOutputs[key as keyof typeof nimareOutputs],
42+
value: value,
43+
};
44+
}
45+
};
46+
47+
const DisplayParsedNiMareFile: React.FC<{ nimareFileName: string | undefined }> = (props) => {
48+
const fileNameSegments = useMemo(() => {
49+
if (!props.nimareFileName) return [];
50+
const segments = props.nimareFileName.replace('.nii.gz', '').split('_');
51+
return segments.map(parseSegment);
52+
}, [props.nimareFileName]);
53+
54+
return (
55+
<Box display="flex" flexWrap="wrap" justifyContent="space-between">
56+
{fileNameSegments.map((segment) => (
57+
<Paper
58+
key={segment.key}
59+
component={Box}
60+
variant="elevation"
61+
display="flex"
62+
flexDirection="column"
63+
width="30%"
64+
marginBottom="0.5rem"
65+
padding="0.5rem"
66+
elevation={1}
67+
>
68+
<Box display="flex" alignItems="center">
69+
<Typography color="muted.main" gutterBottom={false} variant="caption" marginRight="4px">
70+
{segment.key}
71+
</Typography>
72+
<Tooltip title={<Typography variant="caption">{segment.keyDesc}</Typography>} placement="top">
73+
<Icon fontSize="small">
74+
<HelpOutline fontSize="small" sx={{ color: 'muted.main' }} />
75+
</Icon>
76+
</Tooltip>
77+
</Box>
78+
<Typography variant="body2">{segment.value}</Typography>
79+
</Paper>
80+
))}
81+
</Box>
82+
);
83+
};
84+
85+
export default DisplayParsedNiMareFile;

0 commit comments

Comments
 (0)