Vous êtes sur la page 1sur 15

%% X-ray CT DICOM image processing script for permafrost samples % Written by W. R. Clavano, 15 Dec 2010.

% Provided as supplementary material to the paper entitled "Quantitative % processing of X-ray CT images of permafrost cores for cryostratigraphy: % limitations and prospects" submitted by W. R. Clavano, F. Calmels, and % D. G. Froese to the journal Permafrost and Periglacial Processes (DOI: % 10.1002/ppp.xxx). %% NOTES % Lines that need revising for user-specific information are marked with % *** at the end of the line. % Although default values are given, the user might find it necessary to % redefine a few values. Lines are marked with +++ where this is an % option. %% PRE-PROCESSING % Directory pointers % Point to the DICOM subdirectory containing the "series" set to process datadir = ['/Volumes/Parsifal Expansion/Backups.backupdb', ... '/wrclavano.parsifal/2009-10-11-003156/Parsifal/Users', ... '/wrclavano/Desktop/Fabrice/CTSCAN/DVD22/DICOM/PA000000/ST000000/', ... 'SE000000/']; % *** imfiles = dir([datadir,'/IM*']); % Notice that in DICOM format, the directory structure stands for % PA, patient, % ST, study, % SE, series, and % IM, image. % Each slice in the series will contain a 512x512 image, % and a series will have about 1,500 slices. % Specify where processing results are to be saved. Create a folder % if it already does not exist. savedir = '/Users/wrclavano/Desktop/Permafrost/Natural41X/'; % *** % Uncomment the following line to execute... % mkdir(savedir) % The working directory is the current directory where the function % or script files are stored. This is ideally located in a computer's % internal storage, i.e., not on an external drive. workdir = '/Users/wrclavano/Desktop/Permafrost/'; % *** % Make the named working directory the current one. cd(workdir) % SLICE SEQUENCE % Determine slice sequence

for m = 1:length(imfiles), info = dicominfo([datadir,imfiles(m).name]); sliceloc(m) = info.SliceLocation; slicenum(m) = info.InstanceNumber; clear info end clear m [b,sliceix] = sort(slicenum); clear b save([savedir,'slicesequence'],'sliceloc','slicenum','sliceix') clear slice* % VERTICAL PROFILES (RAW IMAGES) % Create center profile slice to provide first approximation of cutoffs IM1 = zeros(length(imfiles),512); IM2 = zeros(size(IM1)); for k = 1:length(imfiles), info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); clear info IM1(k,:) = I(:,256); % +++ IM2(k,:) = I(256,:); % +++ clear I end save([savedir,'crossprofile'],'IM1','IM2') clear IM* % Uncomment following lines to execute... % figure, % imshow(IM1,[]) % INITIAL THRESHOLD FOR BOUNDARY DELINEATION % Find the ice-air threshold load([savedir,'crossprofile'],'IM1') ab = IM1; % +++ nrows = size(ab,1); ncols = size(ab,2); ab = reshape(ab,nrows*ncols,1); nColors = 3; % +++ [cluster_idx cluster_center] = kmeans(ab,nColors,'distance', ...

'sqEuclidean','Replicates',nColors + 1,'display','iter'); clear ab nColors pixel_labels = reshape(cluster_idx,nrows,ncols); clear cluster_idx nrows ncols [sortc,idc] = sort(cluster_center); clear cluster_center sortc % Check that the selected cluster is appropriate, otherwise revise: ice_air = [min(IM1(pixel_labels==idc(2))),max(IM1(pixel_labels==idc(2)))] % +++ clear IM1 pixel_labels idc save([savedir,'ice_air'],'ice_air') clear ice_air % DETERMINE OBJECT GEOMETRY % Initialize matrix to store slice geometry b = zeros(length(imfiles),4); for k = 1:length(imfiles); clc display(['Processing... ',num2str(k/length(imfiles)*100,'%2.0f'),'%']); clear ans info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); [BW,BWedge] = shrinkWrap2D(I,'thresh',ice_air(1)); clear BWedge I info L = bwlabel(BW); s = regionprops(L,{'Area','Centroid','Perimeter'}); [maxA,id] = max([s.Area]); if ~isempty(s) b(k,1) = s(id).Area; b(k,2:3) = s(id).Centroid; b(k,4) = s(id).Perimeter; end clear L s maxA id save([savedir,'IM',num2str(k - 1,'%06.0f'),'SW'],'BW') clear BW heapTotalMemory = java.lang.Runtime.getRuntime.totalMemory heapFreeMemory = java.lang.Runtime.getRuntime.freeMemory if(heapFreeMemory < (heapTotalMemory*0.7)) java.lang.Runtime.getRuntime.gc; end

clear heap* end clear sliceix k ice_air save([savedir,'geometry'],'b') clear b clear java %% PROCESSING % VERTICAL PROFILES (INDEXED IMAGES: BACKGROUND) BWfiles = dir([savedir,'IM*SW*']); BW1 = zeros(length(BWfiles),512); BW2 = zeros(size(BW1)); for k = 1:length(BWfiles), clear BW load([savedir,'IM',num2str(k - 1,'%06.0f'),'SW']) BW1(k,:) = BW(:,256); % +++ BW2(k,:) = BW(256,:); % +++ end clear BW % Uncomment the following lines to execute... % figure, % imshow(BW1) save('-append',[savedir,'crossprofile'],'BW1','BW2') clear BW* % BLOCK SELECTION area_thresh = 0.10; % +++ max_area = max(b(:,1)); area_thresh = area_thresh*max_area; area_id = ones(size(b(:,1))); area_id(b(:,1)<area_thresh) = 0; bdist = sqrt(diff(b(:,2)).^2 + diff(b(:,3)).^2); cent_thresh = 0.1*512/2; % +++ cent_id = ones(size(bdist)); cent_id(bdist>cent_thresh) = 0; cent_id = [cent_id;0];

b4 = b(:,4); b4(b4>1) = -1; rnd_thresh = 0.5; % +++ roundness = 4*pi*b(:,1)./b(:,4).^2; rnd_id = ones(size(b(:,4))); rnd_id(roundness<rnd_thresh) = 0; % Uncomment either lines to execute... n_id = area_id; % n_id = area_id + cent_id + rnd_id; n_id(n_id<2) = 0; n_id(n_id>1) = 1; % % % % % % % % Uncomment the following lines to execute... figure, subplot(311) plot(b(:,1)) subplot(312) plot(n_id) subplot(313) plot(diff(n_id),'r')

n_id = diff(n_id); id1 = find(n_id==1); id2 = find(n_id==-1); id1 = id1 + 1; sections = [id1,id2]'; sections = [length(id1);sections(:)]; clear id1 id2 n_id area_id cent_id rnd_id clear bdist max_area clear roundness b4 cent_thresh rnd_thresh area_thresh save([savedir,'sections'],'sections') % clear sections % SELECT BLOCK TO PROCESS sec = 5; rng = sections(2*sec):sections(2*sec + 1); % VIEW BLOCK HISTOGRAM ab = IM1(rng,:); nrows = size(ab,1); ncols = size(ab,2); % Use figure to select initial cluster centers... figure, hist(ab(:),80)

ab = IM1(rng,:); id = find(BW1(rng,:)==1); abmin = min(ab(id)) abmax = max(ab(id)) id = find(BW1(rng,:)==0); ab(id) = min(-1000,abmin); % +++ ab = reshape(ab,nrows*ncols,1); % Uncomment either set of lines to execute... % nColors = 5; % +++ % [cluster_idx cluster_center] = kmeans(ab,nColors,'distance', ... % 'sqEuclidean','Replicates',nColors + 1,'display','iter'); % +++ nColors = 6; % +++ [cluster_idx cluster_center] = kmeans(ab,nColors,'distance', ... 'sqEuclidean','Start',[-988,-114,236,847,1547,4000]', ... 'display','iter'); % +++ clear ab nColors ab = IM1(rng,:); pixel_labels = reshape(cluster_idx,nrows,ncols); clear cluster_idx nrows ncols % Uncomment the following lines to execute... % figure, % imshow(pixel_labels,[]) [sortc,idc] = sort(cluster_center); clear cluster_center sortc centers{sec} = sortc; save([savedir,'centers'],'centers') % SEGMENT HISTOGRAM: IDENTIFY CLASSIFIERS ice_gas = min(ab(pixel_labels==idc(2))) % *** ice_sol = max(ab(pixel_labels==idc(3))) % *** clear ab sortc idc pixel_labels cutoffs{sec} = [ice_gas,ice_sol]; save([savedir,'cutoffs'],'cutoffs') % clear cutoffs % IMAGE MASK crat = 0.75; % +++ theta = 0:0.01:2*pi; x = 512/2*crat*cos(theta) + 256.5; % +++ y = 512/2*crat*sin(theta) + 256.5; % +++ clear theta crat

BC = poly2mask(x,y,512,512); % +++ clear x y % CLASSIFICATION % Initialize pixel count matrix V = zeros(numel(imfiles),4); for k = rng; info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); load([savedir,'IM',num2str(k - 1,'%06.0f'),'SW']) BW = BW&BC; CW = zeros(size(BW)); id = find(I<cutoffs{sec}(1) & BW==1); CW(id) = 1; id = find(I>=cutoffs{sec}(1) & I<=cutoffs{sec}(2) & BW==1); CW(id) = 2; id = find(I>cutoffs{sec}(2) & BW==1); CW(id) = 3; clear I SW id V(k,:) = [numel(find(CW==1)),numel(find(CW==2)), ... numel(find(CW==3)),numel(find(BW==1))]; save([savedir,'IM',num2str(k - 1,'%06.0f'),'CW'],'CW') clear CW end save([savedir,'volumes'],'V') % clear V %% POST-PROCESSING % DETERMINE GLOBAL BOUNDING BOX bbox = 512/2*ones(1,4); for m = 1:numel(rng); k = rng(m); load([savedir,'IM',num2str(k - 1,'%06.0f'),'SW']) BW = BW&BC; BW = bwlabel(BW); s = regionprops(BW,{'BoundingBox'}); s = reshape([s.BoundingBox],[2,2]); s = [s(:,1),sum(s,2)];

bbox(1) bbox(2) bbox(3) bbox(4)

= = = =

min(bbox(1),s(1,1)); max(bbox(2),s(1,2)); min(bbox(3),s(2,1)); max(bbox(4),s(2,2));

end clear m k s BW save([savedir,'bbox5'],'bbox') load([savedir,'bbox5'],'bbox') bbox = floor([bbox(1) - 10,bbox(2) + 10,bbox(3) - 10,bbox(4) + 10]); % +++ rows = bbox(3):bbox(4); cols = bbox(1):bbox(2); clear bbox % CONSTRUCT HISTOGRAM IMAGE hext = NaN(1,2); for k = rng; info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); load([savedir,'IM',num2str(k - 1,'%06.0f'),'CW'],'CW') hext(1) = nanmin(min(double(I(CW~=0))),hext(1)); hext(2) = nanmax(max(double(I(CW~=0))),hext(2)); clear CW I info end clear k save([savedir,'hext'],'hext') % clear hext hbins = linspace(hext(1),hext(2),2^8); % +++ H = zeros(numel(rng),numel(hbins)); n = 1; for k = rng; info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); load([savedir,'IM',num2str(k - 1,'%06.0f'),'CW'],'CW') H(n,:) = hist(I(CW~=0),hbins); clear CW

n = n + 1; end hbins1 = linspace(hext(1),2100,2^8); % +++ H1 = zeros(numel(rng),numel(hbins1)); n = 1; for k = rng; info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); load([savedir,'IM',num2str(k - 1,'%06.0f'),'CW'],'CW') H1(n,:) = hist(I(CW~=0),hbins1); clear CW n = n + 1; end save([savedir,'histograms'],'H','hbins','H1','hbins1') % clear H* % VERTICAL PROFILES (CLASSIFIED IMAGES) CW1 = zeros(numel(rng),512); CW2 = zeros(size(CW1)); for n = 1:numel(rng), k = rng(n); load([savedir,'IM',num2str(k - 1,'%06.0f'),'CW'],'CW') CW1(n,:) = CW(:,256); CW2(n,:) = CW(256,:); clear I end save('-append',[savedir,'crossprofile'],'CW1','CW2') % clear CW* % CREATE FIGURES FOR SECTION % Axes for vertical profile % *** y = rng(1)*0.2929; % [mm] upside down core; bottom at 529mm y = y/10; % [cm] y = 223 - y; % bottom of the section is at 223cm. yticks = round((1911 + 170*[0,1,2,3,4]));

x = (cols(1):cols(end))*0.3/10; x = x - x(1); xscale = 2; % [cm] bar length xscale = xscale/0.3130*10; % number of pixels in the bar % Raw image figure, set(gcf,'Color','w') imagesc(IM2) axis equal set(gca,'YDir','normal') hold on, % plot([cols(1),cols(40)],2021*[1,1],'w') % plot([cols(end) - 39,cols(end)],2021*[1,1],'w') plot([cols(1) + 10,cols(1) + 10 + xscale],(rng(end) - 10)*[1,1], ... 'Color','w','LineWidth',3) text(cols(1) + 10,rng(end) - 30,'2cm','Color','w') set(gca,'YTick',yticks) set(gca,'YTickLabel','220|215|210|205|200') set(gca,'XTick',[]) set(gca,'XLim',[cols(1),cols(end)]) set(gca,'YLim',[rng(1) - 10,rng(end) + 10]) set(gcf,'Units','inches') get(gcf,'Position') set(gcf,'Position',[9,1,2,5]) set(gca,'Units','inches') get(gca,'Position') set(gca,'Position',[0.30,0.25,1.5,4.5]) colormap(gray(24)) title('Raw grayscale image') set(gcf,'PaperPositionMode','auto') set(gcf,'Renderer','opengl') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-dpng','-r600','-loose','-opengl',[savedir,'rawimage5']) % Classified image CW2a = [zeros(10,512);CW2;zeros(10,512)]; figure, set(gcf,'Color','none') imshow(CW2a(:,cols),[]) set(gca,'YDir','normal')

set(gcf,'Units','inches') get(gcf,'Position') set(gcf,'Position',[9,1,2,5]) hold on, plot(82,557,'k*','MarkerSize',4) set(gca,'Units','inches') get(gca,'Position') set(gca,'Position',[0.25,0.25,1.5,4.5]) title('Classified image') set(gcf,'PaperPositionMode','auto') set(gcf,'Renderer','opengl') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-dpng','-r600','-loose','-opengl',[savedir,'classimage5']) % Gas volume content figure, set(gcf,'Color','none') plot(V(rng,1)./V(rng,4),rng,'k') set(gca,'YLim',[rng(1) - 10,rng(end) + 10]) set(gcf,'Units','inches') get(gcf,'Position') set(gcf,'Position',[9,1,2,5]) set(gca,'Units','inches') get(gca,'Position') set(gca,'Position',[0.25,0.25,1.5,4.5]) hold on, set(gca,'XTick',[0.005,0.010,0.015]) set(gca,'XTickLabel',[0.5,1,1.5]) set(gca,'XLim',[0,0.02]) % plot(0.019,2356,'k*') set(gca,'YTick',[]) title('Gas volume [%]') set(gcf,'PaperPositionMode','auto') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-deps2','-r600','-loose',[savedir,'gasvolume5']) % Ice volume content figure, set(gcf,'Color','none') plot(V(rng,2)./V(rng,4),rng,'k') set(gca,'YLim',[rng(1) - 10,rng(end) + 10]) set(gcf,'Units','inches')

get(gcf,'Position') set(gcf,'Position',[9,1,2,5]) set(gca,'Units','inches') get(gca,'Position') set(gca,'Position',[0.25,0.25,1.5,4.5]) hold on, plot(0.25*[1,1],get(gca,'YLim'),'Color',0.5*[1,1,1],'LineStyle','--') plot(0.5*[1,1],get(gca,'YLim'),'Color',0.5*[1,1,1],'LineStyle','--') plot(0.75*[1,1],get(gca,'YLim'),'Color',0.5*[1,1,1],'LineStyle','--') set(gca,'XTick',[0.25,0.5]) set(gca,'XTickLabel',[25,50]) set(gca,'XLim',[0,0.75]) set(gca,'YTick',[]) title('Ice volume [%]') set(gcf,'PaperPositionMode','auto') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-deps2','-r300','-loose',[savedir,'icevolume5']) % Histogram image H2 = H1./repmat(sum(H1,2),[1,size(H1,2)]); H2 = log1p(H2); % +++ H2 = H2/max(H2(:)); H2 = [zeros(10,256);H2;zeros(10,256)]; % +++ figure, set(gcf,'Color','none') imagesc(hbins1,rng,H2) set(gca,'YDir','normal') hold on, set(gcf,'Units','inches') get(gcf,'Position') set(gcf,'Position',[9,1,2,5]) set(gca,'Units','inches') get(gca,'Position') set(gca,'Position',[0.25,0.25,1.5,4.5]) set(gca,'YTick',[]) set(gca,'XTick',[-750,0,750,1500]) ylim = get(gca,'YLim'); % plot([-750,0,750,1500],ylim(1)*[1,1,1,1],'k+') % plot([centers{sec}(1:4)],ylim(2)*[1,1,1,1],'w+') title('CT number [HU] histogram') colormap(gray(24))

set(gcf,'PaperPositionMode','auto') set(gcf,'Renderer','opengl') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-dpng','-r600','-loose','-opengl',[savedir,'histprofile5']) % Section histogram figure, stairs(hbins,sum(H,1)./max(sum(H,1)),'k') set(gcf,'Color','none') set(gcf,'Units','inches') set(gcf,'Position',[1,1,4,3]) hold on, sortc = centers{sec}; for m = 1:numel(sortc), h1 = plot(sortc(m),1,'*','Color',0.5*[1,1,1]) end h2 = plot(ice_air(1)*[1,1],[0,1],'Color',0.5*[1,1,1],'LineStyle','--'); plot(ice_air(2)*[1,1],[0,1],'Color',0.5*[1,1,1],'LineStyle','--') hleg = legend([h1,h2],{'Cluster centers','Ice cutoffs'}); set(hleg,'Box','off') xlabel('CT Number [HU]') ylabel('Normalized Frequency') axis tight set(gcf,'PaperPositionMode','auto') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-deps2','-r300','-loose',[savedir,'histogram5']) % SAMPLE SLICE % Select slice k = 2021; % Load images info = dicominfo([datadir,'IM', ... num2str(sliceix(k) - 1,'%06.0f')]); I = double(dicomread(info)); load([savedir,'IM',num2str(k - 1,'%06.0f'),'SW']) load([savedir,'IM',num2str(k - 1,'%06.0f'),'CW']) BW = BW&BC; BW = bwlabel(BW); s = regionprops(BW,'BoundingBox'); bbox = s.BoundingBox; bbox = reshape(bbox,[2,2]);

bbox = [bbox(:,1),sum(bbox,2)]; bbox = [bbox(:,1) - 10,bbox(:,2) + 10]; % columns, then rows bbox = floor(bbox); cols = bbox(1):bbox(3); rows = bbox(2):bbox(4); I1 = I; I1(BW==0) = hext(1); I1 = I1(rows,cols); % Raw image figure, imshow(I(rows,cols),[]) caxis([hext(1),hext(2)]) set(gcf,'Units','inches') set(gca,'Units','inches') set(gcf,'Position',[1,1,3.5,3.5]) set(gca,'Position',[0.25,0.25,3,3]) set(gcf,'Renderer','zbuffer') set(gcf,'PaperPositionMode','auto') refresh(gcf) xlim = round(get(gca,'XLim')); ylim = round(get(gca,'YLim')); hold on, plot([xlim(1) + 10,xlim(1) + 10 + xscale],(ylim(end) - 10)*[1,1], ... 'Color',0.99*[1,1,1],'LineWidth',4) text(xlim(1) + 10,ylim(end) - 20,'2cm','Color',0.99*[1,1,1],'FontSize',12) % Uncomment the following line to execute... % print(gcf,'-dpng','-r300','-loose','-opengl',[savedir,'slice_rawimage41']) % Raw image (scaled, gray) figure, imshow(I1,[]) colormap(gray(16)) set(gcf,'Units','inches') set(gca,'Units','inches') set(gcf,'Position',[1,1,3.5,3.5]) set(gca,'Position',[0.25,0.25,3,3]) set(gcf,'Renderer','zbuffer') set(gcf,'PaperPositionMode','auto') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-dpng','-r300','-loose',[savedir,'slice_grayscale41']) % Classified image figure, imshow(CW(rows,cols),[])

set(gcf,'Units','inches') set(gca,'Units','inches') set(gcf,'Position',[1,1,3.5,3.5]) set(gca,'Position',[0.25,0.25,3,3]) set(gcf,'Renderer','zbuffer') set(gcf,'PaperPositionMode','auto') refresh(gcf) % Uncommen the following line to execute... % print(gcf,'-dpng','-r300','-loose',[savedir,'slice_classimage41']) % Raw image (scaled, red-blue) cmin = min(min(I1)); cmax = max(max(I1)); numclevels = 11; J = ceil((numclevels)*(I1 - cmin)./(cmax - cmin)) + 1; figure, imshow(J,[]) colormap(redbluecmap) set(gcf,'Units','inches') set(gca,'Units','inches') set(gcf,'Position',[1,1,3.5,3.5]) set(gca,'Position',[0.25,0.25,3,3]) set(gcf,'Renderer','zbuffer') set(gcf,'PaperPositionMode','auto') refresh(gcf) % Uncomment the following line to execute... % print(gcf,'-dpng','-r300','-loose',[savedir,'slice_colorscale41'])

Vous aimerez peut-être aussi