Skip to content

Commit

Permalink
feat: added ordering of maps
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoalee committed Dec 12, 2024
1 parent 5e3d363 commit ff1e90e
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import NiiVueVisualizer from 'components/Visualizer/NiiVueVisualizer';
import { MetaAnalysisReturn, NeurovaultFile, ResultReturn } from 'neurosynth-compose-typescript-sdk';
import MetaAnalysisResultStatusAlert from './MetaAnalysisResultStatusAlert';
import useGetMetaAnalysisResultById from 'hooks/metaAnalyses/useGetMetaAnalysisResultById';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent';
import useGetNeurovaultImages, { INeurovault } from 'hooks/metaAnalyses/useGetNeurovault';
import DisplayParsedNiMareFile from './DisplayParsedNiMareFile';
import DisplayParsedNiMareFile, { NimareOutputs, parseNimareFileName } from './DisplayParsedNiMareFile';
import ImageIcon from '@mui/icons-material/Image';

const DisplayMetaAnalysisResults: React.FC<{
Expand Down Expand Up @@ -37,16 +37,46 @@ const DisplayMetaAnalysisResults: React.FC<{
setSelectedNeurovaultImage(neurovaultFiles[0]);
}, [neurovaultFiles]);

const sortedNeurovaultFiles = useMemo(() => {
const orderMap = new Map(NimareOutputs.map((output, index) => [output.type, index]));
const sorted = neurovaultFiles?.sort((a, b) => {
const filenameA = parseNimareFileName(a.name);
const filenameB = parseNimareFileName(b.name);

const longerFilename = filenameA.length > filenameB.length ? filenameA : filenameB;
for (let i = 0; i < longerFilename.length; i++) {
if (!filenameA[i]) return -1;
if (!filenameB[i]) return 1;

const orderA =
orderMap.get(filenameA[i].isValueType ? filenameA[i].value : filenameA[i].key) ?? Infinity;
const orderB =
orderMap.get(filenameB[i].isValueType ? filenameB[i].value : filenameB[i].key) ?? Infinity;
if (orderA === orderB) {
if (filenameA[i].value === filenameB[i].value) {
continue;
}
return filenameB[i].value.localeCompare(filenameA[i].value);
} else {
return orderB - orderA;
}
}
return 0;
});
return sorted?.reverse();
// if (props.metaAnalysis?.specification) // check for meta analysis mkdachi2
}, [neurovaultFiles]);

return (
<StateHandlerComponent
isLoading={isLoading || neurovaultFilesIsLoading}
isError={isError || neurovaultFilesIsError}
>
<MetaAnalysisResultStatusAlert metaAnalysis={props.metaAnalysis} metaAnalysisResult={data} />
<Box display="flex" sx={{ height: '100%', minHeight: '600px' }}>
<Box sx={{ width: '27%', maxHeight: '600px', overflowY: 'auto' }}>
<Box sx={{ width: '27%', maxHeight: '650px', overflowY: 'auto' }}>
<List sx={{ padding: 0 }}>
{(neurovaultFiles || []).map((neurovaultFile) => (
{(sortedNeurovaultFiles || []).map((neurovaultFile) => (
<ListItemButton
key={neurovaultFile.id}
onClick={() => setSelectedNeurovaultImage(neurovaultFile)}
Expand All @@ -67,7 +97,7 @@ const DisplayMetaAnalysisResults: React.FC<{
borderBottomRightRadius: '8',
}}
>
<Box sx={{ marginBottom: '1rem' }}>
<Box sx={{ margin: '1rem 0' }}>
<DisplayParsedNiMareFile nimareFileName={selectedNeurovaultImage?.name} />
</Box>
{selectedNeurovaultImage?.file ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,104 @@ import { HelpOutline } from '@mui/icons-material';
import { Box, Icon, Paper, Tooltip, Typography } from '@mui/material';
import { useMemo } from 'react';

const nimareOutputs = {
// must turn this into an array to make it orderable
export const NimareOutputs = [
// possible value types
z: 'Z-statistic',
t: 'T-statistic',
p: 'p-value',
logp: 'Negative base-ten logarithm of p-value',
chi2: 'Chi-squared value',
prob: 'Probability value',
stat: 'Test value of meta-analytic algorithm (e.g., ALE values for ALE, OF values for MKDA)',
est: 'Parameter estimate (IBMA only)',
se: 'Standard error of the parameter estimate (IBMA only)',
tau2: 'Estimated between-study variance (IBMA only)',
sigma2: 'Estimated within-study variance (IBMA only)',
label: 'Label map',
{ type: 'z', isValueType: true, description: 'Z-statistic' },
{ type: 't', isValueType: true, description: 'T-statistic' },
{ type: 'p', isValueType: true, description: 'p-value' },
{ type: 'logp', isValueType: true, description: 'Negative base-ten logarithm of p-value' },
{ type: 'chi2', isValueType: true, description: 'Chi-squared value' },
{ type: 'prob', isValueType: true, description: 'Probability value' },
{
type: 'stat',
isValueType: true,
description: 'Test value of meta-analytic algorithm (e.g., ALE values for ALE, OF values for MKDA)',
},
{ type: 'est', isValueType: true, description: 'Parameter estimate (IBMA only)' },
{ type: 'se', isValueType: true, description: 'Standard error of the parameter estimate (IBMA only)' },
{ type: 'tau2', isValueType: true, description: 'Estimated between-study variance (IBMA only)' },
{ type: 'sigma2', isValueType: true, description: 'Estimated within-study variance (IBMA only)' },
{ type: 'label', isValueType: true, description: 'Label map' },
// methods of meta analysis
desc: 'Description of the data type. Only used when multiple maps with the same data type are produced by the same method.',
level: 'Level of multiple comparisons correction. Either cluster or voxel.',
corr: 'Type of multiple comparisons correction. Either FWE (familywise error rate) or FDR (false discovery rate).',
method: 'Name of the method used for multiple comparisons correction (e.g., “montecarlo” for a Monte Carlo procedure).',
diag: 'Type of diagnostic. Either Jackknife (jackknife analysis) or FocusCounter (focus-count analysis).',
tab: 'Type of table. Either clust (clusters table) or counts (contribution table).',
tail: 'Sign of the tail for label maps. Either positive or negative.',
};
{
type: 'desc',
isValueType: false,
description:
'Description of the data type. Only used when multiple maps with the same data type are produced by the same method.',
},
{
type: 'level',
isValueType: false,
description: 'Level of multiple comparisons correction. Either cluster or voxel.',
},
{
type: 'corr',
isValueType: false,
description:
'Type of multiple comparisons correction. Either FWE (familywise error rate) or FDR (false discovery rate).',
},
{
type: 'method',
isValueType: false,
description:
'Name of the method used for multiple comparisons correction (e.g., “montecarlo” for a Monte Carlo procedure).',
},
{
type: 'diag',
isValueType: false,
description:
'Type of diagnostic. Either Jackknife (jackknife analysis) or FocusCounter (focus-count analysis).',
},
{
type: 'tab',
isValueType: false,
description: 'Type of table. Either clust (clusters table) or counts (contribution table).',
},
{ type: 'tail', isValueType: false, description: 'Sign of the tail for label maps. Either positive or negative.' },
];

const parseSegment = (segment: string): { key: string; keyDesc: string; value: string } => {
const [key, value] = segment.split('-');
if (value === undefined) {
// not a method
return {
key: 'type',
keyDesc: 'The type of data in the map.',
value: `${nimareOutputs[key as keyof typeof nimareOutputs]}`,
};
} else {
return {
key: key,
keyDesc: nimareOutputs[key as keyof typeof nimareOutputs],
value: value,
};
}
export const parseNimareFileName = (fileName: string | undefined) => {
if (!fileName) return [];
const segments = fileName.replace('.nii.gz', '').split('_');
return segments.map((segment) => {
const [key, value] = segment.split('-');
const nimareOutput = NimareOutputs.find((output) => output.type === key);
if (value === undefined) {
// not a method
return {
key: 'type',
isValueType: nimareOutput?.isValueType || false,
keyDesc: 'The type of data in the map.',
value: nimareOutput?.type || '',
};
} else {
return {
key: key,
isValueType: nimareOutput?.isValueType || false,
keyDesc: nimareOutput?.description || '',
value: value,
};
}
});
};

const DisplayParsedNiMareFile: React.FC<{ nimareFileName: string | undefined }> = (props) => {
const fileNameSegments = useMemo(() => {
if (!props.nimareFileName) return [];
const segments = props.nimareFileName.replace('.nii.gz', '').split('_');
return segments.map(parseSegment);
return parseNimareFileName(props.nimareFileName);
}, [props.nimareFileName]);

return (
<Box display="flex" flexWrap="wrap" justifyContent="space-between">
<Box display="flex" flexWrap="wrap">
{fileNameSegments.map((segment) => (
<Paper
key={segment.key}
component={Box}
variant="elevation"
display="flex"
flexDirection="column"
width="30%"
width="29%"
marginRight="2%"
marginBottom="0.5rem"
padding="0.5rem"
elevation={1}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { Alert } from '@mui/material';
import { getResultStatus } from 'helpers/MetaAnalysis.helpers';
import { MetaAnalysisReturn, ResultReturn } from 'neurosynth-compose-typescript-sdk';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';

const localStorageResultAlertKey = 'hide-meta-analysis-result-alert';

const MetaAnalysisResultStatusAlert: React.FC<{
metaAnalysis?: MetaAnalysisReturn;
metaAnalysisResult?: ResultReturn;
}> = ({ metaAnalysis, metaAnalysisResult }) => {
const shouldHide = !!localStorage.getItem(`${localStorageResultAlertKey}-${metaAnalysis?.id}`);
const [hideAlert, setHideAlert] = useState(shouldHide);

const resultStatus = useMemo(() => {
return getResultStatus(metaAnalysis, metaAnalysisResult);
}, [metaAnalysis, metaAnalysisResult]);

const [hideAlert, setHideAlert] = useState<boolean>();

useEffect(() => {
if (!resultStatus || !metaAnalysis?.id) return;
const shouldHide = !!localStorage.getItem(
`${localStorageResultAlertKey}-${resultStatus.severity}-${metaAnalysis?.id}`
);
setHideAlert(shouldHide);
}, [metaAnalysis?.id, resultStatus]);

if (hideAlert === undefined) return null;

return (
<>
{!hideAlert && (
Expand All @@ -24,11 +33,13 @@ const MetaAnalysisResultStatusAlert: React.FC<{
color={resultStatus.color}
onClose={() => {
setHideAlert(true);
localStorage.setItem(`${localStorageResultAlertKey}-${metaAnalysis?.id}`, 'true');
localStorage.setItem(
`${localStorageResultAlertKey}-${resultStatus?.severity}-${metaAnalysis?.id}`,
'true'
);
}}
sx={{
padding: '4px 10px',
marginBottom: '1rem',
alignItems: 'center',
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import NeurosynthAccordion from 'components/NeurosynthAccordion/NeurosynthAccord
import DisplayMetaAnalysisSpecification from './MetaAnalysisSpecification';
import EditSpecificationDialog from './EditSpecificationDialog';
import MetaAnalysisResultStatusAlert from './MetaAnalysisResultStatusAlert';
import { useUserCanEdit } from 'hooks';
import { useGetMetaAnalysisById, useUserCanEdit } from 'hooks';
import { useProjectUser } from 'pages/Project/store/ProjectStore';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
Expand All @@ -14,17 +14,19 @@ function NoMetaAnalysisResultDisplay() {
projectId: string;
metaAnalysisId: string;
}>();
const { data: metaAnalysis } = useGetMetaAnalysisById(metaAnalysisId);
const projectUser = useProjectUser();
const editsAllowed = useUserCanEdit(projectUser || undefined);
const [editSpecificationDialogIsOpen, setEditSpecificationDialogIsOpen] = useState(false);

return (
<>
<MetaAnalysisResultStatusAlert metaAnalysis={undefined} metaAnalysisResult={undefined} />
<MetaAnalysisResultStatusAlert metaAnalysis={metaAnalysis} metaAnalysisResult={undefined} />
<NeurosynthAccordion
elevation={0}
expandIconColor={editsAllowed ? 'secondary.main' : 'primary.main'}
sx={{
marginTop: '1rem',
border: '2px solid',
borderColor: editsAllowed ? 'secondary.main' : 'primary.main',
}}
Expand Down

0 comments on commit ff1e90e

Please sign in to comment.