Tedana 26.0.1 T2* maps with extreme outliers compared to version 24.0.1

Summary of what happened:

I have a resting state fMRI dataset that I previously processed via tedana 24.0.1; it processed with no problems, but I was having trouble getting an appropriate number of ICA components, potentially due to the high acceleration of the sequence we used. I recently tried running the same data through tedana 26.0.1 using the “robustica” option, and it completed with no errors. However, when reviewing the summary HTML file, the T2* histogram is either blank or only shows a few values. When inspecting the T2starmap.nii.gz, there appear to be a few very extreme voxel values (with values anywhere from tens to hundreds), and most of the brain values consist of a limited range of multiples of each other.

For example, on a scan with a max T2* value of 9, most of the voxels had values of 0.036, 0.072, 0.11, 0.14, etc. It seems like the smallest voxel difference is some fraction of the max value, so if the max value is extremely high, most of the voxels are forced to a few or just 1 value. This was not an issue with 24.0.1, but I’m not sure if it’s a software issue or an issue with my pipeline setup. I can provide further code and logs as needed. Any help would be appreciated!

Command used (and if a helper script was used, a link to the helper script or the command generated):

# A bash script activates a conda environment, 
#    runs a python script which sets up variables, 
#    removes fMRIPrep-identified dummy volumes from the BOLD, 
#    and then runs:

# For tedana 26.0.1
tedana_workflow(
    data=shortened_files,
    tes=ECHO_TIMES,
    out_dir=tedana_out_dir,
    mask=mask_file,
    prefix=prefix,
    tedpca=100,
    fittype="curvefit",
    ica_method="robustica",
    n_robust_runs=50,
    tedort=True,
    n_threads=2
)

# For tedana 24.0.1
tedana_workflow(
    data=shortened_files,
    tes=ECHO_TIMES,
    out_dir=tedana_out_dir,
    mask=mask_file,
    prefix=prefix,
    tedpca=75,
    fittype="curvefit",
    tedort=True
)

Version:

tedana 26.0.1 compared with 24.0.1

Environment (Docker, Singularity / Apptainer, custom installation):

Was installed via these commands on an Ubuntu 22.04.5 linux server:

conda create -n tedana_26.0.1 -c conda-forge python=3.12 pip
conda activate tedana_26.0.1
pip install tedana

Data formatted according to a validatable standard? Please provide the output of the validator:

Tedana input data came from fMRIPrep 23.0.2, which uses BIDS

Screenshots / relevant information:


Additional information: the BOLD data is from a multi-echo sequence with 2.5 mm isotropic voxels, TR = 1670 ms, TEs = 15.60 ms, 38.20 ms, 60.80 ms, 83.40 ms, GRAPPA acceleration factor 2, multiband factor 4.

The data was originally run using fMRIPrep 23.2.3 into tedana 24.0.1, and there were no T2* issues. In order to match versions with another analysis, I reran the data through fMRIPrep 23.0.2 into tedana 26.0.1. Both fMRIprep calls used the same commands, and the output appears similar, but this could have introduced some change. The T2* maps created by both versions of fMRIPrep look normal.

Example screenshot of a T2* map created with tedana 24.0.1 with a max voxel value under 1:


Example screenshot of the same data’s T2* map created with tedana 26.0.1 with a max voxel value of 9 and a limited range of low values.


Thanks for the help!

Sincerely,
Keith G. Jones

Can you share the log tsv files from each run? They are named something like tedana_<YYYY>-<MM>-<DD>T<…>.tsv.

For T2starmap.nii.gz, the values are in seconds, so gray matter T2* estimates should typically be less than 0.1. This was true in both v24 and v26 but I think we added some checks at some point to treat the inputted echo times as seconds and I forget if that interacted with how the T2* maps were saves if echo times were entered as milliseconds. Either way, I’d expect the T2* maps to be the same except for possibly a scaling issue and it’s unclear why the values are implausibly bigger in v26.0.01, so there’s likely something else happening here.

We’ve been seeing some quirks across nilearn versions. Are you using the same nilearn version in both runs? An outputted file dataset_description.json should include the version numbers of key python libraries used in each execution.

Semi-related, an implausibly high or low number of components is a known problem. I’m fairly sure it’s an issue with all commonly used ICA component estimation methods (will be part of one of my OHBM2026 posters). As you note, I suspect it is partially due to modern acceleration methods, but I don’t have specific evidence for that. That said, I’ve also tests and found that, while the specific results will change, a wide range of component estimates gives reasonable denoising results. If, for your acquistion parameters, “reasonable” estimates are between X & Y components, I suggest giving a fixed component value near the higher end of that range (i.e. --tedpca 90 for 90 components). You can also use robustica but the initial number of components does affect what robustICA does and you want to start with a plausible number.

Dan

tedana also installs a program called t2smap. It should be running a nearly identical version of the t2*map estimate as the full tedana program. Could you run the following with the two versions of tedana to see if there are differences in the outputted maps?

ts2map_workflow(
data=shortened_files,
tes=ECHO_TIMES,
out_dir=t2s_out_dir,
mask=mask_file,
prefix=prefix
fittype=”curvefit”
)

Thanks for the quick replies! Here are the log files and dataset descriptions converted to .txt so I could upload directly:

Run 1: fMRIPrep 23.2.3 to tedana 24.0.1
tedana_2024-08-11T204141.txt (24.2 KB)
sub-OPT0047_ses-01_task-REST_dir-AP_dataset_description.txt (2.6 KB)

Run 2: fMRIPrep 23.0.2 to tedana 26.0.1:
tedana_2026-03-23T175733.txt (26.7 KB)
sub-OPT0047_ses-01_task-REST_dir-AP_dataset_description.txt (2.9 KB)

All of the python library versions, and the python version itself, are different, as the runs were processed in different environments. I’ll work on rerunning them with identical libraries to see if that changes anything, and I’ll also run t2smap_workflow. Thanks for the help!

Sincerely,
Keith G. Jones

Update: in an attempt to remove as many variables as possible, I reran the exact same dataset, with the same preprocessing, through tedana 24.0.1 and 26.0.1, with the same commands:

t2smap_workflow(
    data=shortened_files,
    tes=ECHO_TIMES,
    out_dir=tedana_out_dir_t2s,
    mask=mask_file,
    prefix=prefix,
    fittype="curvefit"
)

tedana_workflow(
    data=shortened_files,
    tes=ECHO_TIMES,
    out_dir=tedana_out_dir,
    mask=mask_file,
    prefix=prefix,
    fittype="curvefit",
)

The conda environments were identical other than nilearn and numpy versions, and a package called lxml, as tedana 24.0.1 wouldn’t run with newer versions. I confirmed that they were identical by saving a list of package versions immediately before running the python commands. Tedana 24.0.1 did end up throwing a pandas error later in the processing, presumably due to using the newer version, but it at least completed the T2* map. Here’s a summary of the results:

Run 01: dataset through fMRIPrep 23.0.2 to tedana 24.0.1

  • tedana 24.0.1
  • nilearn 0.10.4
  • numpy 1.26.4
  • lxml 6.0.2

Run 01 results:

  • T2* map from t2smap_workflow looks good (same as post above)
  • T2* map from tedana_workflow looks good

Run 02: dataset through fMRIPrep 23.0.2 to tedana 26.0.1

  • tedana 26.0.1
  • nilearn 0.13.1
  • numpy 2.4.3
  • Does not include lxml

Run 02 results:

  • T2* map from t2smap_workflow has outliers and limited values (same as post above)
  • T2* map from tedana_workflow has outliers and limited values

I also tried running tedana 26.0.1 with the TEs in seconds, rather than milliseconds, but there was no change. So, it appears the issue is related to some change in either tedana, nilearn, or numpy. I’m not sure how to isolate which one it is. Any thoughts would be appreciated. Thanks!

Sincerely,
Keith G. Jones

Hi Keith,

I looked through our code and I think I found one potentially relevant thing that changed between the two versions:

Add a function to check if TE values are in ms, otherwise update them by eurunuela · Pull Request #1151 · ME-ICA/tedana · GitHub Takes echo times and, if they are all less than 1, assumes they are in seconds and converts to milliseconds. Unless one of your echo times in milliseconds is <1 or one of your echo times in seconds is >1 I’m not sure how this would cause a problem. I’ll have to keep thinking.

The other change I noticed in the log is:

v24.0.1
Echo-wise intensity thresholds for adaptive mask: [1726.09909769 1107.85925415 693.81110511 412.75461309]
5943 voxels in user-defined mask do not have good signal. Removing voxels from mask.

v26.0.1
Echo-wise intensity thresholds for adaptive mask: [1757.1617 1076.4435 586.6434 325.51932]
1524 voxels in user-defined mask do not have good signal. Removing voxels from mask.

This means that more voxels are included in the T2* map in 26.0.1 vs 24.0.1. I’m not sure why these thresholds would change. We added some additional options for calculating this mask, but I don’t think we changed the default calculation. Can you share the logs from your re-run to see if those values are now identical?

If more “bad” voxels are retained in v26.0.1 then that could explain the extrema, but, if you remove the extrema, everything else should be the same.

Dan

Hi Dan,

Thanks for the input. I’ve attached the new logs below, and the intensity thresholds and excluded voxels are now identical between versions, so I’m guessing that difference was due to using different preprocessing methods for the input data.

While the extreme values with 26.0.1 are strange, there aren’t very many of them, so presumably they could be excluded or ignored. I’m more curious about why having extreme values affects the values of the other voxels and forces them to be highly similar. It’s as if something in the processing is saying “T2* values can be placed into 100 bins between the min and max value”, so for example, if the range is 0 to 1, the T2* values are rounded to the nearest 0.01, but if the range is 0 to 100, the values are rounded to the nearest 1. I’m not sure why the math would work that way, but the results seem to be implying something along those lines.

Here are the updated logs:

Run 1: dataset 1 into tedana 24.0.1 using tedana_workflow, TEs in milliseconds (15.6, 38.2, 60.8, 83.4)
tedana_24.0.1_2026-03-24T101740.txt (22.5 KB)

Run 2: dataset 1 into tedana 26.0.1 using tedana_workflow, TEs in milliseconds
tedana_26.0.1_2026-03-24T103124.txt (26.2 KB)

Run 3: dataset 1 into tedana 26.0.1 using tedana_workflow, TEs in seconds (0.0156, 0.0382, 0.0608, 0.0834)
tedana_26.0.1_2026-03-24T104154.txt (26.1 KB)

Thanks again!

Sincerely,
Keith G. Jones

Hi Keith,

Nothing in those logs jumps out to me and it looks like the two versions are doing identical steps through the T2* map estimation. I just created clean environments for tedana v24.0.01 and v26.0.1 and ran a similar call to tedana (with fittype=curvefit) and I got identical T2* maps between the two versions. The one difference I see is that the image header is slightly garbled in v26.0.01. The header says there are still the number of volumes as time points, but there is clearly only one volume. This is a bug on the tedana side, but unless your image visualization program is getting confused by this, I don’t think it’s the source of your problem.

Could you find a place to share the volumes that are being inputted into tedana and your echo times so I can see if this is a bug that relates to something specific in your data? If you can replicate your problem with only a subset of your data (i.e. running t2smap with the first 10 volumes of each run) that might be easier to share.

Dan

1 Like

Hi Dan,

Thanks for looking at the logs! I ended up looking at the headers of the T2* images to see if I noticed any issues with volumes, and I happened to notice a few differences:

The T2* map created by 24.0.1:

  • data_type: FLOAT32
  • scl_slope: 1.0

The T2* map created by 26.0.1:

  • data_type: UINT8
  • scl_slope: 0.035988

The scl_slope of 0.035988 stuck out to me, as that was the interval I was seeing in the T2* map… which makes sense, because the data is being stored as integers and then multiplied by 0.035988 to get displayed values. That definitely explains why the T2* values are forced into certain bins. Is it possible that some time after 24.0.1, the calculated T2* values were changed into integers with a scaling constant, which then causes issues if the max value is unusually high? Thanks for the help!

Sincerely,
Keith Jones

It is possible that some change in nilearn, nibabel, or how we are using them resulted in this difference. Those programs take short-cuts in dealing with image headers or make assumptions about what we do or don’t want to save. Perhaps something key is getting transformed or lost when some changes in our code.

If you open the volumes in a program that uses that scaling factor, or if you multiply the int version by that scaling factors, are the maps otherwise identical or very similar? There are visualizations of the T2* map in the tedana_report.html file. If those look the same, then this really is a matter of how these files are saved.

Hi Dan,

The T2* visualizations in the HTML reports look similar to the NIFTI maps when displayed in MRIcroGL or fsleyes. For the run we’ve been discussing, the 24.0.1 T2* map looks reasonable, with a range of values between 0 and 0.1 and a precision out to 0.001 or more.

The 26.0.1 T2* map looks like it possibly started with those same values, but they were forced into bins of 0, 0.036, 0.072, etc, as seen on the histogram. Presumably, when the data was saved in integer format and the scaling factor was set in order to accommodate for extreme outliers, most of the physiologically-relevant values were forced to integers of 0, 1, 2, and 3, a lot of the nuance was lost.

24.0.1 HTML Report: Example Dataset A

26.0.1 HTML Report: Example Dataset A

I have another example where the scaling factor in the NIFTI header was set to 0.138, so all physiologically plausible values in the T2* map have been forced to either 0 or 0.138. There are other instances where the scaling factor is much higher, so all T2* values in the brain have essentially been forced to 0, and the map and histogram appear blank.

26.0.1 HTML Report: Example Dataset B

It does sound like there was probably some small change in one of the python libraries that led to this happening in very specific cases, though it’s hard to say what exactly. In terms of temporarily making this dataset work with 26.0.1 so I can use robust ICA, do you think it would be reasonable to compute T2* maps in 24.0.1 and input those as an argument into 26.0.1? From what I can tell, the rest of the 26.0.1 process (calculating and evaluating components) seems to be working well in the examples I’ve tested, though perhaps the component classification isn’t as robust if it’s working with rounded T2* values. Thanks so much for all of your help with this!

Sincerely,
Keith Jones

I was able to reproduce the datatype issue with nilearn 0.13.1. When I used 0.12.1 I didn’t have the same issue.

EDIT: I’ve opened [BUG] unmask uses header from mask image · Issue #6150 · nilearn/nilearn · GitHub about the problem.

2 Likes

Hi all,

I was able to run a few tests using tedana 26.0.1 with nilearn 0.12.1, and the T2* maps look good! I’ll use that as a work around until the nilearn code gets updated in the future. Thanks a lot to @tsalo and @handwerkerd for helping troubleshoot this!

Sincerely,
Keith G. Jones