classdef SalsaSpectrum max(obj.vel) ind = getIndices(obj,vind,coord); order = input('Input polynomial order: '); % arguments given elseif nargin > 1 if mod( length(varargin{1}), 2 ) == 1 ME = MException('SalsaSpectrum:fitBaseline',['You ' ... 'must give an even number ' ... 'of values defining the ' ... 'baseline windows']); throw(ME); end ind = varargin{1}; order = 1; if length(varargin) == 1 coord = 'pix'; elseif length(varargin) == 2 if ~ischar(varargin{2}) order = varargin{2}; coord = 'pix'; else coord = varargin{2}; ind = getIndices(obj,ind,coord); end elseif length(varargin) == 3 coord = varargin{2}; order = varargin{3}; ind = getIndices(obj,ind,coord); end end % end check length of input obj.baseWindowParInd = ind; obj.baseWindowParFreq = getIndices(obj,ind,'pixfreq'); obj.baseWindowParVel = sort( getIndices(obj,ind,'pixvel') ); ind=sort(ind); indices = []; for i = 1:2:length(ind) indices = [indices ind(i):ind(i+1)]; %#ok end obj.baseWindow = indices; % fit the baseline to the indices basePol = polyfit( obj.index(indices), ... obj.data(indices), order); obj.baseLine = polyval(basePol, obj.index); obj.getRms(); if nargin == 1 obj.showBaseline end end function back = getIndices(obj,ind,coord) % help function for the fitBaseline function if strcmp(coord, 'vel') % from velocities to pixels back = round( interp1(obj.vel, obj.index, ind) ) ; elseif strcmp(coord,'freq') % from frequency to pixels back = round( interp1(obj.freq, obj.index, ind ) ); elseif strcmp(coord, 'pixvel') % from pixels to velocities back = interp1(obj.index, obj.vel, ind); elseif strcmp(coord, 'pixfreq') % from pixels to frequencies back = interp1(obj.index, obj.freq, ind); end end function showBaseline(obj) % SHOWBASELINE(obj) % Show the fitted baseline overlaid on the spectrum, as well as boxes % indicating the baseline windows. % % spec.showBaseline() % % This function does not subtract the baseline. Use the % subtractBaseline function to do that. if ~isempty(obj.baseWindow) obj.plot(); hold on handle1=plot(obj.vel, obj.baseLine, 'r'); set(handle1, 'linewidth', 1); obj.showBaselineWindows(); else disp('You must fit a baseline before using showBaseline') end end function subtractBaseline(obj) % SUBTRACTBASELINE(obj) % % Subtract the fitted baseline from the data. if isempty(obj.baseLine) ME = MException('SalsaSpectrum:subtractBaseline', ... ['You must fit a baseline before ' ... 'using subtractBaseline']); throw(ME); elseif obj.baseSubtracted ME = MException('SalsaSpectrum:subtractBaseline', ... 'A baseline has already been subtracted'); throw(ME); else obj.data = obj.data - obj.baseLine; obj.getRms; obj.baseSubtracted = 1; end end function fitGaussiansInteractive(obj) % FITGAUSSIANSINTERACTIVE(obj) % % Wrapper function for the fitGaussians function. Lets the user % define peaks in the spectrum with the mouse and sends those % peaks as guesses to the fitGaussians function that does the % actual fitting. disp(['Mark each peak in the spectrum with the mouse.']) disp('Press return when you are finished.'); obj.plot('vel'); % loop until return is pressed i = 1; while 1 [tmpv tmpd] = ginput(1); if tmpv ~= 0 dataOnLine = mean(obj.data(obj.getIndices ... (tmpv+[-2 0 2],'vel'))); aa=plot(tmpv, dataOnLine, 'ro'); set(aa,'markerfacecolor', 'r','markeredgecolor', 'r'); vind(i) = tmpv; % velocities dind(i) = dataOnLine; % data values i = i + 1; else break end end % assume a line velocity standard deviation of about 8 km/s vsigma = 8; par = [dind vind repmat(vsigma,1,length(dind))]; ngauss = length(par)/3; order = []; if ngauss > 1 for i = 1:ngauss order = [order [i:ngauss:(i+2*ngauss)]]; end elseif ngauss == 1 order = [1 2 3]; end % send to fitGaussians to do the fitting obj.fitGaussians(par(order)); end function fitGaussians(obj, varargin) % FITGAUSSIANS(obj,varargin) % Fit a number of gaussians to the spectrum. Supply guess % values of height, central value and width of each % gaussian, in units of velocity. % % spec.fitGaussians([60 0 8 30 -55 10]) % % If you don't supply any guess, the function will search % for peaks in the spectrum and use those as starting % guesses for the fit. If the fit is not good, you can % redo it with a new guess. % % If you want to add gaussians to a fit, call this % function again with guesses for the new gaussian, and % send a second dummy argument, like % % spec.fitGaussians([60 -10 10],'dummy') % % which will add an extra gaussian of peak 60 K, central % velocity -10 km/s and velocity width 10 km/s to the fit. if nargin == 1 % temporary smooth the data by a factor of 3 os = 3; ind = obj.index(1):os:obj.index(end); velsmooth = obj.getIndices(ind,'pixvel'); datasmooth = interp1(obj.index, smooth(obj.data,os+1), ... ind); % a velocity difference between peaks of 10 km/s is assumed deltav = diff(velsmooth); minpeakdistance = ceil(abs(10/deltav(1))); if minpeakdistance < 2 minpeakdistance = 2; end % suppress warnings from findpeaks warning('off', 'signal:findpeaks:largeMinPeakHeight'); [hpeak indpeak] = findpeaks(datasmooth, 'minpeakheight', ... 8, 'minpeakdistance', ... minpeakdistance, 'threshold',0.3); % break if no peaks found if isempty(indpeak) disp('No peaks found, exiting.') return; end indpeak = indpeak*os; %% exclude the negative peak sometimes present around 200 - 225 pixels. %indremove = obj.getIndices(-130,'vel'); %badpeak = find(indpeak>indremove); % %if ~ismember(badpeak,[]) % indpeak(badpeak) = []; % hpeak(badpeak) = []; %end % %% exclude everything above 220 km/s (there is never any real signal %% there). %indremove = obj.getIndices(130,'vel'); %badpeak = find(indpeak= 2 tmp = varargin{1}; pcentral = obj.getIndices(tmp(2:3:end),'vel'); psigma = tmp(3:3:end) / abs(min(diff(obj.vel))); par = [tmp(1:3:end) pcentral abs(psigma)]; ngauss = length(par)/3; order = []; if ngauss > 1 for i = 1:ngauss order = [order [i:ngauss:(i+2*ngauss)]]; end elseif ngauss == 1 order = [1 2 3]; end par = par(order); if nargin == 2 % if guesses have been supplied. guess = par; elseif nargin == 3 %tmp = obj.getIndices(tmp,'vel'); guess = [obj.gaussPar par]; end end if mod( length(guess), 3 ) ~= 0 % number of guesses % must be divisible by three ME = MException('SalsaSpectrum:fitGaussians', ... ['You must supply 3 guess values ' ... 'for each Gaussian']); throw(ME); end ngauss = length(guess)/3; if ngauss > 5 sprintf(['SalsaSpectrum:fitGaussians: at most five ' ... 'gaussians can be fitted simultaneously. ' ... 'Discarding additional guesses']) guess = guess(1:15); ngauss = 5; end gaussFunc = @(par,ind) (par(1) .* exp( -1/2 * ( ind ... - par(2) ).^2 ./ par(3)^2 )); % do the fitting (should come up with a nicer way to define the % function) NOTE if ngauss == 1 gauss = @(x, ind) gaussFunc( x(1:3), obj.index); elseif ngauss == 2 gauss = @(x, ind) (gaussFunc( x(1:3), obj.index) + ... gaussFunc( x(4:6), obj.index )); ... elseif ngauss == 3 gauss = @(x, ind) (gaussFunc( x(1:3), obj.index) + ... gaussFunc( x(4:6), obj.index) + ... gaussFunc( x(7:9), obj.index)); elseif ngauss == 4 gauss = @(x, ind) (gaussFunc( x(1:3), obj.index) ... + gaussFunc( x(4:6), obj.index) ... + gaussFunc( x(7:9), obj.index) ... + gaussFunc( x(10:12), obj.index)); elseif ngauss == 5 gauss = @(x, ind) (gaussFunc( x(1:3), obj.index) ... + gaussFunc( x(4:6), obj.index) ... + gaussFunc( x(7:9), obj.index) ... + gaussFunc( x(10:12), ... obj.index) + ... gaussFunc( x(13:15), obj.index)); ... end % make the fit % NOTE % % nlinfit - faster % lsqcurvefit - slower, but probably more accurate and upper % and lower bounds can be given. if obj.fittype == 1 [fit, rw, ~, covb]=nlinfit(obj.index, obj.data, ... gauss, guess); ci = nlparci(fit,rw,'covar',covb,'alpha', .32); err = fit - ci(:,1)'; [ypred delta] = nlpredci(gauss, obj.index, fit, rw, ... 'covar', covb, 'alpha', .32); else % lower bounds for lsqcurvefit. options = optimset('Display','off'); lb = repmat([0 -inf 0], 1, ngauss); [fit, ~, residual, ~, ~, ~, jacobian] = ... lsqcurvefit(gauss, guess, obj.index, obj.data, lb, [], options); sigmar = 1./ (length(obj.index)-ngauss) * sum(residual.^2); covb = full(sigmar*inv(jacobian'*jacobian)); err = sqrt(diag(covb)); [ypred delta] = nlpredci(gauss, obj.index, fit, residual, ... 'covar', covb, 'alpha', .32); end obj.gaussFit = gauss(fit, obj.index); obj.gaussPar = fit; obj.gaussErr = err'; % calculate fit parameters in velocity units vCentral = obj.getIndices(fit(2:3:end),'pixvel'); deltav = abs(min(diff(obj.vel))); vSigma = fit(3:3:end) * deltav; vPar = [fit(1:3:end) vCentral abs(vSigma)]; vErrPar = [err(1:3:end) err(2:3:end)*deltav abs(err(3:3:end))*deltav]; order = []; if ngauss > 1 for i = 1:ngauss order = [order [i:ngauss:(i+2*ngauss)]]; end elseif ngauss == 1 order = [1 2 3]; end obj.gaussParVel = vPar(order); obj.gaussErrVel = vErrPar(order); % calculate fit parameters in frequency units freqCentral = obj.getIndices(fit(2:3:end),'pixfreq')/1e6; deltafreq = abs(min(diff(obj.freq)))/1e6; freqSigma = fit(3:3:end) * deltafreq; freqPar = [fit(1:3:end) freqCentral abs(freqSigma)]; freqErrPar = [err(1:3:end) err(2:3:end)*deltafreq abs(err(3:3:end))*deltafreq]; order = []; if ngauss > 1 for i = 1:ngauss order = [order [i:ngauss:(i+2*ngauss)]]; end elseif ngauss == 1 order = 1:3; end obj.gaussParFreq = freqPar(order); obj.gaussErrFreq = freqErrPar(order); obj.gaussiansFitted = 1; obj.residuals = obj.data - obj.gaussFit; % Older matlab versions need different syntax than new if verLessThan('matlab', '8') obj.gaussConfInt = [ypred'-delta ypred'+delta]; else obj.gaussConfInt = [ypred-delta ypred+delta]; end % integrate the gaussian functions to obtain the integrated % intensity in K km/s for i = 1:ngauss ii = i*3-2; obj.gaussIntegrated(i) = quad( @(x) gaussFunc( ... obj.gaussParVel(ii:(ii+2)), x), -300, 300); end sprintf(['%d Gaussians. \nUse plot() to see the ' ... 'fitted Gaussians.'], ngauss) end function getRms(obj) % GETRMS(obj) % % calculate and display the rms of the data contained in the baseline % windows. obj.rms = std( obj.data(obj.baseWindow) ); %sprintf('rms in baseline windows: %5.2f K \n', obj.rms) end function showBaselineWindows(obj) % SHOWBASELINEWINDOWS(obj) % % Plot a graphical representation of the chosen % baseline windows. hold on vind = obj.baseWindowParVel; ind = obj.baseWindowParInd; heightfac = 6; for i = 1:2:length(vind) medval = median( obj.data( ind(i+1):ind(i) ) ); rr=rectangle('position', [vind(i) (-heightfac/2*obj.rms ... + medval) ... abs(vind(i+1)-vind(i)) ... (heightfac*obj.rms)]); set(rr,'edgecolor','g', 'linewidth',1) end end function back = getKeyword(obj, key) % GETKEYWORD(OBJ, KEY) returns the value of KEY from the fits % header OBJ.INFO. % % This function was kindly provided by Magnus Sandén and Eskil % Varenius. keywords=obj.info.PrimaryData.Keywords; r=''; for i=1:length(keywords) if (strcmp(keywords{i,1}, key)) r=keywords{i,2}; end end if (strcmp(r, '')) ME = MException('getKeyword:keywordNotFound', ... 'Keyword not found in fits header.'); throw(ME); end back = r; end function back = showLab(obj) if ~isempty(obj.labVel) handle1 = plot(obj.labVel, obj.labSig, 'c-.'); set(handle1, 'color', [.7 0 .7]); back = handle1; else sprintf(['LAB data not downloaded yet. Use the ' ... 'readLab function to download LAB data']); end % a end % end function showLab function handle = showConfInt(obj,varargin) % SHOWCONFINT(obj) % % If gaussians have been fitted, show the calculated 68% % confidence interval on the current graph. % % by default, velocity units are used. Supply 'freq' och 'pix' % for frequency or pixel units instead. if nargin > 1 coord = varargin{1}; else coord = 'vel'; end obj.plot(coord); hold on xx = [obj.vel fliplr(obj.vel)]; yy = [obj.gaussConfInt(:,1)' fliplr(obj.gaussConfInt(:,2)')]; size(xx) size(yy) hh = fill(xx,yy,'k'); greycol = .75*[1 1 1]; set(hh, 'facecolor', greycol, 'edgecolor', greycol); uistack(hh,'bottom'); handle = hh; end function readLab(obj,varargin) % READLAB(obj) % % download data from the LAB survey at % http://www.astro.uni-bonn.de/hisurvey/profile/index.php % % by default, convolved to Salsa's angular resolution. If you % want higher angular resolution, supply the value (in % degrees) as an argument % if ispc % disp('Sorry, dowloading LAB data does not work on Windows.') % return % end if ~isempty(obj.labVel) disp('LAB data already downloaded and loaded') return end % Theoretical calculation salsares = round( (1.22 * 0.21 / 2.3) * 180/pi * 10 ... ) / 10; % Specify resolution %salsares = 6.4; % dowload spectra with resolution given by user. if nargin == 2 salsares = varargin{1}; end glon = obj.getKeyword('CRVAL2'); glat = obj.getKeyword('CRVAL3'); % check if the correct lab data has already been downloaded if exist('lab.txt','file') == 2 fid = fopen('lab.txt', 'r'); % read the first line and check the coordinates % there. Check first if the file is empty. tline = fgetl(fid); if (tline==-1) % the file is empty, download it again. download = 1; fclose(fid); else coords = sscanf(tline, '%%%% %f %f %f %f'); fclose(fid) if [(round(coords(1)) == round(glon)) && (round(coords(2)) == ... round(glat))] % The correct file is already downloaded. download = 0; disp('correct file already downloaded'); else % Download the file. download = 1; end % check if correct coordinates end % check if the file is empty else download = 1; end % check if the correct file already exists if download comm1 = 'https://www.astro.uni-bonn.de/hisurvey/profile/download.php?'; comm2 = sprintf('ral=%3.2f\\&decb=%3.2f\\&csys=0\\&beam=%3.2f', ... glon, glat, salsares); url = [comm1,comm2]; sprintf(['Downloading LAB data for galactic longitude ' ... '%d and latitude %d'], glon, glat) try [a, status] = urlwrite(url, 'lab.txt'); catch me disp('Could not download LAB data'); end end fid = fopen('lab.txt','r'); % find the part of the file with the LAB data while 1 tline = fgetl(fid); % find the position in the file of the LAB data if regexp(tline, '%%LAB') break end end i = 1; while ~feof(fid) tline = fgetl(fid); tmp = sscanf(tline, '%f %f'); if ischar(tmp) || isequal(tmp, []) break end lab(:,i) = tmp; i = i + 1; end fclose(fid); lab = lab'; obj.labVel = lab(:,1); obj.labSig = lab(:,2); end function clipSpectrum(obj, window) % BACK = CLIPSPECTRUM(obj,window) % % NOTE: EXPERIMENTAL, USE AT YOUR OWN RISK. % % given the indices in the window parameter, this % function "clips" (removes) the indicated spectral % channels from the data. Several spectral windows can % be specified. Only the 'pixel' spectral unit can be % used. % % spec.clipSpectrum([1 10 230 250]) % % would clip all channels between channels 1 and 10 (1 2 3 % 4 5 6 etc) and between channels 230 and 250. indices = []; for i = 1:2:length(window) indices = [indices window(i):window(i+1)]; end obj.data(indices) = NaN; %obj.vel(indices) = []; %obj.freq(indices) = []; %obj.index(indices) = []; if ~isempty( obj.gaussFit ) obj.gaussFit(indices) = NaN; end end function despike(obj,varargin) % DESPIKE(obj,varargin) % % NOTE: EXPERIMENTAL, USE AT YOUR OWN RISK. % % experimental function to remove 'spikes' in the data. Works well for % the spike at the edge of the spectrum, works % sometimes also for strong RFI in the spectrum. if nargin == 2 n = varargin{1}; lim = 10; elseif nargin == 3 n = varargin{1}; lim = varargin{2}; elseif nargin == 1 n = 10; lim = 10; end % if gaussians have been fitted, exclude that % region of the spectrum from the despiking if obj.gaussiansFitted % Extract indices of the part where the gauss % fit is below some value. A value of 0.01 % appears appropriate. gind = find(obj.gaussFit<0.01); else gind = obj.index; end x = obj.data(gind)'; % extend data vector by n data points at both ends xi = [flipud(x(1:n)); x; flipud(x(end-n+1:end))]; % apply order n median filter xfilt = medfilt1(xi,n); % difference to filtered data dif = abs(xfilt(1+n:end-n)-x); % number of spikes nspike = sum(dif>lim); % replace spikes with NaNs x(dif>lim) = NaN; obj.data(gind) = x'; if obj.gaussiansFitted obj.residuals = obj.data - obj.gaussFit; end end function smoothSpectrum(obj,varargin) % SMOOTH(obj,varargin) % % Smooth a spectrum to a lower spectral resolution. To lower % the resolution by a factor of 2, supply the command % % spec.smooth(2) if nargin == 2 os = varargin{1}; else os = 2; end ind = obj.index(1):os:obj.index(end); vel = obj.getIndices(ind,'pixvel'); freq = obj.getIndices(ind, 'pixfreq'); obj.data = interp1(obj.index, smooth(obj.data,os+1), ... ind); if obj.gaussiansFitted obj.gaussFit = interp1(obj.index, obj.gaussFit, ind); obj.residuals = obj.gaussFit - obj.data; end if obj.baseSubtracted obj.baseLine = interp1(obj.index, obj.baseLine, ind); end obj.index = 1:length(obj.data); obj.vel = vel; obj.freq = freq; if obj.baseSubtracted baseindnew = obj.getIndices(obj.baseWindowParVel,'vel'); obj.baseWindowParInd = baseindnew; baseindnew = sort(baseindnew); indices = []; for i = 1:2:length(baseindnew) indices = [indices baseindnew(i):baseindnew(i+1)]; end obj.baseWindow = indices; end end end % methods end % class