diff --git a/demos/MoAE/moae_03_slice_display.m b/demos/MoAE/moae_03_slice_display.m
index e08c7a465..de4847d02 100644
--- a/demos/MoAE/moae_03_slice_display.m
+++ b/demos/MoAE/moae_03_slice_display.m
@@ -18,104 +18,15 @@
 
 this_dir = fileparts(mfilename('fullpath'));
 
-subLabel = '01';
-
 opt.pipeline.type = 'stats';
 
-opt.dir.raw = fullfile(this_dir, 'inputs', 'raw');
 opt.dir.derivatives = fullfile(this_dir, 'outputs', 'derivatives');
 opt.dir.preproc = fullfile(opt.dir.derivatives, 'bidspm-preproc');
 
-opt.dir.roi = fullfile(opt.dir.derivatives, 'bidspm-roi');
-opt.dir.stats = fullfile(opt.dir.derivatives, 'bidspm-stats');
-
 opt.model.file = fullfile(this_dir, 'models', 'model-MoAE_smdl.json');
 
-% Specify the result to compute
-opt.results(1).nodeName = 'run_level';
-
-opt.results(1).name = 'listening';
-% MONTAGE FIGURE OPTIONS
-opt.results(1).montage.do = true();
-opt.results(1).montage.slices = -4:2:16; % in mm
-% axial is default 'sagittal', 'coronal'
-opt.results(1).montage.orientation = 'axial';
-% will use the MNI T1 template by default but the underlay image can be changed.
-opt.results(1).montage.background = ...
-    fullfile(spm('dir'), 'canonical', 'avg152T1.nii');
+opt.subjects = {'01'};
 
 opt = checkOptions(opt);
 
-use_schema = false;
-BIDS_ROI = bids.layout(opt.dir.roi, 'use_schema', use_schema);
-
-filter = struct('sub', subLabel, ...
-                'hemi', 'R', ...
-                'desc', 'auditoryCortex');
-
-rightRoiFile = bids.query(BIDS_ROI, 'data', filter);
-
-filter.hemi = 'L';
-
-leftRoiFile = bids.query(BIDS_ROI, 'data', filter);
-
-% we get the con image to extract data
-ffxDir = getFFXdir(subLabel, opt);
-maskImage = spm_select('FPList', ffxDir, '^.*_mask.nii$');
-bf = bids.File(spm_file(maskImage, 'filename'));
-conImage = spm_select('FPList', ffxDir, ['^con_' bf.entities.label '.nii$']);
-
-%% Layers to put on the figure
-layers = sd_config_layers('init', {'truecolor', 'dual', 'contour', 'contour'});
-
-% Layer 1: Anatomical map
-[anat_normalized_file, anatRange] = return_normalized_anat_file(opt, subLabel);
-layers(1).color.file = anat_normalized_file;
-layers(1).color.range = [0 anatRange(2)];
-
-layers(1).color.map = gray(256);
-
-%% Layer 2: Dual-coded layer
-%
-%   - contrast estimates color-coded;
-
-layers(2).color.file = conImage;
-
-color_map_folder = fullfile(fileparts(which('map_luminance')), '..', 'mat_maps');
-load(fullfile(color_map_folder, 'diverging_bwr_iso.mat'));
-layers(2).color.map = diverging_bwr;
-
-layers(2).color.range = [-4 4];
-layers(2).color.label = '\beta_{listening} - \beta_{baseline} (a.u.)';
-
-%% Layer 2: Dual-coded layer
-%
-%   - t-statistics opacity-coded
-
-spmTImage = spm_select('FPList', ffxDir, ['^spmT_' bf.entities.label '.nii$']);
-layers(2).opacity.file = spmTImage;
-
-layers(2).opacity.range = [2 3];
-layers(2).opacity.label = '| t |';
-
-%% Layer 3 and 4: Contour of ROI
-
-layers(3).color.file = rightRoiFile{1};
-layers(3).color.map = [0 0 0];
-layers(3).color.line_width = 2;
-
-layers(4).color.file = leftRoiFile{1};
-layers(4).color.map = [1 1 1];
-layers(4).color.line_width = 2;
-
-%% Settings
-settings = sd_config_settings('init');
-
-% we reuse the details for the SPM montage
-settings.slice.orientation = opt.results(1).montage.orientation;
-settings.slice.disp_slices = -15:3:18;
-settings.fig_specs.n.slice_column = 4;
-settings.fig_specs.title = opt.results(1).name;
-
-%% Display the layers
-[settings, p] = sd_display(layers, settings);
+transparentMontage(opt);
diff --git a/demos/MoAE/return_normalized_anat_file.m b/demos/MoAE/return_normalized_anat_file.m
deleted file mode 100644
index 6a786ac9c..000000000
--- a/demos/MoAE/return_normalized_anat_file.m
+++ /dev/null
@@ -1,26 +0,0 @@
-function [anat_normalized_file, anat_range] = return_normalized_anat_file(opt, sub_label)
-  %
-
-  % (C) Copyright 2021 Remi Gau
-
-  [BIDS, opt] = getData(opt, opt.dir.preproc);
-
-  anat_normalized_file = bids.query(BIDS, 'data',  ...
-                                    'modality', 'anat', ...
-                                    'space', 'IXI549Space', ...
-                                    'suffix', 'T1w');
-
-  if isempty(anat_normalized_file)
-    opt.query.space = 'IXI549Space';
-    [anat_normalized_file, anatDataDir] = getAnatFilename(BIDS, opt, sub_label);
-    anat_normalized_file = fullfile(anatDataDir, anat_normalized_file);
-  else
-    anat_normalized_file = anat_normalized_file{1};
-  end
-
-  hdr = spm_vol(anat_normalized_file);
-  vol = spm_read_vols(hdr);
-
-  anat_range = [min(vol(:)) max(vol(:))];
-
-end
diff --git a/lib/slice_display b/lib/slice_display
index 4326779c8..f6f1ce42f 160000
--- a/lib/slice_display
+++ b/lib/slice_display
@@ -1 +1 @@
-Subproject commit 4326779c8e9d7681e0b13827196aad64c801e170
+Subproject commit f6f1ce42f56ae7bc40e697a1083bc6514a631a9f
diff --git a/src/bids_model/BidsModel.m b/src/bids_model/BidsModel.m
index 0c49baeae..65e6ad43f 100644
--- a/src/bids_model/BidsModel.m
+++ b/src/bids_model/BidsModel.m
@@ -312,6 +312,7 @@
     end
 
     function results = getResults(obj)
+      %    return results from all nodes
 
       results = struct([]);
       idx = 1;
diff --git a/src/defaults/defaultResultsStructure.m b/src/defaults/defaultResultsStructure.m
index 9bc93fede..3a73b9881 100644
--- a/src/defaults/defaultResultsStructure.m
+++ b/src/defaults/defaultResultsStructure.m
@@ -32,16 +32,16 @@
   %   load(fullfile(color_map_folder, 'diverging_bwr_iso.mat'));
 
   layers{2} = struct('color', struct('file', [], ... % con image
-                                     'map', 'diverging_bwr_iso', ...
-                                     'range', [-4 4]), ...
-                     'label', '\beta_{listening} - \beta_{baseline} (a.u.)', ...
-                     'opacity', struct('file', [], ... % spmT image
+                                     'range', [-4 4], ...
+                                     'label', ''), ...
+                     'opacity', struct('file', [], ... % assume spmT image
                                        'range', [2 3], ...
-                                       'label', '| t |'));
+                                       'label', ''), ...
+                     'type', 'dual');
 
   layers{3} = struct('color', struct('file', [], ... % spmT mask thresholded at 0.05 FWD
-                                     'map', [0 0 0], ...
-                                     'line_width', 2));
+                                     'map', 'w', ...
+                                     'line_width', 1));
 
   result.sdConfig.layers = layers;
 
diff --git a/src/stats/results/checkMontage.m b/src/stats/results/checkMontage.m
new file mode 100644
index 000000000..ecc34e965
--- /dev/null
+++ b/src/stats/results/checkMontage.m
@@ -0,0 +1,70 @@
+function [opt, BIDS] = checkMontage(opt, iRes, node, BIDS, subLabel)
+  %
+  % Check values for create a slice montage.
+  %
+  % Set default values if they are missing.
+  %
+  % USAGE::
+  %
+  %   [opt, BIDS] = checkMontage(opt, iRes, node, BIDS, subLabel)
+  %
+  %
+
+  % (C) Copyright 2019 bidspm developers
+
+  if nargin < 4
+    BIDS = '';
+    subLabel = '';
+  end
+
+  if isfield(opt.results(iRes), 'montage') && any(opt.results(iRes).montage.do)
+
+    background = opt.results(iRes).montage.background;
+
+    % TODO refactor with getInclusiveMask
+    if isstruct(background)
+
+      if ismember(lower(node.Level), {'run', 'session', 'subject'})
+
+        if isempty(BIDS)
+          BIDS =  bids.layout(opt.dir.preproc, ...
+                              'use_schema', false, ...
+                              'index_dependencies', false, ...
+                              'filter', struct('sub', {opt.subjects}));
+        end
+
+        background.sub = subLabel;
+        background.space = opt.space;
+        file = bids.query(BIDS, 'data', background);
+
+        if iscell(file)
+          if isempty(file)
+            % let checkMaskOrUnderlay figure it out
+            file = '';
+
+          elseif numel(file) == 1
+            file = file{1};
+
+          elseif numel(file) > 1
+            file = file{1};
+
+            msg = sprintf('More than 1 overlay image found for %s.\n Taking the first one.', ...
+                          bids.internal.create_unordered_list(background));
+            id = 'tooManyMontageBackground';
+            logger('WARNING', msg, 'id', id, 'options', opt, 'filename', mfilename());
+          end
+
+        end
+
+        background = file;
+
+      end
+
+    end
+
+    background = checkMaskOrUnderlay(background, opt, 'background');
+    opt.results(iRes).montage.background = background;
+
+  end
+
+end
diff --git a/src/stats/results/renameSpmT.m b/src/stats/results/renameSpmT.m
index fc715ddb5..9e771681f 100644
--- a/src/stats/results/renameSpmT.m
+++ b/src/stats/results/renameSpmT.m
@@ -8,18 +8,23 @@ function renameSpmT(result)
   %
 
   % (C) Copyright 2023 bidspm developers
-  outputFiles = spm_select('FPList', result.dir, '^spmT_[0-9].*_sub-.*nii$');
+  prefixes = {'spmT', 'spmF'};
+  for i_prefix = 1:numel(prefixes)
 
-  for iFile = 1:size(outputFiles, 1)
+    outputFiles = spm_select('FPList', result.dir, ['^' prefixes{i_prefix} '_[0-9].*_sub-.*nii$']);
 
-    source = deblank(outputFiles(iFile, :));
+    for iFile = 1:size(outputFiles, 1)
 
-    basename = spm_file(source, 'basename');
-    split = strfind(basename, '_sub');
-    bf = bids.File(basename(split + 1:end));
+      source = deblank(outputFiles(iFile, :));
 
-    target = spm_file(source, 'basename', bf.filename);
+      basename = spm_file(source, 'basename');
+      split = strfind(basename, '_sub');
+      bf = bids.File(basename(split + 1:end));
+
+      target = spm_file(source, 'basename', bf.filename);
+
+      movefile(source, target);
+    end
 
-    movefile(source, target);
   end
 end
diff --git a/src/stats/results/transparentMontage.m b/src/stats/results/transparentMontage.m
new file mode 100644
index 000000000..d7fb413f6
--- /dev/null
+++ b/src/stats/results/transparentMontage.m
@@ -0,0 +1,179 @@
+function transparentMontage(opt)
+  %
+  % Generate montage with transparent plotting using slice_display toolbox.
+  %
+  % USAGE::
+  %
+  %   transparentMontage(opt)
+  %
+  % EXAMPLE::
+  %
+  % opt.pipeline.type = 'stats';
+  %
+  % opt.dir.derivatives = fullfile(this_dir, 'outputs', 'derivatives');
+  % opt.dir.preproc = fullfile(opt.dir.derivatives, 'bidspm-preproc');
+  % opt.model.file = fullfile(this_dir, 'models', 'model-MoAE_smdl.json');
+  %
+  % opt.subjects = {'01'};
+  %
+  % opt = checkOptions(opt);
+  %
+  % transparentMontage(opt);
+  %
+
+  % (C) Copyright 2025 bidspm developers
+
+  bm = opt.model.bm;
+
+  modelResults = bm.getResults();
+  if ~isempty(modelResults)
+    opt.results = modelResults;
+  end
+
+  % loop through the steps to compute for each contrast mentioned for each node
+  for iRes = 1:length(opt.results)
+
+    node = bm.get_nodes('Name',  opt.results(iRes).nodeName);
+
+    if isempty(node)
+
+      id = 'unknownModelNode';
+      msg = sprintf('no Node named %s in model\n %s.', ...
+                    opt.results(iRes).nodeName, ...
+                    opt.model.file);
+      logger('WARNING', msg, 'id', id, 'filename', mfilename(), 'options', opt);
+      continue
+    end
+
+    opt.results(iRes);
+
+    if ~isfield(opt.results(iRes), 'montage') || ~opt.results(iRes).montage.do
+      continue
+    end
+
+    msg = sprintf('\n PROCESSING NODE: %s\n', node.Name);
+    logger('INFO', msg, 'options', opt, 'filename', mfilename());
+
+    if any(strcmp(node.Level, {'Run', 'Subject'}))
+
+      for iSub = 1:numel(opt.subjects)
+
+        subLabel = opt.subjects{iSub};
+
+        ffxDir = getFFXdir(subLabel, opt);
+        load(fullfile(ffxDir, 'SPM.mat'));
+
+        % set defaults
+        % TODO check plotting is done on the right background
+        [optThisSubject, ~] = checkMontage(opt, iRes, node, struct([]), subLabel);
+        optThisSubject = checkOptions(optThisSubject);
+        optThisSubject.results(iRes).montage = setMontage(optThisSubject.results(iRes));
+
+        for iName = 1:numel(optThisSubject.results(iRes).name)
+
+          plotTransparentMontage(optThisSubject, SPM, subLabel, iRes, iName);
+
+        end
+
+      end
+
+    end
+
+  end
+
+end
+
+function plotTransparentMontage(opt, SPM, subLabel, iRes, iName)
+  % Generate a single transparent plot.
+  %
+  overwrite = true;
+
+  color_map_folder = fullfile(returnRootDir(), 'lib', 'brain_colours', 'mat_maps');
+
+  if opt.results(iRes).binary
+    layers = sd_config_layers('init', {'truecolor', 'dual', 'contour'});
+  else
+    layers = sd_config_layers('init', {'truecolor', 'dual'});
+  end
+
+  %% Layer 1: Anatomical map
+  layers(1) = setFields(layers(1), opt.results(iRes).sdConfig.layers{1}, overwrite);
+
+  layers(1).color.file = opt.results(iRes).montage.background{1};
+
+  hdr = spm_vol(layers(1).color.file);
+  [max_val, ~] = slover('volmaxmin', hdr);
+  layers(1).color.range = [0 max_val];
+
+  %% Layer 2: Dual-coded layer
+
+  % - contrast estimates color-coded;
+  layers(2) = setFields(layers(2), opt.results(iRes).sdConfig.layers{2}, overwrite);
+
+  name = opt.results(iRes).name{iName};
+  tmp = struct('name', name);
+  contrastNb = getContrastNb(tmp, opt, SPM);
+  % keep track if this is a t test or F test
+  stat = SPM.xCon(contrastNb).STAT;
+  contrastNb = sprintf('%04.0f', contrastNb);
+
+  % - statistics opacity-coded
+  ffxDir = getFFXdir(subLabel, opt);
+  if strcmp(stat, 'T')
+    colorFile = spm_select('FPList', ffxDir, ['^con_' contrastNb '.nii$']);
+    opacityFile = spm_select('FPList', ffxDir, ['^spmT_' contrastNb '.nii$']);
+
+    layers(2).opacity.label = '| t |';
+
+    load(fullfile(color_map_folder, 'diverging_bwr_iso.mat')); %#ok<*LOAD>
+    layers(2).color.map = diverging_bwr;
+
+  else
+    colorFile = spm_select('FPList', ffxDir, ['^ess_' contrastNb '.nii$']);
+    opacityFile = spm_select('FPList', ffxDir, ['^spmF_' contrastNb '.nii$']);
+
+    layers(2).opacity.label = 'F';
+
+    load(fullfile(color_map_folder, '1hot_iso.mat'));
+    layers(2).color.map = hot;
+
+    hdr = spm_vol(opacityFile);
+    [max_val, ~] = slover('volmaxmin', hdr);
+    layers(2).color.range = [0 max_val];
+
+    layers(2).opacity.range = [0 5];
+  end
+  layers(2).color.file = colorFile;
+  layers(2).opacity.file = opacityFile;
+
+  title = strrep(name, '_', ' ');
+  layers(2).color.label = [title ' (a.u.)'];
+
+  %% Contour
+  if opt.results(iRes).binary
+    layers(3) = setFields(layers(3), opt.results(iRes).sdConfig.layers{3}, overwrite);
+    contour = spm_select('FPList', ffxDir, ['^sub.*' contrastNb '.*_mask.nii']);
+    layers(3).color.file = contour;
+  end
+
+  %% Settings
+  settings = opt.results(iRes).sdConfig.settings;
+
+  % we reuse the details for the SPM montage
+  settings.slice.disp_slices = opt.results(1).montage.slices;
+  settings.slice.orientation = opt.results(1).montage.orientation;
+
+  settings.fig_specs.title = title;
+
+  %% Display the layers
+  settings.slice.zmm;
+  [~, ~, h_figure] = sd_display(layers, settings);
+
+  outputFile = fullfile(ffxDir, [contrastNb '_'  name '.png']);
+  print(h_figure, outputFile, '-dpng');
+  close(h_figure);
+
+  % TODO
+  % rename file
+
+end
diff --git a/src/workflows/stats/bidsResults.m b/src/workflows/stats/bidsResults.m
index e766fbec6..8ac56e50c 100644
--- a/src/workflows/stats/bidsResults.m
+++ b/src/workflows/stats/bidsResults.m
@@ -271,6 +271,8 @@
 
   cleanUpWorkflow(opt);
 
+  transparentMontage(opt);
+
 end
 
 function [opt, listNodeLevels] = keepRequestedNodes(opt, nodeName, analysisLevel)
@@ -547,62 +549,3 @@
   matlabbatch{end}.result = result;
 
 end
-
-function [opt, BIDS] = checkMontage(opt, iRes, node, BIDS, subLabel)
-
-  if nargin < 4
-    BIDS = '';
-    subLabel = '';
-  end
-
-  if isfield(opt.results(iRes), 'montage') && any(opt.results(iRes).montage.do)
-
-    background = opt.results(iRes).montage.background;
-
-    % TODO refactor with getInclusiveMask
-    if isstruct(background)
-
-      if ismember(lower(node.Level), {'run', 'session', 'subject'})
-
-        if isempty(BIDS)
-          BIDS =  bids.layout(opt.dir.preproc, ...
-                              'use_schema', false, ...
-                              'index_dependencies', false, ...
-                              'filter', struct('sub', {opt.subjects}));
-        end
-
-        background.sub = subLabel;
-        background.space = opt.space;
-        file = bids.query(BIDS, 'data', background);
-
-        if iscell(file)
-          if isempty(file)
-            % let checkMaskOrUnderlay figure it out
-            file = '';
-
-          elseif numel(file) == 1
-            file = file{1};
-
-          elseif numel(file) > 1
-            file = file{1};
-
-            msg = sprintf('More than 1 overlay image found for %s.\n Taking the first one.', ...
-                          bids.internal.create_unordered_list(background));
-            id = 'tooManyMontageBackground';
-            logger('WARNING', msg, 'id', id, 'options', opt, 'filename', mfilename());
-          end
-
-        end
-
-        background = file;
-
-      end
-
-    end
-
-    background = checkMaskOrUnderlay(background, opt, 'background');
-    opt.results(iRes).montage.background = background;
-
-  end
-
-end