Monday, January 3, 2011

[UPDATED] read netcdf file using native matlab functions

check the ncread for new version matlab from Mathworks. 
 
The attributes of scale factor, offset value and missing value are considered in this version.

function ncvardata=GetNcVarMatlab(ncfile,ncvar,RangeStr)
% to read a variable from the netcdf file using native matlab netcdf-functions
% Usage: vardata=GetNcVarMatlab(nc-filename,nc-varname,Dimension-Range)
% e.g.,
%        mydata=GetNcVarMatlab('mask.nc','tmask','1:1,1:46,1:400,1:568');
%                      RangeStr here is similar to the output of ncdump
% note: by default, variable will be converted into the class of double,
%                      and the missing value will be converted into nan.
% copyright @ http://scriptdemo.blogspot.com


IsDebug=0;
IsMissingNaN=1; % convert missing values into NaN
if ~exist(ncfile,'file')
   error([ncfile,' can not been found']);
   return
end

ncfid=netcdf.open(ncfile,'NC_NOWRITE');
% get dimension length
[numdims, numvars, numglobalatts, unlimdimID] = netcdf.inq(ncfid);
for NDim=1:numdims
   [dimname{NDim}, dimlen(NDim)] = netcdf.inqDim(ncfid,NDim-1);
end

%var infor
varid=netcdf.inqVarID(ncfid,ncvar);
[varname,varxtype,vardimids,NumAtts]=netcdf.inqVar(ncfid,varid);
numdims=length(vardimids); % some variables may have less dimensions

%check scale_factor & off_set
IsRescale=0; IsMissing=0;
myScale=1; myOffSet=0.0;
for numA=1:NumAtts
    tmpattname=netcdf.inqAttName(ncfid,varid,numA-1);
    switch lower(tmpattname)
       case {'scale_factor','scalefactor'}
            IsRescale=1;
            myscale=netcdf.getAtt(ncfid,varid,tmpattname);
       case {'add_offset','off_set','offset'}
            IsRescale=1;
            myoffset=netcdf.getAtt(ncfid,varid,tmpattname);
       case {'missing_value','missingvalue','missing'}
            IsMissing=1;
            mymissing=netcdf.getAtt(ncfid,varid,tmpattname);
       otherwise
            % do nothing
    end
end
if IsRescale==1
   if (myscale==1 & myoffset==0)
      IsRescale=0;
   end
else
   if ~exist('myscale','var') myscale=1; end
   if ~exist('myoffset','var') myoffset=0; end
end

if ~exist('RangeStr','var')
   RangeStr=':';
   for nd=2:numdims
       RangeStr=[RangeStr,',:'];
   end
end

IndC=strfind(RangeStr,',');% index of commas
if length(IndC)~=numdims-1
   disp(['Input dimension range is not correct, should be: ',num2str(numdims),'-d']);
   return
end

startstr=''; countstr=''; stridestr='';
for nn=1:length(IndC)+1
    if nn==1
       tmpstr=RangeStr(1:IndC(1)-1);
    elseif nn<numdims
       tmpstr=RangeStr(IndC(nn-1)+1:IndC(nn)-1);
    else
       tmpstr=RangeStr(IndC(nn-1)+1:end);
    end

    [tmpstart,tmpcount,tmpstride]=GetIndFromStr(tmpstr,':',dimlen(vardimids(numdims-nn+1)+1));
    if nn==1
        startstr=[num2str(tmpstart),'],'];
        countstr=[num2str(tmpcount),'],'];
        stridestr=[num2str(tmpstride),']'];
    elseif nn==numdims
        startstr=[',[',num2str(tmpstart),',',startstr];
        countstr=['[',num2str(tmpcount),',',countstr];
        stridestr=['[',num2str(tmpstride),',',stridestr];
    else
        startstr=[num2str(tmpstart),',',startstr];
        countstr=[num2str(tmpcount),',',countstr];
        stridestr=[num2str(tmpstride),',',stridestr];
    end
end

if IsDebug
   disp(['ncvardata=netcdf.getVar(ncfid,varid',startstr,countstr,stridestr,');']);
end
eval(['ncvardata=netcdf.getVar(ncfid,varid',startstr,countstr,stridestr,');']);
netcdf.close(ncfid);
ncvardata=permute(ncvardata,length(size(ncvardata)):-1:1);

if IsRescale==1
   %convert all data type into double
   if ~strcmp(class(ncvardata),'double')
      ncvardata=double(ncvardata);
   end
   if IsMissing
      if IsMissingNaN==1; % convert missing values into NaN
         ncvardata(ncvardata==mymissing)=nan;
         ncvardata=ncvardata*double(myscale)+double(myoffset);
      else
         disp(['missing value for ',ncvar,' is ',num2str(mymissing)]);
         %IndValid=find(ncvardata~=mymissing); length(IndValid)
         %ncvardata(IndValid)=ncvardata(IndValid)*double(myscale)+double(myoffset);
         ncvardata(ncvardata~=mymissing)=ncvardata(ncvardata~=mymissing)*double(myscale)+double(myoffset);
      end
   else
      ncvardata=ncvardata*double(myscale)+double(myoffset);
   end
else
  ncvardata=double(ncvardata);
end

function [numstart,numcount,numstride]=GetIndFromStr(tmpstr,ctag,Ndim)
% Read the numbers from the special string
IsDebug=0;
IndC=strfind(tmpstr,ctag);
if isempty(IndC)
   numstart=str2double(tmpstr)-1;
   numcount=1;
   numstride=1;
else
  switch length(IndC)
      case {1}
          switch IndC
            case {1} % : and :num
                if strcmp(tmpstr,':')
                   numstart=0; numcount=Ndim; numstride=1;
                else
                   if strcmp(tmpstr(2:end),'end')
                    numstart=0; numcount=Ndim; numstride=1;
                   else
                    numstart=0; numcount=str2double(tmpstr(2:end))-1; numstride=1;
                   end
                end
            case {length(tmpstr)}
                if strcmp(tmpstr,':')
                   numstart=0; numcount=Ndim; numstride=1;
                   return
                else
                   numstart=str2double(tmpstr(1:IndC-1))-1;
                   numcount=Ndim-numstart;
                   numstride=1;
                end
            otherwise
               numstart=str2double(tmpstr(1:IndC-1))-1;
               if strcmp(tmpstr(IndC+1:end),'end')
                  numend=Ndim;
               else
                  numend=str2double(tmpstr(IndC+1:end));
               end
               numcount=numend-numstart;
               numstride=1;
          end
      case {2}
          % num0:stride:num1
          if IndC(1)==1
             numstart=0;
          else
             numstart=str2double(tmpstr(1:IndC(1)-1))-1;
          end
          if IndC(2)==IndC(1)+1
             numstride=1;
          else
             numstride=str2double(tmpstr(IndC(1)+1:IndC(2)-1));
          end
          if IndC(2)==length(tmpstr)
                 numend=Ndim-1;
          else
            if strcmp(tmpstr(IndC(2)+1:end),'end')
               numend=Ndim-1;
            else
               numend=str2double(tmpstr(IndC(2)+1:end))-1;
            end
          end
          numcount=floor((numend-numstart)/numstride)+1;
          numend=numstart+(numcount-1)*(numStride);
      otherwise
          error('Not defined range type')
  end
end
if IsDebug
   disp(['tmpstr=',tmpstr])
   disp(['start:',num2str(numstart), ' stride:',num2str(numstride),' count:',num2str(numcount)])
end

2 comments:

Unknown said...

Hi thanks for making this code accessible. I tried to run it using Matlab7.11 on both ubuntu 10.10 and xp spc3 platforms. I also tried it using Matlab7.10 on Windows 7 home. But in all instances I am getting the error messages related to dimensions : Could you please elaborate the last argument for this function? "mydata=GetNcVarMatlab('mask.nc','tmask','1:1,1:46,1:400,1:568');"?

All the variables in my case are one dimensional.
Thanks a lot!
here is the out put from ncdump for one of my files:

dimensions:
MSL_alt = 400 ;
variables:
float MSL_alt(MSL_alt) ;
MSL_alt:long_name = "Mean Sea Level geometric height" ;
MSL_alt:units = "km" ;
MSL_alt:missing_value = "-999" ;
MSL_alt:_FillValue = -999.f ;
MSL_alt:valid_range = -2.f, 120.f ;
float Temp(MSL_alt) ;
Temp:long_name = "temperature" ;
Temp:units = "C" ;
Temp:missing_value = "-999" ;
Temp:_FillValue = -999.f ;
Temp:valid_range = -150.f, 100.f ;
float Vp(MSL_alt) ;
Vp:long_name = "H2O vapor pressure" ;
Vp:units = "mb" ;
Vp:missing_value = "-999" ;
Vp:_FillValue = -999.f ;
Vp:valid_range = 0.f, 60.f ;
float Pres(MSL_alt) ;
Pres:long_name = "pressure level" ;
Pres:units = "mb" ;
Pres:missing_value = "-999" ;
Pres:_FillValue = -999.f ;
Pres:valid_range = 0.f, 1200.f ;
float Ref(MSL_alt) ;
Ref:long_name = "Analyzed refractivity, consistent with Temp, Vp, and Pres" ;
Ref:units = "N" ;
Ref:missing_value = "-999" ;
Ref:_FillValue = -999.f ;
Ref:valid_range = 0.f, 500.f ;
float Lat(MSL_alt) ;
Lat:long_name = "latitude, -90 to 90 north" ;
Lat:units = "deg" ;
Lat:missing_value = "-999" ;
Lat:_FillValue = -999.f ;
Lat:valid_range = -90.f, 90.f ;
float Lon(MSL_alt) ;
Lon:long_name = "longitude, -180 to 180 east" ;
Lon:units = "deg" ;
Lon:missing_value = "-999" ;
Lon:_FillValue = -999.f ;
Lon:valid_range = -180.f, 180.f ;
float Ref_obs(MSL_alt) ;
Ref_obs:long_name = "Observed refractivity, interpolated from the atmPrf file" ;
Ref_obs:units = "N" ;
Ref_obs:missing_value = "-999" ;
Ref_obs:_FillValue = -999.f ;
Ref_obs:valid_range = 0.f, 500.f ;

// global attributes:
:occ_id = 1 ;
:fiducial_id = ;
:reference_sat_id = 10 ;
:occulting_sat_id = 5 ;
:start_time = 674266226. ;
:stop_time = 674266292.460002 ;
:year = 2001 ;
:month = 5 ;
:day = 19 ;
:hour = 0 ;
:minute = 10 ;
:second = 26. ;
:lat = -82.9064833516599 ;
:lon = -22.7066976024562 ;
:fileStamp = "CHAM.2001.139.00.10.G05" ;
:center = "UCAR/CDAAC" ;
:bad = "0" ;
:errstr = ;
:Znh = -999. ;
:Znh2 = -999. ;

who said...

Hi zola, sorry for late reply. I was busy in the past few weeks.

In my case, I have a 4D variable "tmask", one time record, 46 levs, 400 in y (latitude) and 568 in x (longitude).

Make sure the dimensions in RangeStr are the same order as that you get using ncdump. If you are using netcdf.getvar() directly, it returns something with dimensions in a reverse order.

ShowCalendar