% Calvin Kersbergen 091117 % ckersbe1@jhmi.edu % V1: this script is a fully-automated counting technique for Brn3a-stained % retinal ganglion cells. It increases image contrast, performs a low-pass % filter, thresholds based on the grayscale of the image, and then counts % and determines boundaries of cells. Once counted, the program excludes % cells smaller than 5% of average cell size (arbitrary at this point but seems to be ok % and presents cells greater than 1.7* average for an area analysis, in % which the rounded value of the total "blob" area divided by the average % cell size in the image determines the number of cells contained within % the blob. See OneNote for analysis of efficacy. % 10/3/17 V2: updated to allow for different noise thresholds based on location % and added a manual computation of average cell size. I think that should % solve the cell density/noise problem since noise would decrease the % average cell size and thus decrease the noise cutoff. % 10/23/17 V3: Updates include a built in % manual contrast adjustment to allow user to adjust the contrast oneself % so that if automatic contrast adjustment fails, then user can set the % contrast by increasing dark and decreasing bright. Outputs of the manual % thresholding are exported in the command window and should be included in % the case of repeating measurements. % For center and middle, I kept the Graythresh thresholding (I know it % undercounts slightly) but for periphery the thresholding is more % sensitive, but also more likely to fail. % 10/30/17 Notes: when running on variable intensity, a new problem I've % encountered is the fact that cell size increases even with greythresh % when the brn-3a intensity is very high. I've been manually adjusting % based on when I see good size segmentation and recording, but it isn't % sustainable and will need an additional fix. Not sure how to approach % that yet. % Also, adaptthresh does not work on R2015b. % 11/21/17 V4: During validation, it appears that consistent manual % thresholding is more accurate than the automatic thresholding for center % and middle (questionable for periphery). So therefore I built in noise % thresholds for the center and middle as well. when thresholding, I % recommend keeping low noise, but small amounts of noise will not % significantly impact the counts. If the noise level increases too high, % however, then it will bring down the average cell counts and therefore % overcount by counting single cells as multiple cells. clear all, clc close all %user selects file [fname pname] = uigetfile({'*.tif';'*.TIF';'*.tiff';'*.TIFF'},'select tiff file'); cd(pname) rawImage = imread(fname); % opens image figure imshow(rawImage) % displays raw image to user in Figure 1 title ('raw image') xlabel ('select if from center (c), middle (m), or periphery (p)') fprintf ('select if from center (c), middle (m), or periphery (p)') done = 0; fignum = gcf; while not(done) % selection of area chooses the size threshold the algorithm will use (only for automatic thesholding) waitforbuttonpress pressed = get(fignum, 'CurrentCharacter'); if pressed == 'p' thresholdLow = 0.3; done = 1; fprintf ('\n selected periphery \n') elseif pressed == 'm' thresholdLow = 0.05; done = 1; fprintf ('\n selected middle \n') elseif pressed == 'c' thresholdLow = 0.05; done = 1; fprintf ('\n selected center \n') else beep disp ('\n not a valid key \n') end end % convert to grayscale rawImage = rgb2gray(rawImage); % this may or may not need to be turned on or off depending on the image input (RGB or Uint8) B = wiener2(rawImage, [7 7]); % low pass filter, this is adjustable. 7x7 provides a nice filter for brn3a RGCs if pressed == 'p' %periphery selected for automatic threshold B = imadjust(B,[0.125 0.9],[]); % increases contrast of image (can manipulate inputs, this was what I found that works best) thresHold2 = adaptthresh(B,0.001); % noise threshold is low for periphery - see notes C = imbinarize(B, thresHold2); % see notes about this algorithm avgCellSize2 = 230; %manually set cell size based on speciifc thresholding else % center or middle selected for automatic threshold thresHold = graythresh(B); % greythresh is very strong - probably undercounts but is consistently good C = im2bw(B, thresHold); % see notes about this algorithm avgCellSize2 = 180; %manually set cell size based on specific thresholding. If not accurate, use manual thresholding and it will be calculated based on the input. end hold on figure %shows raw image next to manual thresholding image subplot(1,2,1) imshow(rawImage) title('raw image') subplot(1,2,2) imshow (C) title('thresholded image') xlabel('is this thresholding good? g = good, f = fail') fprintf ('\n is this thresholding good? g = good, f = fail \n') done = 0; done1 = 0; fignum = gcf; while not(done) waitforbuttonpress pressed1 = get(fignum, 'CurrentCharacter'); if pressed1 == 'g' %automatic thresholding is good, advances to counts fprintf( '\n automatic thresholding selected \n') done = 1; elseif pressed1 == 'f' %automatic thresholding failed, need manual input thresholdLow = 0.3; while not(done1) fprintf(' \n manual thresholding selected \n') B1 = B; %manual thresholding figure; imshow(B) xlabel('adjust contrast. press any key once complete') hfig = imcontrast; %opens window to adjust contrast fprintf('\n press any button once complete \n') waitforbuttonpress window_min = str2double(get(findobj(hfig, 'tag', 'window min edit'), 'String')); %saves low threshold window_max = str2double(get(findobj(hfig, 'tag', 'window max edit'), 'String')); %saves high threshold Cnew = imadjust(B1,[window_min/255 window_max/255],[0 1]); % converts from 0-255 to 0-1 thresHold2 = adaptthresh(Cnew,0.1); %thresholding algorithm Cnew = imbinarize(Cnew, thresHold2); %binarizes %convert modified image to workable form subplot(1,3,1) %plots raw, automatic, and manual side by side imshow(rawImage) title('raw image') subplot(1,3,2) imshow(C) title('automatic threshold') subplot(1,3,3) imshow(Cnew) title('manual threshold') xlabel('Is this threshold better? Press "y" if good, "n" to repeat') fprintf('\n Is this threshold better? Press any button to advance \n') fignum1 = gcf; waitforbuttonpress pressed2 = get(fignum1, 'CurrentCharacter'); if pressed2 == 'y' %thresholding good, advances to counts done1 = 1; lowThresh = window_min %displays thresholds used to user highThresh = window_max %displays thresholds used to user elseif pressed2 == 'n' %thresholding not good, loops until user is satisfied done1 = 0; end end done = 1; C = Cnew; end end [B,L,N] = bwboundaries(C,'noholes'); %determines boundaries B = boundary. imgStats = regionprops(C,'area','centroid'); %gives center and area of each enclosed boundary figure; %plots cells with boundaries and labels imshow(C); title('counted cells') xlabel('Red+ = 1 cell. Blue* = multiple cells. green* = removed for size. yellow* = removed for shape') hold on; for k = 1:length(B) plot(imgStats(k).Centroid(1), imgStats(k).Centroid(2),'r+') %plots all cells with red + sign boundary = B{k}; if(k > N) plot(boundary(:,2), boundary(:,1), 'g', 'LineWidth', 2); else plot(boundary(:,2), boundary(:,1), 'r', 'LineWidth', 2); end end % size filter i = 1; if pressed1 == 'f' % if manual thresholding, performs automatic determination of area because not optimized (optimized for automatic already) for k = 1:N if imgStats(k).Area < 400 && imgStats(k).Area > 20 avgCellSize(i) = imgStats(k).Area; i = i + 1; else end end avgCellSize2 = mean(avgCellSize(:)); end count = N; sizeCellremoved = 0; shapeCellremoved = 0; thresholdShape = 0.4; for k = 1:length (B) if pressed == 'p' numCell = 1; if imgStats(k).Area < thresholdLow*avgCellSize2 % cells smaller than threshold count = count - 1; sizeCellremoved = sizeCellremoved + 1; plot(imgStats(k).Centroid(1), imgStats(k).Centroid(2),'g*') elseif imgStats(k).Area > (1.75*avgCellSize2) % cells larger than 1 cell numCell = round(imgStats(k).Area / avgCellSize2); if numCell > 1 count = count + (numCell - 1); plot(imgStats(k).Centroid(1), imgStats(k).Centroid(2),'b*') else end end elseif pressed == 'c' || pressed == 'm' numCell = 1; if imgStats(k).Area < thresholdLow*avgCellSize2 % cells smaller than threshold count = count - 1; sizeCellremoved = sizeCellremoved + 1; plot(imgStats(k).Centroid(1), imgStats(k).Centroid(2),'g*') %plots cells eliminated elseif imgStats(k).Area > (1.75*avgCellSize2) % cells larger than 1 cell numCell = round(imgStats(k).Area / avgCellSize2); if numCell > 1 count = count + (numCell - 1); plot(imgStats(k).Centroid(1), imgStats(k).Centroid(2),'b*') %plots cells split by size segmentation else end end end end if (count/k > 1.5) || sizeCellremoved/N > 0.2 % this appears if there is a lot of noise removed or a lot of "large" cells in the image. fprintf('\n WARNING: high noise in image \n') elseif avgCellSize2 < 160 || avgCellSize2 > 240 fprintf('\n WARNING: check cell size segmentation - cells may be too small or too big\n') else end %scaling % 0.496 um/pixel in all images unless new microscope, new zoom, etc. scaleF = 0.496; scale1 = size(rawImage); areaMM = ((scale1(1)*scaleF)/1000)*((scale1(2)*scaleF)/1000); cellDensityMM = count/areaMM; count cellDensityMM