diff --git a/matlab/TCV/gdat_tcv.m b/matlab/TCV/gdat_tcv.m
index 56d436ce8f5e95f0e72dac0763d3cb3aca775138..9e781fde81a724b5522e47d908102d6382509e1b 100644
--- a/matlab/TCV/gdat_tcv.m
+++ b/matlab/TCV/gdat_tcv.m
@@ -1652,64 +1652,31 @@ elseif strcmp(mapping_for_tcv.method,'switchcase')
     end
     if liuqe_matlab==1 && strcmp(substr_liuqe,'_1'); substr_liuqe = ''; end
     % psiscatvol obtained from linear interpolation in fact so not quite ok near axis where psi is quadratic
+    % NOTE: Since 2019 psi_scatvol uses psitbx with cubic-spline interpolation, unsure what shots have new data or not ...
     recompute_psiscatvol_always = 1;
     if liuqe_version==-1; recompute_psiscatvol_always = 1; end
+    % NOTE: Since oct.2019 psi_scatvol stored in nodes now uses LIUQE.M (shot range to check if any ...)
     if all(abs(zshift)<1e-5) && liuqe_matlab==0 && recompute_psiscatvol_always==0
       psi_max=gdat_tcv([],['\results::thomson' edge_str_dot ':psi_max' substr_liuqe],'nverbose',gdat_params.nverbose);
       psiscatvol=gdat_tcv([],['\results::thomson' edge_str_dot ':psiscatvol' substr_liuqe],'nverbose',gdat_params.nverbose);
     else
       % calculate new psiscatvol
-      if liuqe_matlab==0
-        psitdi = tdi(['tcv_eq(''psi'',''LIUQE' substr_liuqe_tcv_eq ''')']);
-        psiaxis = tdi(['tcv_eq(''psi_axis'',''LIUQE' substr_liuqe_tcv_eq ''')']);
-      else
-        psitdi = tdi(['tcv_eq(''psi'',''LIUQE.M' substr_liuqe_tcv_eq ''')']);
-        psiaxis = tdi(['tcv_eq(''psi_axis'',''LIUQE.M' substr_liuqe_tcv_eq ''')']);
-      end
-      if numel(psitdi.dim)<1
-        warning('problem with psitdi in gdat_tcv ')
-        psitdi
-        psiaxis
-        return
-      end
-      rmesh=psitdi.dim{1};
-      zmesh=psitdi.dim{2};
-      zthom=mdsdata(['dim_of(\thomson' edge_str_dot ':te,1)']);
-      zeffshift=zshift;
-      % set zeffshift time array same as psitdi
-      switch length(zeffshift)
-       case 1
-        zeffshift=zeffshift * ones(size(psitdi.dim{3}));
-       case length(psitdi.dim{3})
-        % ok
-       case length(gdat_data.t)
-        zeffshift=interp1(gdat_data.t,zeffshift,psitdi.dim{3});
-       otherwise
-        if (gdat_params.nverbose>=1);
-          disp(' bad time dimension for zshift')
-          disp(['it should be 1 or ' num2str(length(gdat_data.t)) ' or ' num2str(length(psitdi.dim{3}))])
-        end
-      end
-      for it=1:length(gdat_data.t)
-        itpsitdi=iround_os(psitdi.dim{3},gdat_data.t(it));
-        psirz=psitdi.data(:,:,itpsitdi);
-        %psiscatvol0=interp2(rmesh,zmesh,psirz',0.9*ones(size(zthom)),zthom-zeffshift(itpsitdi),'spline'); % faster with interpos
-        psiscatvol0=interpos2Dcartesian(rmesh,zmesh,psirz,0.9*ones(size(zthom)),zthom-zeffshift(itpsitdi),-0.1,-0.1);
-        %psiscatvol0=interp2(rmesh,zmesh,psirz',0.9*ones(size(zthom)),zthom-zeffshift(itpsitdi),'linear');
-        psiscatvol.data(it,:)=psiscatvol0;
-        % since take closest psi(R,Z) from psitdi, should also take closest for psi_max and not interpolating
-        itpsiaxis = iround_os(psiaxis.dim{1},gdat_data.t(it));
-        psi_max.data(it,1) = psiaxis.data(itpsiaxis);
-      end
-      psiscatvol.dim{1} = gdat_data.t;
-      psiscatvol.dim{2} = gdat_data.x;
+      [psiscatvol,psi_max] = psi_scatvol_zshift(gdat_data, zshift, edge_str_dot, psitbx_str, gdat_params);
+      
+      % Add the results to the output of gdat
+      gdat_data.psiscatvol = psiscatvol;
+      gdat_data.psi_max = psi_max;
     end
     if ~isempty(psiscatvol.data) && ~ischar(psiscatvol.data) && ~isempty(psi_max.data) && ~ischar(psi_max.data)
-      for ir=1:length(psiscatvol.dim{2})
-        rho2 = max(1.-psiscatvol.data(:,ir)./psi_max.data,0);
-        rho(ir,:)= sqrt(rho2');
-        % rho(ir,:)= sqrt(1.-psiscatvol.data(:,ir)./psi_max.data(:))';
-      end
+      psi_norm = 1.-psiscatvol.data./repmat(psi_max.data(:).',[size(psiscatvol.data,1),1]);
+      mask = psi_norm < 0;
+      if any(mask(:))
+        % NOTE: I am not entirely sure there could not be a case where this
+        % is actually possible outside of the confined plasma
+        warning('Found negative values of psi_norm at TS positions, replacing them with 0');
+        psi_norm(mask) = 0;
+      end
+      rho = sqrt(psi_norm);
     else
       if gdat_params.nverbose>=1 && gdat_data.gdat_params.edge==0
         warning(['psiscatvol empty?, no rho calculated for data_request = ' data_request_eff]);
@@ -3123,3 +3090,83 @@ gdat_data.t=time;
 if any(strcmp(fieldnames(tracetdi),'units'))
   gdat_data.units=tracetdi.units;
 end
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+function [psiscatvol,psi_max] = psi_scatvol_zshift(gdat_data, zshift, system_str_dot, psitbx_str, gdat_params )
+% Input arguments:
+%   gdat_data: current structure containing TS data
+%   zshift: vertical shift of the equilibrium, can be a vector for time-dependent data
+%   system_str_dot: '' or '.edge' indicating thomson syste,
+%   psitbx_str: source for the equilibrium map, psitbx-style
+%   gdat_params: parameters for the gdat call % TODO: Why is it different from gdat_data.gdat_params?
+
+% Use psitbx
+t_th = gdat_data.t;
+th_z = mdsdata(['dim_of(\thomson' system_str_dot ':te,1)']); % TODO: Isn't this gdat_data.dim{1}?
+th_r = 0.9*ones(size(th_z));
+ntt = numel(t_th);
+nch = numel(th_z); % number of chords
+
+[t_psi,status] = mdsvalue('dim_of(tcv_eq("i_p",$))',psitbx_str); % TODO: replace with time_psi when tcv_eq supports it
+if ~rem(status,2)
+  warning('problem with getting equilibrium time base in gdat_tcv')
+  display(t_psi);
+  return
+end
+
+zshifteff=zshift;
+% set zshifteff time array same as t_psi
+switch numel(zshifteff)
+  case 1
+    zshifteff=zshifteff * ones(size(t_th));
+  case numel(t_psi)
+    zshifteff=interp1(t_psi,zshifteff,t_th);
+  case numel(t_th)
+    % ok
+  otherwise
+    if (gdat_params.nverbose>=1)
+      disp(' bad time dimension for zshift')
+      disp(['it should be 1 or ' num2str(numel(t_th)) ' or ' num2str(numel(t_psi))])
+      return
+    end
+end
+
+% Get LIUQE times corresponding to TS times
+t_eq = t_psi(iround(t_psi,t_th));
+
+% Get PSI map
+psi = psitbxtcv(gdat_data.shot,t_eq,'*0',psitbx_str);
+% PSITBXTCV will have removed duplicate times
+i_psi = iround(psi.t,t_eq); % Mapping from psi times to TS times
+
+% Define grid points where to evaluate PSI
+if ~any(diff(zshifteff))
+  % Constant vector
+  pgrid = psitbxgrid('cylindrical','points',{th_r,th_z - zshifteff(1),0});
+else
+  % We need as many time points in psi as TS times
+  psi = subs_time(psi,i_psi); % Now has ntt times
+  i_psi = 1:ntt; % Trivial mapping from psi times to TS times
+  % Time-varying vector
+  th_z_eff = repmat(th_z(:),1,ntt) - repmat(reshape(zshifteff,1,ntt),nch,1);
+  th_r_eff = repmat(th_r(:),1,ntt);
+  pgrid = psitbxgrid('cylindrical','time-points',{th_r_eff,th_z_eff,0});
+end
+
+% Compute psi at TS positions
+psi_ts = psitbxf2f(psi,pgrid);
+psiscatvol.data = squeeze(psi_ts.x(:,i_psi));
+psiscatvol.dim{1} = gdat_data.x;
+psiscatvol.dim{2} = t_th;
+% NOTE: we should probably not include time points where equilibrium time is far from TS time.
+
+% Compute psi_axis at TS times
+% Do not use psi_axis node because:
+% - For legacy LIUQE, psi_axis is not computed by psitbx, so we could get a
+% different answer and cause complex numbers computing rho_psi
+% - For MATLAB LIUQE, psi_axis is only the result of asxymex whereas the
+% psitbx adds some Newton iterations so again complex numbers are possible
+psi_norm = psitbxp2p(psi,'01');
+psi_max.data = psi_norm.psimag(i_psi);
+psi_max.dim = {t_th};
+