-
Notifications
You must be signed in to change notification settings - Fork 0
/
morph3D.m
923 lines (816 loc) · 41.7 KB
/
morph3D.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
%% Semi-automatic 3D morphology analysis: 3DMorph
%First run script in interactive mode to generate a parameters file. It may
%then be run in automatic mode (with or without figure outputs) to batch
%process a series of images. *Note: these images must have the same scaling
%factor and use the same user-defined values. Loads .tiff or .lsm z-stack
%images.
%User input: Threshold adjustment, maximum cell size for
%segmentation, minimum cell size to remove out-of-frame processes, and
%skeletonization method.
%Outputs: Before removing small processes: Total territorial (convex)
%volume covered, total empty volume, and percentage of volume covered. On
%only full cells: Average centroid distance between cells (cell dinsity or
%dispersion), territotrial volume, cell volume, cell complexity
%(territorial volume / cell volume), number of endpoints, branch points,
%and the minimum, maximum, and average branch length.
function morph3D(in_dir, in_file, params, out_dir, tmp_dir)
% create a local cluster object
pc = parcluster('local');
% specify location of temporary directory
addpath(tmp_dir);
% explicitly set the JobStorageLocation to the temp directory that
% is unique to each cluster job (and is on local, fast scratch)
pc.JobStorageLocation = tmp_dir;
pc.JobStorageLocation
% get the number of dedicated cores from environment
% num_workers = str2double(getenv('MOAB_PROCCOUNT'));
%
% num_workers
% Method Selection
parpool(pc, 4, 'AttachedFiles',{'Functions/arclength.m', ...
'Functions/BoundingBoxOfCell.m', ...
'Functions/bwclearborder.m', ...
'Functions/CellSizeCutoffGUI.m', ...
'Functions/ConnectPointsAlongPath.m', ...
'Functions/FileDataGUI.m', ...
'Functions/FullCellsGUI.m', ...
'Functions/Graph2Skel3D.m', ...
'Functions/imreadalltiff.m', ...
'Functions/NearestPixel.m', ...
'Functions/reorderpixellist.m', ...
'Functions/SelectFilesGUI.m', ...
'Functions/Skel2Graph3D.m', ...
'Functions/SkeletonImageGUI.m', ...
'Functions/SlimSkel3D.m', ...
'Functions/SomaCentroid.m', ...
'Functions/ThresholdGUI.m', ...
'Functions/Skeleton/Skeleton3D.m', ...
'Functions/Skeleton/FillEulerLUT.m', ...
'Functions/Skeleton/pk_get_nh.m', ...
'Functions/Skeleton/p_EulerInv.m', ...
'Functions/Skeleton/p_is_simple.m', ...
'Functions/Skeleton/p_oct_label.m', ...
'Functions/Skeleton/pk_get_nh_idx.m', ...
'Functions/Skeleton/pk_follow_link.m'});
addpath(genpath('Functions'));
addpath(genpath(out_dir));
addpath(genpath(in_dir));
FileList = in_file;
Parameters = params;
Interactive=2;
NoImages=0;
% for total = 1:numel(FileList)
% try
%% Load file and saved values
% clearvars -except file ch ChannelOfInterest scale zscale Parameters FileList PathList Interactive NoImages total
if Interactive == 2
load(Parameters);
% Without this assignment, parfor can't find these variables loaded in
scale = scale;
zscale = zscale;
BranchLengthFile = BranchLengthFile;
ShowImg = 1;
ShowObjImg = 1;
ShowCells = 1;
ShowFullCells = 1;
ConvexCellsImage = 1;
OrigCellImg = 1;
SkelImg = 1;
EndImg = 1;
BranchImg = 1;
if NoImages ==1
ShowImg = 0; ShowObjImg = 0; ShowCells = 0; ShowFullCells = 0; ConvexCellsImage = 0; OrigCellImg = 0; SkelImg = 0; EndImg = 0; BranchImg = 0;
end
end
if Interactive == 2
[~,file] = fileparts(FileList);
end
%Load in image stack from either .lsm or .tif file
if exist(strcat(file,'.lsm'),'file') == 2
%use bfopen to read in .lsm files to variable 'data'. Within data, we want
%the 1st row and column, which is a list of all channels. From these, we
%want the _#_ channel (ChannelOfInterest).
data = bfopen(strcat(file,'.lsm'));
s = size(data{1,1}{1,1}); % x and y size of the image
l = length(data{1,1});
zs = l(:,1)/ch; %Number of z planes
%To read only green data from a 3 channel z-stack, will write as slice 1
%ch1, 2, 3, then slice 2 ch 1, 2, 3, etc. Need to extract every third image
%to a new array. [] concatenates all of the retrieved data, but puts them
%all in many columns, so need to reshape.
img = reshape([data{1,1}{ChannelOfInterest:ch:end,1}],s(1),s(2),zs);
end
if exist('img','var')==0 %If both a tiff and lsm file exist, load only the lsm file
%Open .tif file and reshape channels
if exist(strcat(file,'.tif'),'file') == 2
tiffInfo = imfinfo(strcat(file,'.tif'));
no_frame = numel(tiffInfo);
data = cell(no_frame,1);
for iStack = 1:no_frame
data{iStack} = imread(strcat(file,'.tif'),'Index',iStack);
end
data = data(ChannelOfInterest:ch:end,1);
s = size(data{1,1});
zs = length(data);
img = reshape([data{:,1}], s(1), s(2), zs);
end
end
voxscale = scale*scale*zscale;%Calculate scale to convert voxels into unit^3.
%% Threshold
%Load GUI to set thresholding parameters.
if Interactive == 1
midslice = round(zs/2,0);
orig = img(:,:,midslice);
callgui = ThresholdGUI;
uiwait(callgui);
end
if Interactive == 2
BinaryThresholdedImg=zeros(s(1),s(2),zs);
for i=1:zs %For each slice
level=graythresh(img(:,:,i)); %Finds threshold level using Otsu's method. Different for each slice b/c no need to keep intensity conistent, just want to pick up all mg processes. Tried adaptive threshold, but did not produce better images.
level=level*adjust; %Increase the threshold by *1.6 to get all fine processes
BinaryThresholdedImg(:,:,i) = im2bw(img(:,:,i),level);% Apply the adjusted threshold and convert from gray the black white image.
end
NoiseIm=bwareaopen(BinaryThresholdedImg,noise); %Removes objects smaller than set value (in pixels). For 3D inputs, uses automatic connectivity input of 26. Don't want small background dots left over from decreased threshold.
end
ConnectedComponents=bwconncomp(NoiseIm,26); %returns structure with 4 fields. PixelIdxList contains a 1-by-NumObjects cell array where the k-th element in the cell array is a vector containing the linear indices of the pixels in the k-th object. 26 defines connectivity. This looks at cube of connectivity around pixel.
numObj = numel(ConnectedComponents.PixelIdxList); %PixelIdxList is field with list of pixels in each connected component. Find how many connected components there are.
%show full image compressed to 2D. Use imagesc to make it look 3D.
progbar = waitbar(0,'Processing your data...');
for i = 1:numObj
waitbar (i/numObj, progbar);
ex=zeros(s(1),s(2),zs);
ex(ConnectedComponents.PixelIdxList{1,i})=1;%write in only one object to image. Cells are white on black background.
flatex = sum(ex,3);
allObjs(:,:,i) = flatex(:,:);
end
if isgraphics(progbar)
close(progbar);
end
DetectedObjs = sum(allObjs,3);
cmapCompress = parula(max(DetectedObjs(:)));
cmapCompress(1,:) = zeros(1,3);
if ShowImg == 1
title = [file,'_Threshold (compressed to 2D)'];
figure('Name',title);imagesc(DetectedObjs);
colormap(cmapCompress);
daspect([1 1 1]);
end
%Extract list of pixel values and which object they belong to for
%segmentation viewing (in CellSizeCutoffGUI).
for i = 1:numObj
ObjectList(i,1) = length(ConnectedComponents.PixelIdxList{1,i});
ObjectList(i,2) = i;
end
ObjectList = sortrows(ObjectList,-1);%Sort columns by pixel size.
% ObjectList = sortrows(ObjectList,'descend'); %Sort columns by pixel size.
udObjectList = flipud(ObjectList);%ObjectList is large to small, flip upside down so small is plotted first in blue.
%% Cell Segmentation
% Decreased threshold may cause cells to be inappropriately connected.
% Choose the threshold for a large cell, and segment larger objects into
% separate cells (by identifying number of nuclei and running fitgmdist
% (fit Gaussian mixture distribution). Function is run 3 times to improve
% accuracy and replicability
if Interactive == 1
callgui = CellSizeCutoffGUI;
waitfor(callgui);
end
if ShowObjImg == 1
if Interactive ~= 1
num = [1:3:(3*numObj+1)];
fullimg = ones(s(1),s(2));
progbar = waitbar(0,'Plotting...');
for i = 1:numObj;
waitbar (i/numObj, progbar);
ex=zeros(s(1),s(2),zs);
j=udObjectList(i,2);
ex(ConnectedComponents.PixelIdxList{1,j})=1;%write in only one object to image. Cells are white on black background.
flatex = sum(ex,3);
OutlineImage = zeros(s(1),s(2));
OutlineImage(flatex(:,:)>1)=1;
se = strel('diamond',4);
Outline = imdilate(OutlineImage,se);
fullimg(Outline(:,:)==1)=1;
fullimg(flatex(:,:)>1)=num(1,i+1);
end
if isgraphics(progbar)
close(progbar);
end
end
cmapnumObj = jet(max(fullimg(:)));
cmapnumObj(1,:) = zeros(1,3);
title = [file,'_Selected Objects (compressed to 2D)'];
figure('Name',title);imagesc(fullimg);
colormap(cmapnumObj);
colorbar('Ticks',[1,3*numObj+1], 'TickLabels',{'Small','Large'});
daspect([1 1 1]);
end
col=1;
progbar = waitbar(0,'Segmenting...');
for i = 1:numObj %Evaluate all connected components in PixelIdxList.
waitbar (i/numObj, progbar);
if numel(ConnectedComponents.PixelIdxList{1,i}) > CellSizeCutoff %If the size of the current connected component is greater than our predefined cutoff value, segment it.
ex=zeros(s(1),s(2),zs);%Create blank image of correct size.
ex(ConnectedComponents.PixelIdxList{1,i})=1;%write object onto blank array so only the cell pixels = 1.
se=strel('diamond',6); %Set how much, and what shape, we want to erode by. If increase, erosion will be greater.
nucmask=imerode(ex,se);%Erode to find nuclei only. This erosion is large - don't want any remaining thick branch pieces.
nucsize = round((CellSizeCutoff/50),0);
nucmask=bwareaopen(nucmask,nucsize);%Get rid of leftover tiny spots. Increase second input argument to remove more spots (and decrease segmentation)
indnuc=bwconncomp(nucmask);%Find these connected comonents (number of remaining nuclei).
nuc = numel(indnuc.PixelIdxList);%Determine the number of nuclei (how many objects to segment into).
if nuc ==0 %If erosion only detects one nuc, but this should be segmented, increase nuc to at least 2
error('Error: The program finds 0 nuclei to segement your object into. Adjust se=strel(diamond,4) to a lower number to decrease image erosion.');
end
if nuc ==1 %If erosion only detects one nuc, but this should be segmented, increase nuc to at least 2
nuc = 2;
end
[x,y,z]=ind2sub(size(ex),find(ex));%Find nonzero elements in ex (ie connected microglia cells) and return x y z locations.
points = [x y z]; %concatenate to one array
GMModel = fitgmdist(points,nuc,'replicates',3); %Fit Gaussian mixture distribution to data
idx = cluster(GMModel,points);
for j=1:nuc %Extract location of all pixels for each object.
obj = (idx == j); % |1| for cluster 1 membership
% obj = find(clust==j);%return linear index of all values == nuc.
object = points(obj,1:3); %find x y z data of given linear index.
objidx=sub2ind(size(NoiseIm),object(:,1),object(:,2),object(:,3)); %convert x y z data to index in the same size as ex.
ex=zeros(s(1),s(2),zs);%Create blank image of correct size
ex(objidx)=1;%write in only one object to image. Cells are white on black background.
ex=bwareaopen(ex,noise);
individual=bwconncomp(ex,26);
NumberOfIdentifiedObjects = length(individual.PixelIdxList);
for m = 1:NumberOfIdentifiedObjects
Microglia{1,col}=individual.PixelIdxList{1,m}; %Write this separated object to new cell array, Microglia in location 'col'.
col=col+1; %Increase the col counter so data is not overwritten.
end
clear('individual');
end
clear('da','NucCoord','CenterOfNuc','indnuc','nuc','x','y','z','points');
else %If the size of the current connected component is NOT greater than our predefined cutoff value, keep the current component and rewrite to new cell array.
Microglia{1,col}=ConnectedComponents.PixelIdxList{1,i}; %Write the object to new cell array, Microglia in location 'col'.
col=col+1;%Increase the col counter so data is not overwritten.
end
end
numObjSep = numel(Microglia); %Rewrite the number of objects to include segmented cells.
%Extract list of pixel values and which object they belong to for
%segmentation viewing (in FullCellsGUI).
for i = 1:numObjSep
SepObjectList(i,1) = length(Microglia{1,i});
SepObjectList(i,2) = i;
end
SepObjectList = sortrows(SepObjectList,-1); %Sort columns by pixel size.
udSepObjectList = flipud(SepObjectList);%ObjectList is large to small, flip upside down so small is plotted first in blue.
%Below is used in FullCellsGUI
for i = 1:numObjSep
ex=zeros(s(1),s(2),zs);
ex(Microglia{1,i})=1;%write in only one object to image. Cells are white on black background.
flatex = sum(ex,3);
AllSeparatedObjs(:,:,i) = flatex(:,:);
end
if isgraphics(progbar)
close(progbar);
end
if Interactive == 1
question = {'Would you like to see your separated cells?','Warning: A 3D image will take a moment to process! We may downsample it for you...'};
choiceSegmentImg = questdlg(question,'Output Segmented Image?','3D image please!', '2D image please!', 'No thanks','No thanks');
%Response:
switch choiceSegmentImg
case '3D image please!'
ShowCells = 1;
case '2D image please!'
ShowCells = 2;
case 'No thanks'
ShowCells = 0;
end
end
num = [1:3:(3*numObjSep+1)];
cmap = jet(max(num));
cmap(1,:) = zeros(1,3);
if ShowCells == 1
title = [file,'_Selected Cells'];
figure('Name',title);
colormap(cmap);
progbar = waitbar(0,'Plotting...');
for i = 1:numObjSep
waitbar (i/numObjSep, progbar);
ex=zeros(s(1),s(2),zs);%Create blank image of correct size
j=udSepObjectList(i,2);
ex(Microglia{1,j})=1;%write in only one object to image. Cells are white on black background.
% If it's larger than 512x512, downsample image to increase processing time (this is ONLY to display, doesn't change actual figure).
if s(1)>=1024||s(2)>= 1024
ex = imresize(ex,0.5);
ex = (ex(:,:,1:2:end));
end
if 512>= s(1)&& s(1)<1024||512>=s(2)&& s(2)<1024
ex = imresize(ex,0.5);
ex = (ex(:,:,1:2:end));
end
ds = size(ex);
fv=isosurface(ex,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv,'FaceColor',cmap(i*3,:),'FaceAlpha',1,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
axis([0 ds(1) 0 ds(2) 0 ds(3)]);%specify the size of the image
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270); % Look at image from top viewpoint instead of side
daspect([1 1 1]);
colorbar('Ticks',[0,1], 'TickLabels',{'Small','Large'});
end
if isgraphics(progbar)
close(progbar);
end
end
if ShowCells == 2
fullimg = ones(s(1),s(2));
progbar = waitbar(0,'Plotting...');
for i = 1:numObjSep
waitbar (i/numObjSep, progbar);
ex=zeros(s(1),s(2),zs);
j=udSepObjectList(i,2);
ex(Microglia{1,j})=1;%write in only one object to image. Cells are white on black background.
flatex = sum(ex,3);
OutlineImage = zeros(s(1),s(2));
OutlineImage(flatex(:,:)>1)=1;
se = strel('diamond',4);
Outline = imdilate(OutlineImage,se);
fullimg(Outline(:,:)==1)=1;
fullimg(flatex(:,:)>1)=num(1,i+1);
end
if isgraphics(progbar)
close(progbar);
end
title = [file,'_Selected Cells (compressed to 2D)'];
figure('Name',title);imagesc(fullimg);
colormap(cmap);
colorbar('Ticks',[1,max(num)], 'TickLabels',{'Small','Large'});
daspect([1 1 1]);
end
%% Territorial volume
%Uses convhulln to create a 3D polygon around the object's external points.
%For the total occupied vs unoccupied volume, don't want to exclude any
%cells/processes. Use Microglia list here, not FullMg.
ConvexVol = zeros(numObjSep,1);
sz = size(img);
progbar = waitbar(0,'Finding territorial volume...');
for i = 1:numObjSep
waitbar (i/numObjSep, progbar);
[x,y,z] = ind2sub(sz,[Microglia{1,i}]); %input: size of array ind values come from, list of values to convert.
obj = [y,x,z]; %concatenate x y z coordinates.
[k,v] = convhulln(obj);
ConvexVol(i,:) = v*voxscale;
end
if isgraphics(progbar)
close(progbar);
end
TotMgVol = sum(ConvexVol); %Calculate total volume of image covered by microglia.
CubeVol = (s(1)*s(2)*zs)*voxscale; %volume of image cube in um^3.
EmptyVol = CubeVol-TotMgVol;%And the remaining 'empty space'.
PercentMgVol = ((TotMgVol)/(CubeVol))*100;
%% Full cells
%Option to remove all cells touching the x y border, and all objects below
%size limit. From here on in code, will only be looking at these full
%cells.
if Interactive == 1
callgui2 = FullCellsGUI;
waitfor(callgui2);
end
if KeepAllCells == 1
FullMg = Microglia;
else
col=1;
progbar = waitbar(0,'Finding All Full Cells...');
for i = 1:numObjSep
waitbar (i/numObjSep, progbar);
if numel(Microglia{1,i})>=SmCellCutoff %Extract elements of Microglia that are >SmCellCutoff pixels
if RemoveXY ==1
ex=zeros(s(1),s(2),zs);%Create blank image of correct size
ex(Microglia{1,i})=1;%plot it onto original blank image
antiborder = logical(padarray(ex,[0 0 1],0));%add row of zeros to top and bottom in z axis so no objects are touching this border. Also make logical.
cleared = bwclearborder(antiborder,26); %Like imclearborder, but MUCH faster! Removes any 1 objects touching border in 26 connectivity (so whole object)
nonedge = max(cleared(:)); %If the object was removed, this should be 0, so don't add it to the FullMg array
if nonedge == 1 % If the cell is not touching an x or y edge, keep it in new cell array.
FullMg{1,col} = (Microglia{1,i}); %Suppress not preallocated error.
col = col+1;
end
else
FullMg{1,col} = (Microglia{1,i}); %Suppress not preallocated error.
col = col+1;
end
end
end
if isgraphics(progbar)
close(progbar);
end
end
numObjMg = numel(FullMg);% number of microglia after excluding edges and small processes.
%Extract list of pixel values and which object they belong to for
%segmentation viewing (in FullCellsGUI).
for i = 1:numObjMg
MgObjectList(i,1) = length(FullMg{1,i});
MgObjectList(i,2) = i;
end
MgObjectList = sortrows(MgObjectList,-1); %Sort columns by pixel size.
udMgObjectList = flipud(MgObjectList);%ObjectList is large to small, flip upside down so small is plotted first in blue.
num = [1:3:(3*numObjMg+1)];
cmap = jet(max(num));
cmap(1,:) = zeros(1,3);
if Interactive == 1
%See all full cells?
question2 = {'Would you like to see remaining full cells?','Warning: A 3D image will take a moment to process! We may downsample it for you...'};
choiceFullCellsImg = questdlg(question2,'Output Full Cell Image?','3D image please!', '2D image please!', 'No thanks','No thanks');
%Response:
switch choiceFullCellsImg
case '3D image please!'
ShowFullCells = 1;
case '2D image please!'
ShowFullCells = 2;
case 'No thanks'
ShowFullCells = 0;
end
end
if ShowFullCells == 1
progbar = waitbar(0,'Plotting...');
title = [file,'_Full Cells'];
figure('Name',title);
colormap(cmap);
for i = 1:numObjMg
waitbar (i/numObjMg, progbar);
ex=zeros(s(1),s(2),zs);%Create blank image of correct size
j=udMgObjectList(i,2);
ex(FullMg{1,j})=1;%write in only one object to image. Cells are white on black background.
% If it's larger than 512x512, downsample image to increase processing time (this is ONLY to display, doesn't change actual figure).
if s(1)>=1024||s(2)>= 1024
ex = imresize(ex,0.5);
ex = (ex(:,:,1:2:end));
end
if 512>= s(1)&& s(1)<1024||512>=s(2)&& s(2)<1024
ex = imresize(ex,0.5);
ex = (ex(:,:,1:2:end));
end
ds = size(ex);
fv=isosurface(ex,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv,'FaceColor',cmap(i*3,:),'FaceAlpha',1,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
axis([0 ds(1) 0 ds(2) 0 ds(3)]);%specify the size of the image
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270); % Look at image from top viewpoint instead of side
daspect([1 1 1]);
colorbar('Ticks',[0,1], 'TickLabels',{'Small','Large'});
end
if isgraphics(progbar)
close(progbar);
end
end
if ShowFullCells == 2
fullimg = ones(s(1),s(2));
progbar = waitbar(0,'Plotting...');
for i = 1:numObjMg
waitbar (i/numObjMg, progbar);
ex=zeros(s(1),s(2),zs);
j=udMgObjectList(i,2);
ex(FullMg{1,j})=1;%write in only one object to image. Cells are white on black background.
flatex = sum(ex,3);
OutlineImage = zeros(s(1),s(2));
OutlineImage(flatex(:,:)>1)=1;
se = strel('diamond',4);
Outline = imdilate(OutlineImage,se);
fullimg(Outline(:,:)==1)=1;
fullimg(flatex(:,:)>1)=num(1,i+1);
end
if isgraphics(progbar)
close(progbar);
end
title = [file,'_Full Cells (compressed to 2D)'];
figure('Name',title);imagesc(fullimg);
colormap(cmap);
colorbar('Ticks',[1,max(num)], 'TickLabels',{'Small','Large'});
daspect([1 1 1]);
end
%% Volume of Full Cells
% Determines the cell volume by the number of voxels multiplied by the
% voxscale to convert into real world units. Also finds the convex
% territorial volume of only full cells. Cell complexity or extent) is
% calculated as territorial volume / cell volume and represents how
% bushy/amoeboid or branched cells are within their territory.
NumberOfPixelsPerCell = cellfun(@numel,FullMg);
[biggest,idx] = max(NumberOfPixelsPerCell);
CellVolume = (NumberOfPixelsPerCell*voxscale)'; %list of volume of each cell
MaxCellVol = biggest*voxscale; %Volume determined by microscope scale, and voxel number reported in cc.PixelIdxList. Should be in um^3
if Interactive == 1
ConvexImgQuestion = {'Would you like to see a convex volume image of each full cell?'};
choiceConvexVolImg = questdlg(ConvexImgQuestion,'Output Convex Volume Images?','Yes please!', 'No thanks','No thanks');
%Response
switch choiceConvexVolImg
case 'Yes please!'
ConvexCellsImage = 1;
case 'No thanks'
ConvexCellsImage = 0;
end
end
% Find the convexvol of only full cells
FullCellTerritoryVol = zeros(numObjMg,1);
for i = 1:numObjMg
[x,y,z] = ind2sub(sz,[FullMg{1,i}]); %input: size of array ind values come from, list of values to convert.
obj = [y,x,z]; %concatenate x y z coordinates.
[k,v] = convhulln(obj);
FullCellTerritoryVol(i,:) = v*voxscale;
if ConvexCellsImage == 1
figure;
trisurf(k,obj(:,1),obj(:,2),obj(:,3));
axis([0 s(1) 0 s(2) 0 zs]);
daspect([1 1 1]);
end
end
% Find the complexity (or extent) of full cells.
FullCellComplexity = zeros(numObjMg,1);
for i = 1:length(FullCellTerritoryVol)
FullCellComplexity(i,:) = FullCellTerritoryVol(i)/CellVolume(i);
end
%% Distance Between Centroids
%Finds location of centroid of each cell, and measures distance between
%them as a measure of cell density or dispersion.
cent = (zeros(numObjMg,3));
progbar = waitbar(0,'Finding centroids...');
for i=1:numObjMg
waitbar (i/numObjMg, progbar);
ex=zeros(s(1),s(2),zs);%Create blank image of correct size
ex(FullMg{1,i})=1;%write in only one object to image. Cells are white on black background.
CentroidCoord = SomaCentroid(ex);
cent(i,:) = CentroidCoord;
end
if isgraphics(progbar)
close(progbar);
end
centum = (zeros(numObjMg,3));
centum(:,1)=cent(:,1)*scale; %Convert pixel location to microns so that distances are in correct scale
centum(:,2)=cent(:,2)*scale;
centum(:,3)=cent(:,3)*zscale;
centdist = pdist2(centum,centum); %Calculate distance from each centroid to all other centroids
centdist=nonzeros(centdist); %Remove all 0s (distance from one centroid to itself)
AvgDist = mean(centdist);
%% 3D Skeleton
%Can use two different methods to keep all processes (more fine
%structures), or only major branches (and ignore small extensions). From
%skeleton, endpoints and branch points are identified. In skeleton image,
%red processes are primary, yellow secondary, green tertiary, and blue are
%connected to endpoints. Branch lengths are measured as the shortest
%distance from each endpoint to the centroid. If the program is unable to
%propoerly identify a centroid or endpoints, it will output a 0 and move to
%the next cell.
kernel(:,:,1) = [1 1 1; 1 1 1; 1 1 1];
kernel(:,:,2) = [1 1 1; 1 0 1; 1 1 1];
kernel(:,:,3) = [1 1 1; 1 1 1; 1 1 1];
numendpts = zeros(numel(FullMg),1);
numbranchpts = zeros(numel(FullMg),1);
MaxBranchLength = zeros(numel(FullMg),1);
MinBranchLength = zeros(numel(FullMg),1);
AvgBranchLength = zeros(numel(FullMg),1);
if Interactive == 1
%Use Skeleton method to include small processes/fillipodia?
question3 = ['Would you like to include all small processes or fillipodia in your skeleton analysis? ','Note: only keep small processes if you want all fine fillipodia - this will increase processing time.'];
choiceSkelMethod = questdlg(question3,'Skeletonization Method','Keep small processes', 'Only major branches', 'Only major branches');
%Response:
switch choiceSkelMethod
case 'Keep small processes'
SkelMethod = 1;
case 'Only major branches'
SkelMethod = 2;
end
end
if Interactive == 1
callgui = SkeletonImageGUI;
uiwait(callgui);
end
if SkelImg||EndImg||BranchImg||OrigCellImg||BranchLengthFile ==1
folder = mkdir ([out_dir, '/', file, '_figures']);
fpath =(strcat(out_dir, '/', file, '_figures'));
end
if BranchLengthFile == 1;
BranchLengthList=cell(1,numel(FullMg));
end
% start timing this code fragment
tic
parfor i=1:numel(FullMg)
% for i=1:numel(FullMg)
try
ex=zeros(s(1),s(2),zs); %Create blank image of correct size
ex(FullMg{1,i})=1;%write in only one object at a time to image.
ds = size(ex);
if OrigCellImg == 1
title = [file,'_Cell',num2str(i)];
figure('Name',title);
fv=isosurface(ex,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv,'FaceColor',cmap(i,:),'FaceAlpha',1,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
axis([0 ds(1) 0 ds(2) 0 ds(3)]);%specify the size of the image
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270); % Look at image from top viewpoint instead of side
daspect([1 1 1]);
filename = ([file '_Original_cell' num2str(i)]);
saveas(gcf, fullfile(fpath, filename), 'jpg');
end
% take these as defaults, to avoid parfor warnings
WholeSkel = SlimSkel3D(ex,100);
DownSampled = 0;
adjust_scale = scale;
if SkelMethod == 2
if s(1)>512 %convert large cells to 512x512 to speed up skeletonization. The branch lengths are later adjusted to account for this down-sampling.
ex = imresize(ex,0.5);
adjust_scale = 2*scale;
DownSampled = 1;
else
DownSampled = 0;
adjust_scale = scale;
end
SmoothEx = imgaussfilt3(ex); %Smooth the cell so skeleton doesn't pick up many fine hairs
FastMarchSkel = skeleton(SmoothEx);%Find the skeleton! This uses msfm3d and rk4 files, which have been compiled and the .mexw64 versions included. If errors, re-run compilation of these files (in FastMarching_version3b folder), and add the folder and subfolders to path.
%Convert cell output of branches into one image for further processing.
WholeSkel=zeros(size(ex));
WholeList = round(vertcat(FastMarchSkel{:}));
SkelIdx = sub2ind(size(ex),WholeList(:,1),WholeList(:,2),WholeList(:,3));
WholeSkel(SkelIdx)=1;
end
[BoundedSkel, right, left, top, bottom] = BoundingBoxOfCell(WholeSkel); %Create a bounding box around the skeleton and only analyze this area to significantly increase processing speed.
si = size(BoundedSkel);
% Find endpoints, and trace branches from endpoints to centroid
i2 = floor(cent(i,:)); %From the calculated centroid, find the nearest positive pixel on the skeleton, so we know we're starting from a pixel with value 1.
if DownSampled == 1
i2(1) = round(i2(1)/2);
i2(2) = round(i2(2)/2);
end
closestPt = NearestPixel(WholeSkel,i2,scale);
i2 = closestPt; %Coordinates of centroid (endpoint of line).
i2(:,1)=(i2(:,1))-left+1;
i2(:,2) = (i2(:,2))-bottom+1;
endpts = (convn(BoundedSkel,kernel,'same')==1)& BoundedSkel; %convolution, overlaying the kernel cube to see the sum of connected pixels.
EndptList = find(endpts==1);
[r,c,p]=ind2sub(si,EndptList);%Output of ind2sub is row column plane
EndptList = [r c p];
numendpts(i,:) = length(EndptList);
masklist =zeros(si(1),si(2),si(3),length(EndptList));
ArclenOfEachBranch = zeros(length(EndptList),1);
for j=1:length(EndptList)%Loop through coordinates of endpoint.
i1 = EndptList(j,:);
mask = ConnectPointsAlongPath(BoundedSkel,i1,i2);
masklist(:,:,:,j)=mask;
% Find the mask length in microns
pxlist = find(masklist(:,:,:,j)==1);%Find pixels that are 1s (branch)
distpoint = reorderpixellist(pxlist,si,i1,i2); %Reorder pixel lists so they're ordered by connectivity
%Convert the pixel coordinates by the scale to calculate arc length in microns.
distpoint(:,1) = distpoint(:,1)*adjust_scale; %If 1024 and downsampled, these scales have been adjusted
distpoint(:,2) = distpoint(:,2)*adjust_scale; %If 1024 and downsampled, these scales have been adjusted
distpoint(:,3) = distpoint(:,3)*zscale;
[arclen,seglen] = arclength(distpoint(:,1),distpoint(:,2),distpoint(:,3));%Use arc length function to calculate length of branch from coordinates
ArclenOfEachBranch(j,1)=arclen; %Write the length in microns to a matrix where each row is the length of each branch, and each column is a different cell.
end
%Find average min, max, and avg branch lengths
MaxBranchLength(i,1) = max(ArclenOfEachBranch);
MinBranchLength(i,1) = min(ArclenOfEachBranch);
AvgBranchLength(i,1) = mean(ArclenOfEachBranch);
%Save branch lengths list
if BranchLengthFile == 1
BranchLengthList{1,i} = ArclenOfEachBranch;
end
fullmask = sum(masklist,4);%Add all masks to eachother, so have one image of all branches.
fullmask(fullmask(:,:,:)>3)=4;%So next for loop can work, replace all values higher than 3 with 4. Would need to change if want more than quaternary connectivity.
% Define branch level and display all on one colour-coded image.
pri = (fullmask(:,:,:))==4;
sec = (fullmask(:,:,:))==3;
tert = (fullmask(:,:,:))==2;
quat = (fullmask(:,:,:))==1;
if SkelImg == 1
title = [file,'_Cell',num2str(i)];
figure('Name',title); %Plot all branches as primary (red), secondary (yellow), tertiary (green), or quaternary (blue).
hold on
fv1=isosurface(pri,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv1,'FaceColor',[1 0 0],'FaceAlpha',0.5,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
fv1=isosurface(sec,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv1,'FaceColor',[1 1 0],'FaceAlpha',0.5,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
fv1=isosurface(tert,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv1,'FaceColor',[0 1 0],'FaceAlpha',0.5,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
fv1=isosurface(quat,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv1,'FaceColor',[0 0 1],'FaceAlpha',0.5,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270); % Look at image from top viewpoint instead of side
daspect([1 1 1]);
hold off
filename = ([file '_Skeleton_cell' num2str(i)]);
saveas(gcf, fullfile(fpath, filename), 'jpg');
end
% Find branchpoints
brpts =zeros(si(1),si(2),si(3),4);
for kk=1:3 %For branchpoints not connected to end branches (ie. not distal branches). In fullmask, 1 is branch connected to end point, so anything greater than that is included.
temp = (fullmask(:,:,:))>kk;
tempendpts = (convn(temp,kernel,'same')==1)& temp; %Get all of the 'distal' endpoints of kk level branches
brpts(:,:,:,kk+1)=tempendpts;
end
% Find any branchpoints of 1s onto 4s (ie. final branch coming off of main trunk).
quatendpts = (convn(quat,kernel,'same')==1)& quat; %convolution, overlaying the kernel cube onto final branches only.
quatbrpts = quatendpts - endpts; %Have points at both ends of final branches. Want to exclude any distal points (true endpoints)
%Only want to keep these quant branchpoints if they're connected to a 4(primary branch). Otherwise, the branch point will have been picked up in the previous for loop.
fullrep= fullmask;
fullrep(fullrep(:,:,:)<4)=0;%Keep only the 4s, as 4s (don't convert to 1)
qbpts = fullrep+quatbrpts;%Add the two vectors, so should have 4s and 1s.
qbpts1 = convn(qbpts,ones([3 3 3]),'same'); %convolve with cube of ones to get 'connectivity'. All 1s
brpts(:,:,:,1) = (quatbrpts.*qbpts1)>= 5;
allbranch = sum(brpts,4); %combine all levels of branches
BranchptList = find(allbranch==1);%Find how many pixels are 1s (branchpoints)
[r,c,p]=ind2sub(si,BranchptList);%Output of ind2sub is row column plane
BranchptList = [r c p];
numbranchpts(i,:) = length(BranchptList);
if EndImg == 1
title = [file,'_Cell',num2str(i)];
figure('Name',title); %Plot all branches with endpoints
fv1=isosurface(fullmask,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv1,'FaceColor',[0 0 1],'FaceAlpha',0.1,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270); % Look at image from top viewpoint instead of side
fv2=isosurface(endpts,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv2,'FaceColor',[1 0 0],'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270);
daspect([1 1 1]);
filename = ([file '_Endpoints_cell' num2str(i)]);
saveas(gcf, fullfile(fpath, filename), 'jpg');
end
if BranchImg == 1
title = [file,'_Cell',num2str(i)];
figure('Name',title); %Plot all branches with branchpoints
fv1=isosurface(fullmask,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv1,'FaceColor',[0 0 1],'FaceAlpha',0.1,'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270); % Look at image from top viewpoint instead of side
fv2=isosurface(allbranch,0);%display each object as a surface in 3D. Will automatically add the next object to existing image.
patch(fv2,'FaceColor',[1 0 0],'EdgeColor','none');%without edgecolour, will auto fill black, and all objects appear black
camlight %To add lighting/shading
lighting gouraud; %Set style of lighting. This allows contours, instead of flat lighting
view(0,270);
daspect([1 1 1]);
filename = ([file '_Branchpoints_cell' num2str(i)]);
saveas(gcf, fullfile(fpath, filename), 'jpg');
end
catch ERR
disp(['Error occurred!' ERR.identifier])
disp(['msg: ' ERR.message])
end
disp(['cell ' num2str(i) ' of ' num2str(numel(FullMg))]); %To see which cell we are currently prcoessing.
end
% print out time since 'tic' was seen
toc
%Save Branch Lengths File
if BranchLengthFile == 1
names = ["cell1"];
%Write in headings
for CellNum = 1:numel(FullMg)
input = strcat('Cell ',num2str(CellNum));
names(CellNum,1) = input;
end
BranchFilename = 'BranchLengths';
%Prime empty Branches array
BranchLengths_out{numel(FullMg), 2} = [];
%Add in data
for CellNum = 1:numel(FullMg)
if numel(BranchLengthList{1,CellNum})>0
BranchLengths_out{CellNum, 1} = names(CellNum, 1);
BranchLengths_out{CellNum, 2} = BranchLengthList{1,CellNum}';
end
end
%Write to file
writetable(cell2table(BranchLengths_out), strcat(fullfile(fpath, BranchFilename), '.csv'), 'WriteVariableNames', false);
end
%Make two results arrays...
%Make results arrays for average / total data
ImageResults_out{5, 2} = [];
ImageResults_out(1:5, 1) = {'Parameter', 'AvgCentroidDistance_um', 'TotMgTerritoryVol_um3', 'TotUnoccupiedVol_um3', 'PercentOccupiedVol_um3'};
ImageResults_out(1:5, 2) = {'Value', AvgDist, TotMgVol, EmptyVol, PercentMgVol};
writetable(cell2table(ImageResults_out(2:end, :), 'VariableNames', ImageResults_out(1, :)), strcat(out_dir, '/ImageResults_', file, '.csv'));
%Make results array for per-cell data
CellResults_out{numel(FullMg)+1, 9} = [];
CellResults_out(1, 1:9) = {'Cell', 'CellTerritoryVol_um3', 'CellVolumes', 'RamificationIndex', 'NumOfEndpoints', 'NumOfBranchpoints', 'AvgBranchLength', 'MaxBranchLength', 'MinBranchLength'};
for CellNum = 1:numel(FullMg)
CellResults_out(CellNum + 1, 1:9) = {names(CellNum, 1), FullCellTerritoryVol(CellNum,1), CellVolume(CellNum,1), FullCellComplexity(CellNum,1), numendpts(CellNum,1), numbranchpts(CellNum,1), AvgBranchLength(CellNum,1), MaxBranchLength(CellNum,1), MinBranchLength(CellNum,1)};
end
writetable(cell2table(CellResults_out(2:end, :), 'VariableNames', CellResults_out(1, :)), strcat(out_dir, '/CellResults_', file, '.csv'));
handles=findall(0,'type','figure');
for fig = 1:numel(handles)
filename = get(handles(fig),'Name');
saveas(handles(fig), fullfile(fpath, filename), 'jpg');
end
close all;
%% Parameters file
%Save .mat parameters file for batch processing. Name is "Parameters_file
%name_date(year month day hour)"
if Interactive == 1
time = clock;
name = ['Parameters_',file,'_',num2str(time(1)),num2str(time(2)),num2str(time(3)),num2str(time(4))];
save(name,'ch','ChannelOfInterest','scale','zscale','adjust','noise','s','ShowImg','ShowObjImg','ShowCells','ShowFullCells','CellSizeCutoff','SmCellCutoff','KeepAllCells','RemoveXY','ConvexCellsImage','SkelMethod','SkelImg','OrigCellImg','EndImg','BranchImg','BranchLengthFile');
end
end