diff --git a/talks/matlab_vs_python/migp/demo_MIGP_NIFTI.m b/talks/matlab_vs_python/migp/demo_MIGP_NIFTI.m
index 81fdfe00fa0a86a1bb2e3549c3c3de20217638af..e14287e6f6aa5aaf5adeeadc5e8396db015cbd08 100644
--- a/talks/matlab_vs_python/migp/demo_MIGP_NIFTI.m
+++ b/talks/matlab_vs_python/migp/demo_MIGP_NIFTI.m
@@ -1,19 +1,18 @@
-
-
-% MIGP - MELODIC's Incremental Group-PCA
+%% MIGP - MELODIC's Incremental Group-PCA
 % Steve Smith, FMRIB, 2012-2013
 % not yet finally tweaked/evaluated, or published - please do not pass on.
 % adapted by Sean Fitz, 2020
 
-%%%%%%%%%%%%%%%%%%%%%%%%%% USER OPTIONS
+%% User Options (equivalent to melodic defaults)
 
-INlist=dir('data/cobre/fmri_*.nii.gz');  % list of input 4D NIFTI standard space datasets
-INmask='data/brain_mask.nii.gz';       % 3D NIFTI mask image
-GO='matMIGP';           % output filename
-dPCAint=550;           % internal number of components - typically 2-4 times number of timepoints in each run (if you have enough RAM for that)
-dPCAout=100;           % number of eigenvectors to output - should be less than dPCAint and more than the final ICA dimensionality
+INlist=dir('~/nilearn_data/cobre/fmri_*_smooth.nii.gz');  % list of input 4D NIFTI standard space datasets
+INmask='~/nilearn_data/brain_mask.nii.gz';       % 3D NIFTI mask image
+GO='matMIGP';          % output filename
+dPCAint=299;           % internal number of components - typically 2-4 times number of timepoints in each run (if you have enough RAM for that)
+dPCAout=299;           % number of eigenvectors to output - should be less than dPCAint and more than the final ICA dimensionality
+sep_vn=false;          % switch on separate variance nomalisation for each input dataset
 
-%%%%%%%%%%%%%%%%%%%%%%%%%% END OF USER OPTIONS
+%% Run MIGP
 
 Nsub=length(INlist); [~,r]=sort(rand(Nsub,1));  % will process subjects in random order
 mask=read_avw(INmask); 
@@ -27,26 +26,33 @@ for i = 1:Nsub
 
     % read data
     filename=[INlist(r(i)).folder, '/', INlist(r(i)).name];
+    fprintf('Reading data file %s\n', filename);
     grot=double(read_avw(filename)); 
     grot=reshape(grot,prod(masksize),size(grot,4)); 
+    
+    % demean
+    fprintf('\tRemoving mean image\n');
     grot=demean(grot(mask~=0,:)');
     
-    % var-norm
-    [uu,ss,vv]=ss_svds(grot,30);
-    vv(abs(vv)<2.3*std(vv(:)))=0;
-    stddevs=max(std(grot-uu*ss*vv'),0.001);
-    grot=grot./repmat(stddevs,size(grot,1),1);
+    % var-norm separately for each subject
+    if sep_vn==true
+        fprintf('\tNormalising by voxel-wise variance\r');
+        [uu,ss,vv]=ss_svds(grot,30);
+        vv(abs(vv)<2.3*std(vv(:)))=0;
+        stddevs=max(std(grot-uu*ss*vv'),0.001);
+        grot=grot./repmat(stddevs,size(grot,1),1);
+    end
     
     
     if (i==1)
         W=demean(grot); clear grot;
     else
-        
         % concat
         W=[W; demean(grot)]; clear grot;
         
         % reduce W to dPCAint eigenvectors
         if size(W,1)-10 > dPCAint
+            fprintf('\tReducing data matrix to a %i dimensional subspace\n', dPCAint);
             [uu,dd]=eigs(W*W',dPCAint);  
             W=uu'*W; 
             clear uu;
@@ -56,8 +62,11 @@ for i = 1:Nsub
     
 end
 
+%% Reshape and save
+
 grot=zeros(prod(masksize),dPCAout); 
 grot(mask~=0,:)=W(1:dPCAout,:)'; grot=reshape(grot,[masksize ,dPCAout]);
+fprintf('Save to %s.nii.gz\n', GO)
 save_avw(grot,GO,'f',[1 1 1 1]);
 system(sprintf('fslcpgeom %s %s -d',filename,GO));
 
diff --git a/talks/matlab_vs_python/migp/fetch_data.ipynb b/talks/matlab_vs_python/migp/fetch_data.ipynb
index 33f4f27bb97a47483b9f2f0308b95c6c460369e2..44ae65f3ebbc9bafd5da9239817550537d1dc1a0 100644
--- a/talks/matlab_vs_python/migp/fetch_data.ipynb
+++ b/talks/matlab_vs_python/migp/fetch_data.ipynb
@@ -16,7 +16,7 @@
     "* [Clean the data](#clean-the-data)\n",
     "* [Run `melodic`](#run-melodic)\n",
     "\n",
-    "Firstly we will import the necessary packages for this notebook:                                 "
+    "Firstly we will import the necessary packages for this notebook:                           "
    ]
   },
   {
@@ -69,7 +69,9 @@
     "<a class=\"anchor\" id=\"download-the-data\"></a>\n",
     "## Download the data\n",
     "\n",
-    "We use a method from [`nilearn`](https://nilearn.github.io/index.html) called `fetch_cobre` to download the fMRI data:"
+    "Create a directory in the users home directory to store the downloaded data:\n",
+    "\n",
+    "`expanduser` will expand the `~` to the be users home directory:"
    ]
   },
   {
@@ -78,13 +80,25 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# Create a directory in the users home directory to store the downloaded data\n",
     "data_dir = op.expanduser('~/nilearn_data')\n",
     "\n",
     "if not op.exists(data_dir):\n",
-    "    os.makedirs(data_dir)\n",
-    "    \n",
-    "# Download the data (if not already downloaded)\n",
+    "    os.makedirs(data_dir)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Download the data (if not already downloaded). We use a method from [`nilearn`](https://nilearn.github.io/index.html) called `fetch_cobre` to download the fMRI data:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
     "d = datasets.fetch_cobre(data_dir=data_dir)    "
    ]
   },
@@ -158,7 +172,7 @@
    "outputs": [],
    "source": [
     "# generate melodic command line string\n",
-    "melodic_cmd = f\"melodic -i {','.join(smooth)} --mask={op.join(data_dir, 'brain_mask.nii.gz')} -d 10 -v --Oall -o cobre.gica \"\n",
+    "melodic_cmd = f\"melodic -i {','.join(smooth)} --mask={op.join(data_dir, 'brain_mask.nii.gz')} -d 10 -v -o cobre.gica \"\n",
     "print(melodic_cmd)\n",
     "\n",
     "# run melodic\n",
@@ -183,6 +197,13 @@
     "ics = nb.load('cobre.gica/melodic_IC.nii.gz')\n",
     "fig = map_plot(ics)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
diff --git a/talks/matlab_vs_python/migp/matlab_MIGP.ipynb b/talks/matlab_vs_python/migp/matlab_MIGP.ipynb
index 830c173746af46ee6a603d0e700c26dbd6c7e19e..779e4648870b3fba904c68beb0caa5836fd4c142 100644
--- a/talks/matlab_vs_python/migp/matlab_MIGP.ipynb
+++ b/talks/matlab_vs_python/migp/matlab_MIGP.ipynb
@@ -1,5 +1,19 @@
 {
  "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Matlab MIGP\n",
+    "\n",
+    "This notebook will load the dimension reduced data from Matlab MIGP, run group ICA, and then plot the group ICs.\n",
+    "\n",
+    "* [Run `melodic`](#run-melodic)\n",
+    "* [Plot group ICs](#plot-group-ics)\n",
+    "\n",
+    "Firstly we will import the necessary packages for this notebook:  "
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -10,7 +24,17 @@
     "from nilearn import image\n",
     "import nibabel as nb\n",
     "import matplotlib.pyplot as plt\n",
-    "import numpy as np"
+    "import numpy as np\n",
+    "import os.path as op"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "It will be necessary to know the location where the data was stored so that we can load the brainmask. \n",
+    "\n",
+    "`expanduser` will expand the `~` to the be users home directory:"
    ]
   },
   {
@@ -19,16 +43,28 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "data_dir = op.expanduser('~/nilearn_data')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"run-melodic\"></a>\n",
+    "### Run ```melodic```\n",
     "\n",
-    "melodic -i matMIGP.nii.gz \\\n",
-    "\t--mask=data/brain_mask.nii.gz \\\n",
-    "\t-d 20 \\\n",
-    "\t-v \\\n",
-    "\t--nobet \\\n",
-    "\t--disableMigp \\\n",
-    "\t--varnorm \\\n",
-    "\t-o matMIGP_dim20.ica"
+    "Generate a command line string and run group ```melodic``` on the Matlab MIGP dimension reduced data with a dimension of 10 components:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# generate melodic command line string\n",
+    "melodic_cmd = f\"melodic -i matMIGP.nii.gz --mask={op.join(data_dir, 'brain_mask.nii.gz')} -d 10 -v --nobet --disableMigp -o matmigp.gica\"\n",
+    "print(melodic_cmd)"
    ]
   },
   {
@@ -37,15 +73,56 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "ics = nb.load('matMIGP_dim20.ica/melodic_IC.nii.gz')\n",
+    "# run melodic\n",
+    "! {melodic_cmd}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"plot-group-ics\"></a>\n",
+    "### Plot group ICs\n",
+    "\n",
+    "Now we can load and plot the group ICs generated by ```melodic```.\n",
+    "\n",
+    "This function will be used to plot ICs:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def map_plot(d):\n",
     "\n",
-    "N = ics.shape[-1]\n",
+    "    N = d.shape[-1]\n",
     "\n",
-    "fig, ax = plt.subplots(int(np.ceil((N/2))),2, figsize=(12, N))\n",
+    "    fig, ax = plt.subplots(int(np.ceil((N/2))),2, figsize=(12, N))\n",
     "\n",
-    "for img, ax0 in zip(image.iter_img(ics), ax.ravel()):\n",
-    "    coord = plotting.find_xyz_cut_coords(img, activation_threshold=2.3)\n",
-    "    plotting.plot_stat_map(img, cut_coords=coord, vmax=5, axes=ax0)"
+    "    for img, ax0 in zip(image.iter_img(d), ax.ravel()):\n",
+    "        coord = plotting.find_xyz_cut_coords(img, activation_threshold=3.5)\n",
+    "        plotting.plot_stat_map(img, cut_coords=coord, vmax=10, axes=ax0)\n",
+    "        \n",
+    "    return fig"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Hopefully you can see some familiar looking RSN spatial patterns:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ics = nb.load('matmigp.gica/melodic_IC.nii.gz')\n",
+    "fig = map_plot(ics)"
    ]
   },
   {
diff --git a/talks/matlab_vs_python/migp/python_MIGP.ipynb b/talks/matlab_vs_python/migp/python_MIGP.ipynb
index f029476f0aacb22f53922c473df75a78788d27df..739457281bde1ff8dafa88cf0cf46d9d32eb1212 100644
--- a/talks/matlab_vs_python/migp/python_MIGP.ipynb
+++ b/talks/matlab_vs_python/migp/python_MIGP.ipynb
@@ -1,5 +1,20 @@
 {
  "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Python MIGP\n",
+    "\n",
+    "This notebook will perform *python* MIGP dimension reduction, run group ICA, and then plot the group ICs.\n",
+    "\n",
+    "* [Run python `MIGP`](#run-python-migp)\n",
+    "* [Run `melodic`](#run-melodic)\n",
+    "* [Plot group ICs](#plot-group-ics)\n",
+    "\n",
+    "Firstly we will import the necessary packages for this notebook:  "
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -13,7 +28,17 @@
     "from scipy.sparse.linalg import svds, eigs\n",
     "import matplotlib.pyplot as plt\n",
     "from nilearn import plotting\n",
-    "from nilearn import image"
+    "from nilearn import image\n",
+    "import os.path as op"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "It will be necessary to know the location where the data was stored so that we can load the brainmask. \n",
+    "\n",
+    "`expanduser` will expand the `~` to the be users home directory:"
    ]
   },
   {
@@ -22,11 +47,17 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "in_list = [nb.load(f) for f in glob.glob('data/cobre/fmri_*.nii.gz')]\n",
-    "in_mask = nb.load('data/brain_mask.nii.gz')\n",
-    "go = '3DMIGP'\n",
-    "dPCA_int = 550\n",
-    "dPCA_out = 100"
+    "data_dir = op.expanduser('~/nilearn_data')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"run-python-migp\"></a>\n",
+    "### Run python `MIGP`\n",
+    "\n",
+    "Firstly we need to set the MIGP parameters:"
    ]
   },
   {
@@ -35,7 +66,15 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "random.shuffle(in_list)"
+    "# create lists of (nibabel) image objects\n",
+    "in_list = [nb.load(f) for f in glob.glob(f'{data_dir}/cobre/fmri_*_smooth.nii.gz')]\n",
+    "in_mask = nb.load(f'{data_dir}/brain_mask.nii.gz')\n",
+    "\n",
+    "# set user parameters (equivalent to melodic defaults)\n",
+    "GO = 'pyMIGP.nii.gz'  # output filename\n",
+    "dPCA_int = 299        # internal number of components - typically 2-4 times number of timepoints in each run (if you have enough RAM for that)\n",
+    "dPCA_out = 299        # number of eigenvectors to output - should be less than dPCAint and more than the final ICA dimensionality\n",
+    "sep_vn = False        # switch on separate variance nomalisation for each input dataset"
    ]
   },
   {
@@ -44,26 +83,36 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# randomise the subject order\n",
+    "random.shuffle(in_list)\n",
+    "\n",
+    "# load and unravel brainmask\n",
     "mask = in_mask.get_fdata().ravel()\n",
     "\n",
+    "# function to demean the data\n",
     "def demean(x):\n",
     "    return x - np.mean(x, axis=0)\n",
     "\n",
+    "# loop through input files/subjects\n",
     "for i, f in enumerate(in_list):\n",
     "    \n",
-    "    print(i, end=' ')\n",
-    "    \n",
     "    # read data\n",
+    "    print(f'Reading data file {f.get_filename()}')\n",
     "    grot = f.get_fdata()\n",
     "    grot = np.reshape(grot, [-1, grot.shape[-1]])\n",
     "    grot = grot[mask!=0, :].T\n",
+    "    \n",
+    "    # demean\n",
+    "    print(f'\\tRemoving mean image')\n",
     "    grot = demean(grot)\n",
     "    \n",
     "    # var-norm\n",
-    "    [uu, ss, vt] = svds(grot, k=30)\n",
-    "    vt[np.abs(vt) < (2.3 * np.std(vt))] = 0;\n",
-    "    stddevs = np.maximum(np.std(grot - (uu @ np.diag(ss) @ vt), axis=0), 0.001)\n",
-    "    grot = grot/stddevs\n",
+    "    if sep_vn:\n",
+    "        print(f'\\tNormalising by voxel-wise variance')\n",
+    "        [uu, ss, vt] = svds(grot, k=30)\n",
+    "        vt[np.abs(vt) < (2.3 * np.std(vt))] = 0;\n",
+    "        stddevs = np.maximum(np.std(grot - (uu @ np.diag(ss) @ vt), axis=0), 0.001)\n",
+    "        grot = grot/stddevs\n",
     "    \n",
     "    if i == 0:\n",
     "        W = demean(grot)\n",
@@ -73,23 +122,28 @@
     "        \n",
     "        # reduce W to dPCA_int eigenvectors\n",
     "        if W.shape[0]-10 > dPCA_int:\n",
+    "            print(f'\\tReducing data matrix to a {dPCA_int} dimensional subspace')\n",
     "            uu = eigs(W@W.T, dPCA_int)[1]\n",
     "            uu = np.real(uu)\n",
     "            W = uu.T @ W\n",
     "        \n",
-    "    f.uncache()\n",
-    "        \n"
+    "# reshape and save\n",
+    "grot = np.zeros([mask.shape[0], dPCA_out])\n",
+    "grot[mask!=0, :] = W[:dPCA_out, :].T\n",
+    "grot = np.reshape(grot, in_list[0].shape[:3] + (dPCA_out,))\n",
+    "\n",
+    "print(f'Save to {GO}')\n",
+    "nb.Nifti1Image(grot, affine=in_list[0].affine).to_filename(GO)\n"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "grot = np.zeros([mask.shape[0], dPCA_out])\n",
-    "grot[mask!=0, :] = W[:dPCA_out, :].T\n",
-    "grot = np.reshape(grot, in_list[0].shape[:3] + (dPCA_out,))"
+    "<a class=\"anchor\" id=\"run-melodic\"></a>\n",
+    "### Run ```melodic```\n",
+    "\n",
+    "Generate a command line string and run group ```melodic``` on the Python MIGP dimension reduced data with a dimension of 10 components:"
    ]
   },
   {
@@ -98,7 +152,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "nb.Nifti1Image(grot, affine=in_list[0].affine, header=in_list[0].header).to_filename('pyMIGP.nii.gz')"
+    "# generate melodic command line string\n",
+    "melodic_cmd = f\"melodic -i pyMIGP.nii.gz --mask={op.join(data_dir, 'brain_mask.nii.gz')} -d 10 -v --nobet --disableMigp -o pymigp.gica\"\n",
+    "print(melodic_cmd)"
    ]
   },
   {
@@ -107,16 +163,20 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
-    "\n",
-    "melodic -i pyMIGP.nii.gz \\\n",
-    "\t--mask=data/brain_mask.nii.gz \\\n",
-    "\t-d 20 \\\n",
-    "\t-v \\\n",
-    "\t--nobet \\\n",
-    "\t--disableMigp \\\n",
-    "\t--varnorm \\\n",
-    "\t-o pymigp_dim20.ica"
+    "# run melodic\n",
+    "! {melodic_cmd}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a class=\"anchor\" id=\"plot-group-ics\"></a>\n",
+    "### Plot group ICs\n",
+    "\n",
+    "Now we can load and plot the group ICs generated by ```melodic```.\n",
+    "\n",
+    "This function will be used to plot ICs:"
    ]
   },
   {
@@ -125,15 +185,34 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "ics = nb.load('pymigp_dim20.ica/melodic_IC.nii.gz')\n",
+    "def map_plot(d):\n",
     "\n",
-    "N = ics.shape[-1]\n",
+    "    N = d.shape[-1]\n",
     "\n",
-    "fig, ax = plt.subplots(int(np.ceil((N/2))),2, figsize=(12, N))\n",
+    "    fig, ax = plt.subplots(int(np.ceil((N/2))),2, figsize=(12, N))\n",
     "\n",
-    "for img, ax0 in zip(image.iter_img(ics), ax.ravel()):\n",
-    "    coord = plotting.find_xyz_cut_coords(img, activation_threshold=1.5)\n",
-    "    plotting.plot_stat_map(img, cut_coords=coord, vmax=5, axes=ax0)"
+    "    for img, ax0 in zip(image.iter_img(d), ax.ravel()):\n",
+    "        coord = plotting.find_xyz_cut_coords(img, activation_threshold=3.5)\n",
+    "        plotting.plot_stat_map(img, cut_coords=coord, vmax=10, axes=ax0)\n",
+    "        \n",
+    "    return fig"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Hopefully you can see some familiar looking RSN spatial patterns:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ics = nb.load('pymigp.gica/melodic_IC.nii.gz')\n",
+    "fig = map_plot(ics)"
    ]
   },
   {