-
Notifications
You must be signed in to change notification settings - Fork 0
/
03-chap3.Rmd
1907 lines (1533 loc) · 130 KB
/
03-chap3.Rmd
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
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
```{r, echo = F}
load("data/ac.RData")
```
# Results {#res}
## Quantitative imaging and image analysis {#res-met}
### A 3D-printed stamp to standardize sample mounting and semi-automatize imaging {#res-mount}
\begin{tcolorbox}[colback = white, sharp corners = northwest]
\textbf{NOTE}
\tcblower
Most of Chapter 3.1.1 has been published as an article in the Journal BMC Biotechnology (Kleinhans and Lecaudey, BMC Biotechnology (2019) 19:68). The author contribution was described in the paper as follows: DSK designed the study, carried out the experiments and analysis of the results. VL provided the infrastructure and funding for performing the experiments. DSK and VL wrote the final manuscript. All authors read and approved the final manuscript.
Some passages in this section 3.1.1 have been quoted \textit{verbatim} from the above-mentioned article for the scientific accuracy of the terms used.
\end{tcolorbox}
Even though on a macroscopic scale development is a remarkably similar and synchronized process between zebrafish embryos, a single biological process on a _microscopic_ scale even in sibling embryos can look drastically different. Given the noisy and variable character of biological systems, it is important to record a sufficient number of samples to obtain a quantitative and representative view of a biological process. Furthermore, to process biological samples of whole organisms in a high-content manner it is important to have a standardized way of sample mounting, data acquisition, data processing and analysis.
However, imaging a high number of samples and generating large datasets to date is still largely limited by the classical way developmental biologists mount embryos for imaging. A number of factors limiting the standardization are summarized in table \@ref(tab:resmountingtab)
```{r resmountingtab, fig.pos = 'H'}
read.delim("data/tables/mountdata.txt", sep = ",", row.names = NULL, quote = "\"") %>%
kable(format = 'latex', booktabs = T, escape = F,
col.names = c('Standard', 'Limitations', 'Solutions', 'Improvement'),
caption = 'Limitations of traditional zebrafish mounting techniques') %>%
kable_styling(full_width = T, font_size = 8) %>%
column_spec(4, width = "4.2cm") %>%
row_spec(0, bold = T)
```
Therefore, especially for 3D segmentation and 2D tracking experiments where an exact staging and orientation of the embryo is necessary, there is a need for methods to standardize sample mounting and image acquisition of multiple embryos.
The protocol we describe here was designed to be used with XY scanning universal sample holders that usually come with any motorized-stage inverted microscope. Similar to previous approaches [@Campinho2018; @Donoughe2018; @Wittbrodt2014; @Yu2018], it uses a 3D-printed stamping device to produce an Agarose imprint with a diameter of 20 mm on the cover glass of a 35 mm $\mu$-dish. The imprint consists of 44 equally spaced $\mu$-wells, which are designed to fit the average morphology of a zebrafish embryo between 24 and 96 hours-post-fertilization (hpf).
The aim was to develop a standardized mounting method allowing us to: (i) mount many samples in parallel in a 2D coordinate system of rows and columns, (ii) reduce the acquisition time and thus photo-bleaching and photo-toxicity during imaging, (iii) semi-automatize the acquisition, (iv) reduce the post-processing steps, and (v) facilitate subsequent processing such as genotyping due to a 1:1 correlation between image data and specimen arrangement sequence.
For mounting, an improved 3D specimen preparation and well-plate-like sample navigation for zebrafish larvae confocal microscopy was developed with which lateral line development can be recorded over more than 20 h, in up to 44 positions, in a confocal Z-stack of less than 120 $\mu$m and a time interval of 5–10 min. (depending on the number of channels and exposure time). The stamp was designed to be used for embryos between 24 and 96 hpf. For a tailor-made well, embryos were fixed and imaged _in toto_ to measure the dimensions in X, Y and Z of different (whole embryo, trunk, yolk) embryonic structures (figure \@ref(fig:mountmicro)B-B'). Using Microsoft 3D-Builder a well was assembled from basic shapes like cube, sphere and wedge. After, the well was duplicated a couple of times and put in a grid-like arrangement to fit on a disc base 20 mm in diameter. Printing was performed on a Formlabs extrusion printer (figure \@ref(fig:mountmicro)C).
(ref:mountmicro) Stamp and $\mu$-well properties. **A - A'** Mounting (A) without and with (A') $\mu$-well. Legend to the right. **B - B'** Dimensions of a single micro well in (B) X-Y and (B') X-Z in mm **C** Elements and dimensions of the stamp wafer. *D* Assembled stamp with a screw mounted on the back of the stamp wafer.
```{r mountmicro, fig.cap = "(ref:mountmicro)", fig.scap = "Stamp and micro-well properties"}
knitr::include_graphics("figures/results/00_methods/mounting/stamp_dims.png")
```
(ref:stampresults) Mounting Results. **A - A'** Maximum intensity projection of a 50 hpf embryo mounted on its side in XY and XZ. (right) Color scale indicates depth encoding. **C** Multi-position (36), multi-channel (2) time-lapse recording (13 h duration; 15 min. interval). **D** Multi-channel (2) Extended Depth of Focus (EDF) projections from widefield Z-stacks (recorded with 20x Objective). Scale Bar = 1mm **E** Multipoint coordinates in X, Y and Z (recorded with 40x Objective). The offset describes the distance of each point from the mean of all points in X, Y and Z. Panel 1–3 (top to bottom) show dimensions X, Y and Z in comparison for the pLLP, the eye and the otic vesicle. The red line indicates the median, the blue line indicates zero offset, error bars indicate mean $\pm$ s.d.. Numeric values indicate the variance in each group. **F-F''** Systematic retrieval for genotyping. **F** Mounted embryos in a 2-D coordinate system of rows (A-M) and columns (1–3). **F'** Imaging Sequence in a snake-by-column fashion. In a time-lapse setting, it starts at point 1 (P01) again to initiate the next timepoint. **F''** After imaging, the embryos are retrieved in the same sequence as they were imaged (snake by column, left panel). **F''** Each genotyping result on the electrophoresis gel is easily correlated to one imaging dataset with defined X-Y coordinates.
```{r stampresults, fig.cap = "(ref:stampresults)", fig.scap = "Mounting results"}
knitr::include_graphics("figures/results/00_methods/mounting/stamp_results.png")
```
#### Procedure
##### Preparation of the agarose cast
To prepare the agarose cast, the stamp is first cleaned from dust and remnants with tissue soaked in 70$\%$ Ethanol and pressured air (figure \@ref(fig:mounting) IA). To prepare the casting medium a 1$\%$ Agarose (w/v) solution is prepared in an autoclaved 100 mL bluecap bottle by dissolving 200 mg of agarose in 20 mL of E3 using a microwave oven. From the ready solution 650 $\mu$L are applied to the $\varnothing$ 20 mm coverslip of a $\varnothing$ 35 mm imaging dish (materials in table \@ref(tab:mat-mount)). Subsequently the clean stamp is gently placed onto the placed solution and adjusted to the center. The dish is then rotated to distribute excess agarose over the entire dish surface to stabilize the imprint once polymerized.
After about 30 min. the stamp is removed by first slipping a clean preparation needle between the stamp and the polymer and then lifting it from the cast (figure \@ref(fig:mounting)IB). If necessary, air bubbles appearing between the cover glass and the polymer are eliminated by punctuation with a preparation needle. The mounting cast may be used immediately or stored at 4$^\circ$C for several days (with lid closed).
##### Preparation of mounting media
Two solutions of low-melting point Agarose (LMPA) are prepared in autoclaved 100 mL bluecap bottles by dissolving 60 and 100 mg LMPA in 16 mL of E3 in a microwave oven - yielding 0.375 and 0.625 $\%$ respectively. Per stamped cast, 2 aliquots of 1.6 mL are prepared in 2 mL tubes for each LMPA concentration and placed in a heating block adjusted to 41$^\circ$C.For live imaging, 400 $\mu$L of 4.2 mg/mL Tricaine (25X) are added to keep the embryos anesthetized during imaging. Final concentrations of LMPA solution are therefore 0.3 and 0.5 $\%$, respectively. LMPA solutions containing Tricaine were prepared fresh for each mounting session. The LMPA solution and the mounting cast have almost equal refractive indices. Therefore, when adding the LMPA solution the cast becomes invisible. To still be able to locate the $\mu$-wells and to position the embryos accordingly, the illumination contrast and mirror angle of a transmitted light base are adjusted to make the $\mu$-wells visible again (figure \@ref(fig:mounting)IIA-C).
##### Mounting procedure
In case of live imaging the embryos are first anesthetized in a Petri dish with 4 to 5 drops of 4.2 mg/mL Tricaine (40 $\mu$g/mL in E3) added 4 to 5 min. before usage.
For mounting, the cast is first gently filled from the border (figure \@ref(fig:mounting)II A3) with 500 $\mu$L of 0.3$\%$ LMPA solution. Then, 44 embryos (one for each well) are collected from their Petri dish with a glass Pasteur pipette. To minimize the amount of liquid added to the LMPA, the embryos are allowed to sink to the air – liquid interface and immediately added in one drop to the liquid LMPA solution in the stamped cast. Next, each embryo is moved to a separate $\mu$-well with a preparation needle by positioning the yolk within the hemi-spherical structure of each well and the tail aligned horizontally with the shape of the $\mu$-well (figure \@ref(fig:mounting)II C-D). The LMPA was allowed to polymerize for about 40 min. For time-lapse recording longer than 1 h, 1 mL of 0.5$\%$ LMPA was added on top and allowed to polymerize for another 10 min. to construct an Agarose _sandwich_ to stabilize the structure. Since the 0.3$\%$ LMPA will still be very fragile, the 0.5$\%$ LMPA should be added to the outer well first, carefully raising the level.
Since Agarose polymerization speed depends on temperature, for mounting the temperature of the room should not be less than 23$^\circ$C to give sufficient time. For indefinite time of embryo orientation, a higher room temperature or a 5 V terrarium heating mat (at maximum temperature ca. 38$^\circ$C) can be used. For the latter, a hole with the diameter of an imaging dish should first be cut in the middle of the heating mat. For mounting, the mat should then be placed and fixed on the stereo-microscope stage with the dish in the hole.
##### Imaging setup
The dish is placed onto the sample holder of an inverted confocal spinning disc microscope so that the embryos are aligned to the Y axis of the microscope stage. The stage is then moved to place the embryo at Position 01 (P01, top-left position) right above the objective (figure \@ref(fig:mounting)III A).
###### Define embryo positions
Since the embryos are mounted in a 3-D grid with well defined dimensions, all positions can be defined _via_ a pre-defined points list that is loaded into the microscope software. For our system we use the 'Nikon Imaging Software' where we move the stage to P01, define a multi-point list with distance X / distance Y = 3450 / 1280 $\mu$m, bring P01 into focus and offset all points in Z. The list can also be saved for re-use in a future experiment. Alternatively one can also define a custom well plate and calibrate the stage.
###### Refine Positions
Even though the mounting method allows for a precise positioning, each embryo physiology is still a bit different resulting in differences between positions but same structures of up to 100 $\mu$m in X, Y and Z (figure \@ref(fig:stampresults)E). Therefore, before starting an experiment each position needs to be refined.
##### Retrieval
For further experiments such as genotyping, the embryos are retrieved from the agarose in the same sequence as they were imaged (figure \@ref(fig:stampresults)F'). To do so, a glass pipette is inserted into the agarose and directed to the head region of an embryo. By applying a gentle underpressure the embryo is then sucked into the glass pipette.
To lyse the embryos and extract the genomic DNA, each embryo is placed in a single tube of an 8-tube PCR strip. Since 8-tube PCR strips are designed to work with multichannel pipettes, the genotyping PCR is performed and analysed by gel electrophoresis using an 8x-multichannel pipette. When using a 34-well comb, the pipette tips will reach every second well of the agarose gel. Filling the wells staggered (offset by 1), one can load 4 × 8 wells in one row (figure \@ref(fig:stampresults)F''). Since each embryo has a defined position, it is straightforward to associate each genotype to the corresponding image data (figure \@ref(fig:stampresults)F' - F'''). Since a single mismatch would mess up the entire experiment by resulting in a frameshift of the one-to-one correspondence, this is a very important feature. The imaging dish can be reused several times. For cleaning, the agarose bed is removed from the dish using a small scoop or preparation needle and wiped gently with a lint-free tissue soaked in Ethanol.
(ref:mounting) Stamping procedure **Agarose Cast** (A) clean stamp surface (B) preparation of the stamp before lifting (C) ready-for-use agarose imprint. **Mounting** (A) without LMPA (B and C) with LMPA, while the latter shows the imprint with light coming from a different angle, making the chambers visible again. (D) Horizontal alignment of embryos. **Imaging** (A) Positioning of the $\mu$-well (B) Alignment in Brightfield and (C) Definition of a custom well plate.
```{r mounting, fig.cap = "(ref:mounting)", fig.scap = "Stamping procedure", out.extra = '', fig.pos = "H"}
knitr::include_graphics("figures/results/00_methods/mounting/stamp_mounting.png")
```
#### Summary
The major improvements introduced by this method are (1) using a low percentage LMPA, which extends the timespan for mounting which is necessary to align a higher number of embryos. It also gives the embryo more freedom to grow during longer time-lapse imaging and facilitates retrieval of afterwards. (2) using a stamped cast, which allows for standardized and reproducible positions of the embryo as shown for the lateral line primordium, the eye and the otic vesicle (figure \@ref(fig:stampresults)E). A significant increase of number of embryos that can be imaged during a single experiment (figure \@ref(fig:stampresults)C-D). A significant reduction of the Z-stack size and therefore of the illumination of the samples (figure \@ref(fig:stampresults)A).
In comparison to existing methods we provide a solution that is easy and in-expensive even for non-specialized labs. Also, while similar methods are well suited for high throughput and lower resolutions, ours may also be used for long time-lapse and high resolution imaging.
### anaLLzr2D - Automated 2D neuromast analysis and nuclei count {#mat-anallzr2d}
For LL analysis I developed a custom IJ macro script that segments individual cell clusters and the pLLP. From the opening dialog (figure \@ref(fig:anallzr2ddialog)) the user can choose to _count nuclei_ and / or _sort ROIs_. If nuclei count is chosen, the macro expects a dual-channel (Ch1: _cldnb:lyn-gfp_; Ch2: a nuclei label) _tiff_-file as input. If ROI sorting is selected, segmented CCs are numbered and sorted from left to right instead of top to bottom (IJ's native sorting method).
- _Membrane label blur_ controls the detail of pLLP and CC segmentation, where lower values result in more detail
- _Closing filter_ controls how harsh objects are separated from each other
- _Nuclei label blur_ controls the details of single nuclei and is evaluated after ground truth data described in section \@ref(mat-GrTrDat)
(ref:anallzr2ddialog) anaLLzR2D opening dialog **checkboxes** Choose to count nuclei and whether ROIs sorting should be applied. **sliders** Choose filter and blurring levels.
```{r anallzr2ddialog, out.width = '40%', fig.cap = "(ref:anallzr2ddialog)", fig.scap = "anaLLzR2D opening dialog", out.extra = '', fig.pos = "h"}
knitr::include_graphics("figures/materials/macros/anallzr2D_macro.png")
```
#### Image Analysis
Using ROIs as masks, the nuclei within each ROI are counted with a 2-D maxima finder. However, in their unprocessed form the images are too noisy to get meaningful results. The images therefore have to be smoothened with a blurring filter. To detect the right amount of nuclei, it is necessary to evaluate the distance over which the blurring should be applied. A typical nucleus in the pLLP is about 5 $\mu$m in diameter. To determine the right blurring value, a range of 4-6 $\mu$m in steps of 0.5 was tested. Figure \@ref(fig:maxllreg) shows a registered maximum Z projected lateral line used for ground truth evaluation.
(ref:maxllreg) Registration of 2D data. **Cluster Segments** Registered, MaxIP data with cell cluster segments of the lyn-GFP signal laid upon the DAPI signal. **Cell Labels** Magenta dots represent the maxima found within each ROI and hence the nuclei labels.
```{r maxllreg, fig.cap = "(ref:maxllreg)", fig.scap = "Registration of 2D data", out.extra = '', fig.pos = "h"}
knitr::include_graphics("figures/materials/ground_truth/clusters/clusters.png")
```
#### Code Snippets
The `[...]` symbol indicates code re-use from an earlier instance.
##### Segmentation
The membrane label image is segmented based on optimized filter parameters that were derived from trial and error. After, the macro halts for manual correction.
```{r anallzr2d_seg, echo = TRUE, eval = FALSE, size = "scriptsize", attr.source = '.numberLines'}
# Background subtraction
run("Subtract Background...", "rolling = 50");
run("Morphological Filters",
"operation = Opening element = Disk radius = 20");
run("Gaussian Blur...", "sigma = 5 scaled");
# Thresholding
setAutoThreshold("Moments dark");
setOption("BlackBackground", false);
run("Convert to Mask");
# Particle analysis
run("Analyze Particles...", "size = 250-Infinity exclude add");
roiManager("Show All without labels");
run("Enhance Contrast", "saturated = 0.35");
waitForUser("Check ROIs, correct if necessary");
if (sort) {
sortROIs();
}
```
##### Sorting
To sort manually corrected ROIs from left to right, each ROIs position in calculated relatively to total image width.
```{r anallzr2d_sorting, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# Sort ROIs from left to right
function sortROIs() {
run("Set Measurements...", "centroid redirect = None decimal = 0");
for (j = 0 ; j < roiManager("count"); j++) {
roiManager("select", j);
roiManager("measure");
x = getResult("X", 0);
w = getWidth();
a = x/w;
roiManager("rename", a);
run("Clear Results");
}
setBatchMode(false);
roiManager("sort");
for (j = 0 ; j < roiManager("count"); j++) {
roiManager("select", j);
roiManager("rename", j);
run("Clear Results");
}
for (j = 0 ; j < roiManager("count"); j++) {
roiManager("select", j);
roiManager("rename", j+1);
run("Clear Results");
}
}
```
##### Count nuclei
After smoothing the nuclei signal in the DAPI labeled channel, maxima are detected only within each ROI.
```{r anallzr2d_counts, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# count nuclei within segmented cell clusters
# steps performed on DAPI channel
# count nuclei
run("Gaussian Blur...", "sigma = 0.6 scaled");
roiManager("open", datdir + filename + "_ROIset.zip");
rcount = roiManager("count");
# for each ROI
for (j = 0 ; j < rcount; j++) {
roiManager("open", datdir + filename + "_ROIset.zip");
roiManager("select", j);
run("Duplicate...", " ");
run("Enhance Contrast", "saturated = 0.35");
run("Find Maxima...", "noise = 0 output = [Point Selection]");
run("Capture Image");
roiManager("select", j);
run("Find Maxima...", "noise = 0 output = Count");
NC = getResult("Count");
setResult("Nuclei", j, NC);
setResult("Pos", j, j + 1);
}
```
#### Data Analysis
Comparing the maxima counts of each Gaussian parameter with the Ground Truth gives an indication for false -positives resp. -negatives. In figure \@ref(fig:GrTratioCC) the relative numbers for each blurring parameter can be seen in percentage above or below the mean cell count of the ground truth (blue horizon). The red area represents false negatives, the green false positives.
(ref:GrTratioCC) Relative difference of maxima counts
```{r GrTratioCC, out.width = '75%', fig.height = 2.75, fig.pos = 'H', fig.cap = "(ref:GrTratioCC)", fig.scap = "Relative difference of maxima counts"}
NB_all_diff_mean %>%
mutate(
Group = as.character(as.numeric(str_replace(Group, 'NB', ''))/10)
) %>%
ggplot(aes(Group, mean_diff)) +
geom_bar(aes(fill = Group), colour = "black", stat = "identity", show.legend = FALSE) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 0, ymax = Inf), fill = "red", alpha = .05) +
annotate(geom = "text", x = 5.5, y = 5, fontface = "bold", label = "false negative", color = "red") +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0), fill = 'green', alpha = .025) +
annotate(geom = "text", x = 1.5, y = -5, fontface = "bold", label = "false positive", color = "darkgreen") +
geom_bar(aes(fill = Group), colour = "black", stat = "identity", show.legend = FALSE) +
geom_hline(aes(yintercept = 0, linetype = "Ground Truth 100%"), colour = "blue", size = 1) +
geom_errorbar(
aes(fill = Group, ymin = mean_diff-(sd_diff/2),
ymax = mean_diff + (sd_diff/2)), width = .2, position = position_dodge(.9)) +
labs(
title = "N = 3, Error bars = Std./2, NB = nuclei blurring",
caption = '',
x = "Blurring Parameters",
y = "Rel. Count Diff. [%]") +
scale_fill_brewer(palette = "Greys", direction = -1) +
scale_linetype_manual(name = "", values = c(1)) +
mythemeLIGHT_bottom() +
theme(legend.position = c(0.18, 0.22),
legend.text = element_text(size = 11)
)
```
\noindent To estimate the quality of nuclei detection for each parameter, the ratio of automatically detected and ground truth objects count can be calculated and compared (table \@ref(tab:nrcount)). The closer it is to 1, the better.
```{r nrcount}
NB_all_CC_NR_mean %>%
rownames_to_column() %>%
mutate(
rowname = str_replace(rowname, 'Mean_ratio', 'mean ratio'),
rowname = str_replace(rowname, 'STD', 'std')
) %>%
knitr::kable(
booktabs = T, caption = "Blurring parameter for nuclei count",
col.names = c('', '4.5', '5.0', '5.5', '6.0', '7.0'),
align = c('r', 'c', 'c', 'c', 'c', 'c')) %>%
kable_styling(full_width = T, latex_options = "striped", font_size = 9) %>%
footnote('blurring parameters', general_title = 'column headers')
```
In summary, maximum performance is achieved at a scaled parameter of 6 $\mu$m, with a ratio of `r NB_all_CC_NR_mean[1,"NB60"]` for the count objects and a standard deviation of `r NB_all_CC_NR_mean[2,"NB60"]`.
### anaLLzr2DT - Automated 2D pLLP migration and neuromast deposition analysis through time {#mat-anallzr2dt}
For the analysis of the migratory behavior (speed, acceleration) and shape (area, roundness) of the pLLP, as well as for the formation and deposition of the pro-Neuromasts through time, I developed a custom IJ macro script that segments the migrating pLLP and individual cell clusters on each frame of a timelapse. Upon macro execution the opening dialog is presented which is divided in three sections (figure \@ref(fig:anallzr2dtdialog)A).
In the **processing** section the user may choose which modules are executed.
- _Segmentation_ controls whether the images are segmented before measurement. If de-seleted the user has to provide segmentation masks separately.
- _Include Cell Clusters_ controls whether Cell Clusters should be included in the analysis. If de-selected, only the pLLP will be considered.
- _Registration_ controls whether the pLLP should be captured in time and space and saved in a separate stack.
- _Multichannel_ controls whether a second channel summary statistics from each ROI at each timepoint should be taken.
+ measurements taken are the mean, standard deviation, minimum and maximum intensity
In the **options** section the user may choose whether the macro should be run in _headless mode_ (without showing every single action), whether the input images are timeseries and whether all other windows should be closed upon start of processing.
After confirmation, the user has to enter a date of experiment as an identifier in a second dialog. Furthermore the user is presented the images physical properties pre-filled where the idea here is just a review since this is a major source of mistakes (figure \@ref(fig:anallzr2dtdialog)B). Finally, a third dialog is presented to the user giving an approximate duration and basic instructions (figure \@ref(fig:anallzr2dtdialog)C).
(ref:anallzr2dtdialog) anaLLzR2DT opening dialog **A** Opening dialog: Main functionality **B** Opening Dialog: metadata setup options **C** Time approximation dialog and basic instructions.
```{r anallzr2dtdialog, out.width = '95%', fig.cap = "(ref:anallzr2dtdialog)", fig.scap = "anaLLzR2DT opening dialog", fig.pos = "H"}
knitr::include_graphics("figures/materials/macros/anallzr2DT_macro.png")
```
Code-snippets describing the main functionality are described in the next couple of sub-sections. The `[...]` symbol indicates code re-use from an earlier instance.
###### Registration
The first module of the macro is pLLP registration in X, Y and cropping in Z. For better segmentation results, first the SNR is enhanced by background subtraction. To rotate the image the pLLPs migrational path is approximated by the position of the first and the last segment, then the image is cropped to a fixed height.
```{r anallzr2dt_reg, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# subtract background
run("Z Project...", "projection = [Average Intensity]");
ZPAVG = getTitle();
if (reg) {
print("Calculating registration parameters...");
setSlice(n);
run("Duplicate...", " ");
DORG = getTitle();
imageCalculator("Subtract create", DORG, ZPAVG);
close(DORG);
run("Morphological Filters",
"operation = Closing element=Disk radius = 15");
REG = getTitle();
run("Gaussian Blur...", "sigma = 6 scaled");
run("Duplicate...", " ");
run("Enhance Contrast...", "saturated = 0.3 normalize");
run("8-bit");
setAutoThreshold("MaxEntropy dark");
run("Convert to Mask");
# analyze segments
run("Analyze Particles...", "size = 150-10000 include exclude add");
rmcount = roiManager("count")-1;
print("rois: " + rmcount);
# angle
if(roiManager("count") == 1) {
roiManager("select", 0);
List.setMeasurements;
Angle = List.getValue("FeretAngle");
print("Angle: " + Angle);
if (Angle < 0) {Angle = Angle * (-1);}
if (Angle > 90) {Angle = (180-Angle) * (-1);}
} else {
roiManager("select", rmcount);
List.setMeasurements;
X1Line = List.getValue("X");
Y1Line = List.getValue("Y");
roiManager("select", 0);
List.setMeasurements;
X2Line = List.getValue("X");
Y2Line = List.getValue("Y");
makeLine(X1Line, Y1Line, X2Line, Y2Line);
List.setMeasurements;
Angle = List.getValue("Angle");
if (Angle < 0) {Angle = Angle*(-1);}
if (Angle > 90) {Angle = (180-Angle)*(-1);}
}
print("Angle: " + Angle);
run("Rotate... ",
"angle = " + Angle + " grid = 1 interpolation = Bilinear");
ZPAVG = getTitle();
selectWindow(REG);
run("Rotate... ",
"angle = "+ Angle +" grid = 1 interpolation = Bilinear");
run("Make Binary");
REG = getTitle();
# cropping
roiManager("reset");
run("Analyze Particles...",
"size = 150 - 10000 include add");
roiManager("select", 0);
List.setMeasurements;
XRect = List.getValue("X");
YRect = List.getValue("Y");
selectWindow(REG);
getDimensions(width, height, channels, slices, frames);
List.setMeasurements;
height = 120 / sizeX; # change height of rect here
toUnscaled(YRect);
YRect = YRect - (height / 2);
print("YRectcor: " + YRect);
}
# register
resetMinAndMax();
if (dual) {
# C1
selectWindow(ORG);
if (reg) {
print(" Registering " + embryoID +"...");
run("Rotate... ",
"angle = "+ Angle +" grid = 1 interpolation = Bilinear stack");
makeRectangle(0, YRect, width, height);
run("Crop");
}
# C2
open(dualdir + dualdirlist[q]);
dualname = replace(dualdirlist[q], ".tif", "");
if (reg) {
run("Rotate... ",
"angle="+ Angle +" grid = 1 interpolation = Bilinear stack");
makeRectangle(0, YRect, width, height);
run("Crop");
}
close();
} else {
selectWindow(ORG);
if (reg) {
run("Rotate... ",
"angle = "+ Angle +" grid = 1 interpolation = Bilinear stack");
makeRectangle(0, YRect, width, height);
run("Crop");
} else {
}
}
ORG = getTitle();
# crop ZPAVG for image calc
selectWindow(ZPAVG);
if (reg) {
makeRectangle(0, YRect, width, height);
run("Crop");
}
```
###### Segmentation
After registration of the image follows segmentation. For this we again improve SNR by background correction, following by disconnecting loosely joint segments.
```{r anallzr2dt_seg, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# background correction
print(" Segmenting "+ embryoID +"_RC...");
getDimensions(width, height, channels, slices, frames);
imageCalculator("Subtract create stack", ORG, ZPAVG);
IC = getTitle();
selectWindow(IC);
print("Bleach correction...");
run("Bleach Correction",
"correction = [Simple Ratio] background = 0");
nslbc = nSlices();
for (j = 1; j < nslbc; j++) {
setSlice(j);
run("Morphological Filters",
"operation = Closing element = Disk radius = 15");
}
run("Images to Stack", "name ="+ ORG +" title = [] use");
# segmentation
selectWindow(MC);
run("Gaussian Blur...", "sigma = 5.5 scaled stack");
# disconnect segments
run("Enhance Contrast...",
"saturated = 0.5 normalize process_all");
setSlice(n);
resetThreshold();
setAutoThreshold("MaxEntropy dark");
run("Convert to Mask",
"method = MaxEntropy background = Dark black");
run("Invert LUT");
run("Fill Holes", "stack");
run("Options...",
"iterations = 2 count = 1 pad do = Erode stack");
run("Options...",
"iterations = 2 count = 1 pad do = Open stack");
run("Options...",
"iterations = 1 count = 1 pad do = Dilate stack");
}
waitForUser("Check Segmentations");
}
```
###### Analysis
Finally we measure and save the results in the defined names and directories
```{r anallzr2dt_anal, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
for (b = 0; b < orgdirlist.length; b++) {
# get genotypes and embryoIDs from arrays
type = types[b];
embryoID = embryoIDs[b];
orgname = replace(orgdirlist[b], ".tif", "");
embryodir = output + File.separator + orgname + File.separator;
File.makeDirectory(embryodir);
# open and define binary
open(bindir+bindirlist[b]);
BIN = getTitle();
# open and define orginal
if (dual) {
open(rcdirc1 + rcdirc1list[b]);
} else {
open(rcdir + rcdirlist[b]);
}
RC = getTitle();
dotIndex = indexOf(RC, ".");
title = substring(RC, 0, dotIndex);
# enter 2nd loop to increment over each slice of the time-series
selectWindow(BIN); # select binary
pangles = newArray(nSlices() + 1);
for (i = 1 ; i <= nSlices(); i++) {
s = nSlices();
setSlice(i);
if (ccs) {
run("Analyze Particles...",
"size = 150-10000 include add");
} else {
run("Analyze Particles...",
"size = 750-10000 include add");
}
# loop though ROI List
for (j = 0 ; j < roiManager("count"); j++) {
roiManager("select", j);
run("Set Scale...",
"distance = 1 known = 0.00005 pixel = 1 unit = micron");
List.setMeasurements;
x = List.getValue("X");
roiManager("rename", x);
}
run("Properties...",
"channels = 1 slices = 1 frames = [s] unit = micron pixel_width = [xs]
pixel_height = [ys] voxel_depth = [zs] frame = [time] global");
# Sort ROIs and select last one
roiManager("Sort");
for (j = 0 ; j < roiManager("count"); j++) {
ccn = roiManager("count")+j;
if (ccn == roiManager("count")) {
ccn = "prim";
roiselect = roiManager("count")-1;
} else {
ccn = "CC"+j;
roiselect = j-1;
}
roiManager("select", roiselect);
roiManager("rename", ccn);
}
rmc = roiManager("count");
m = rmc-1;
selectWindow(RC);
# Prim registration
run("Select None");
roiManager("Select", m);
sln = getSliceNumber();
run("Enlarge...", "enlarge=6");
run("Fit Ellipse");
run("Duplicate...", "use");
rename(sln);
resetMinAndMax();
# Rotate
List.setMeasurements;
A = List.getValue("Angle");
run("Select None");
if (A < 10) {
A = A;
} else {
A = 180-A;
A = A*(-1);
}
pangles[i] = A;
run("Rotate... ", "angle = [A] grid = 1 interpolation = Bilinear slice");
run("Flip Horizontally");
# Measure and save segmented Mask ROI
pLLProis = embryodir + File.separator + "ROIs" + File.separator;
File.makeDirectory(pLLProis);
pLLPxy = embryodir + File.separator + "ROIsXY" + File.separator;
File.makeDirectory(pLLPxy);
selectWindow(BIN);
roiManager("show none"); # supress roimanager popping up
roiManager("Select", m);
# Save ROIs and XY coordinates
if (i < 10) {
slice = d2s(0,0) + d2s(i,0);
roiManager("save", pLLProis + "s" + slice + ".zip");
saveAs("XY Coordinates", pLLPxy + "s" + slice + ".txt");
} else {
roiManager("save", pLLProis + "s" + i + ".zip");
saveAs("XY Coordinates", pLLPxy + "s" + i + ".txt");
}
# Measure
run("Set Measurements...",
"area centroid bounding fit shape feret's stack redirect = None decimal = 2");
roiManager("measure");
roiManager("reset");
run("Select None");
# Calculate additional variables based on measurements
n = nResults();
r = n-1; # actual RowNumber
r2 = n-2; # RowNumber -1
if (i == 1) { # get X & Y coordinates, keep X0 and Y0 for normalization
X0 = getResult("X");
Y0 = getResult("Y");
} else {
X1 = getResult("X", r2);
X2 = getResult("X", r);
Y1 = getResult("Y", r2);
Y2 = getResult("Y", r);
}
# Width of bounding rectangle
W = getResult("Width");
# Calculations (XN = normalized X; LE = Leading Edge)
# Euclidian Distance of X + normalized to offspring 'zero'
if (i == 1) {
XED = 0;
XN = 0;
} else {
XED = sqrt((X2-X1)*(X2-X1)+(Y2-Y1)*(Y2-Y1));
XN = (X2 - X0) + XED;
}
LE = XN + (W/2); # Leading Edge
T = time * r; # Time interval
setResult("embryo", r, orgname); # set Results
setResult("group", r, type);
setResult("time", r, T);
setResult("deg", r, A);
setResult("X_ED", r, XED);
setResult("X_N", r, XN);
updateResults();
}
close(BIN, RC);
# Merge registered prim timepoints
setBatchMode("exit and display");
run("Images to Stack", "method = [Copy (top-left)] name = Stack title = [] use");
run("Properties...",
"channels = 1 slices = 1 frames = [s] unit = micron pixel_width = [xs]
pixel_height = [ys] voxel_depth = [zs] frame = [time] global");
run("Flip Horizontally", "stack");
if (dual) {
# save C1
saveAs("Tiff", pLLPdir + orgname + "-C01.tif");
close();
# open C2
open(rcdirc2 + rcdirc2list[b]);
resetMinAndMax();
RC = getTitle();
dotIndex = indexOf(RC, ".");
title = substring(RC, 0, dotIndex);
for (i = 1 ; i <= nSlices(); i++) {
s = i;
setSlice(i);
# Prim registration
if (i < 10) {
slice = d2s(0, 0) + d2s(i, 0);
roiManager("open", pLLProis + "s" + slice + ".zip");
} else {
roiManager("open", pLLProis + "s" + i + ".zip");
}
rmc = roiManager("count");
m = rmc-1;
roiManager("Select", m);
selectWindow(RC);
sln = getSliceNumber();
run("Enlarge...", "enlarge=6");
run("Fit Ellipse");
run("Duplicate...", "use");
rename(sln);
# Rotate
A = pangles[s];
run("Select None");
run("Rotate... ", "angle = [A] grid = 1 interpolation = Bilinear slice");
run("Flip Horizontally");
# select & deselect to remove selected ROIs
selectWindow(RC);
run("Select None");
roiManager("reset");
}
# close and merge individual pllp images into one stack
close(RC);
run("Images to Stack", "method = [Copy (top-left)] name = Stack title = [] use");
run("Properties...", "channels = 1 slices = 1 frames = [s] unit = micron
pixel_width = [xs] pixel_height = [ys] voxel_depth = [zs] frame = [time] global");
run("Flip Horizontally", "stack");
roiManager("reset");
# Save Results Table
run("Input/Output...",
"jpeg = 100 gif = -1 file = .txt use_file copy_column copy_row save_column");
saveAs("results", embryodir + orgname + "_Results" + ".txt");
```
### anaLLzr3D - Automated 3D single cell segmentation and A.I. analysis in the pLLP {#mat-anallzr3d}
For measurement of the apical index (section \@ref(ACI)) I developed a custom IJ macro script that segments and analyses each cell of a pLLP in 3D. Upon macro execution the opening dialog is presented which is divided in three sections (figure \@ref(fig:anallzr3ddialog)A).
In the **processing** section the user has to define the input format as well as in which direction the Z-stack was recorded. Furthermore, the user may choose to have the pLLP registered, save intermediate steps for debugging and to have objects segmented without any restrictions and manual ROI correction.
In the **thresholds** section the user may fine tune segmentation and filter thresholds:
- _Segmentation_ controls the segmentation threshold at which the membrane signal is detected and therefore the cell volumes are separated from each other (section \@ref(mat-GrTrDat))
- _Min. volume_ controls the minimum volume, below which objects are discarded
- _Max. volume_ controls the maximum volume, above which objects are discarded
In the **measurements** section, the user may choose whether apical constriction measurement should be applied or not. In case apical constriction measurement is selected, the user may choose from a second dialog box whether A.I. should be measured at an absolute- or relative- distance from the tip. Furthermore, the user has the option to measure from a fit ellipsoid (as done for the A.I. measurement described in section \@ref(ACI)) or rectangle.
(ref:anallzr3ddialog) anaLLzR3D opening dialog **A** Opening dialog: Main functionality **B** Opening Dialog: Apical Constriction options **C** Log window after startup
```{r anallzr3ddialog, out.width = '95%', fig.cap = "(ref:anallzr3ddialog)", fig.pos = "H", fig.scap = "anaLLzR3D opening dialog"}
knitr::include_graphics("figures/materials/macros/anallzr3D_macro.png")
```
#### Image Analysis
For pLLP analyses I developed a custom IJ macro script that recognizes cell boundaries _via_ the fluorescence signal emitted by a membrane tethered eGFP which expression is controlled by the _claudinB_ lateral line specific promotor [@Haas2006c]. The central IJ tool used to do this is the MorphoLibJ's[@Legland2016] _Morpholigical Segmentation_ plugin. The plugin however requires to choose for a 'segmentation threshold' that determines the quality and the quantity of segmented objects. This parameter therefore plays an essential role in the reliability of the analysis results.
##### Registration
The first module of the macro is the registration of the pLLP in X, Y and cropping in Z. This is accomplished by an initial maximum Z projection and blurring of the image, 2-D segmentation using a minimum threshold and lastly by rotating the segment through the angle formed by the long axis of the ellipsoid (see section \@ref(ACI-pol) for more information) and the horizon (at 0$^{\circ}$). After rotation the image is cropped according to the obtained ROI, as described before. Additionally, the centers of the most constricting areas are detected _via_ an intensity based dynamic threshold and highlighted as magenta circles in figure \@ref(fig:maxraw).
(ref:maxraw) Registration of 3D data. **(unregistered)** location and orientation of the unregistered MaxIPs in XY. The red line indicates the angle in degrees from the horizontal midline. The blue oval indicates registration ROI as determined by the macro. **(registered)** pLLPs after XY transformation took place. red circles indicate rosette centers as detected by the macro based on maximum signal intensity.
```{r maxraw, fig.cap = "(ref:maxraw)", fig.scap = "Registration of 3D data"}
knitr::include_graphics(path = "figures/materials/ground_truth/registration.png")
```
```{r imgprop, fig.pos = "H"}
read.delim("tables/ground_truth/scaling_3D.txt") %>%
knitr::kable(
booktabs = T, escape = F,
caption = "3-D Ground Truth image scaling",
align = c("r", "l"),
col.names = NULL) %>%
kable_styling(full_width = T, latex_options = c('striped', 'hold_position'))
```
##### Image data
In figure \@ref(fig:stackmem) the fluorescence signal of the three pLLPs used for the Ground-Truth is shown in a single central cross-section along the _dorso-ventral_ and the _apico-basal_ axis.
(ref:stackmem) _cldnb:lyn-gfp_ fluorescence signal in a cross-section of the pLLP (Obj.: 40X APO, scale bar = 100 $\mu$m)
```{r stackmem, out.width = '70%', fig.cap = "(ref:stackmem)", fig.scap = "Image data pLLP segmentation"}
knitr::include_graphics("figures/materials/ground_truth/stackmem.png")
```
To compare the results between the Ground Truth segments and the segments obtained from different threshold levels graphically, for a single pLLP the Ground Truth and threshold levels are shown as a composite color image in figure \@ref(fig:anallzrvols). By using a green lookup-table (LUT) for the Ground Truth and a magenta LUT for the threshold level[n], one can readily detect overlapping objects (white), over segmentation (magenta) and under segmentation (green). False _positive_ segments are cells that are not part of the Ground Truth, including those cells that would distort our dataset. False _negative_ segments are cells that are part of the Ground Truth but were not detected by the macro (green cells), excluding those cells impacts the cell count. As one can see, the green cells are randomly distributed, therefore, when averaging a variable for all cells, those cells are less likely to distort our dataset. Only if under segmentation becomes too high will it impact the distribution of values. At Treshold Level 2 (T02) we have the least green cells, but the most magenta cells. At Threshold Level 4 (T04) we have no magenta, but some more green cells. Therefore, to be on the safe side in terms of cell parameters and dataset integrity, for the definition of our Ground Truth T04 would be the best pick.
(ref:anallzrvols) Graphical comparison of the thresholds tested. Volume renderings have been done with IJ's [VolumeViewer]("https://github.com/fiji/Volume_Viewer/releases/tag/Volume_Viewer-2.01.2")
```{r anallzrvols, out.width = '85%', fig.cap = "(ref:anallzrvols)", fig.scap = 'anaLLzr3D - Graphical comparison of tested thresholds'}
knitr::include_graphics("figures/materials/ground_truth/volumes.png")
```
\noindent
#### Code Snippets
The `[...]` symbol indicates code re-use from an earlier instance.
##### Registration
First we need to get parameters angle and height for registration. All steps are performed on Z-projected data.
```{r anallzr_macro_mod_1, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# 2D segmentation mask
run("Z Project...", "projection = [Max Intensity]");
run("Gaussian Blur...", "sigma = 8 scaled");
setAutoThreshold("Minimum dark");
run("Convert to Mask");
run("Select None");
# angle from horizontal midline
run("Analyze Particles...", "include add");
rmcount = roiManager("count")-1;
if(roiManager("count") == 1) {
roiManager("select", 0);
run("Fit Ellipse");
List.setMeasurements;
Angle = List.getValue("FeretAngle");
if (Angle < 0) {Angle = Angle * (-1);}
if (Angle > 90) {Angle = (180 - Angle) * (-1);}
} else {
roiManager("select", 0);
run("Fit Ellipse");
roiManager("update");
List.setMeasurements;
X1Line = List.getValue("X");
Y1Line = List.getValue("Y");
roiManager("select", rmcount);
List.setMeasurements;
X2Line = List.getValue("X");
Y2Line = List.getValue("Y");
makeLine(X1Line, Y1Line, X2Line, Y2Line);
List.setMeasurements;
Angle = List.getValue("Angle");
if (Angle < 0) {Angle = Angle*(-1);}
if (Angle > 90) {Angle = (180-Angle)*(-1);}
}
run("Select None");
run("Rotate... ", "angle = "+ Angle +" grid = 1 interpolation = Bilinear");
# height to crop image to
roiManager("reset"); # the image was rotated, so we need to get the ROIs again
run("Select None");
run("Make Binary");
run("Erode");
run("Analyze Particles...", "size = 150-10000 include exclude add");
rmcount = roiManager("count") - 1;
if(roiManager("count") == 1) {
roiManager("select", 0);
} else {
roiManager("select", rmcount);
}
List.setMeasurements;
XRect = List.getValue("X");
YRect = List.getValue("Y");
getDimensions(width, height, channels, slices, frames);
Regwidth = width;
Regheight = 400; # change height of rectangle here
toUnscaled(YRect);
YRect = YRect - (Regheight/2);
```
##### Transformation
Next we transform our 3D data based on the registration parameters derived from the previous step.
```{r anallzr_macro_mod_2, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# register pLLP
run("Rotate... ", "angle = "+ Angle +" grid = 1 interpolation = Bilinear stack");
makeRectangle(0, YRect, Regwidth, Regheight);
run("Crop");
# create threshold mask to clear signals outside ROI
run("Normalize Local Contrast",
"block_radius_x = 300 block_radius_y = 20 standard_deviations = 4 stretch");
run("Gaussian Blur...", "sigma = 1 scaled");
setAutoThreshold("Otsu dark");
run("Convert to Mask");
# most right roi
for (j = 0 ; j < roiManager("count"); j++) {
roiManager("select", j);
run("Set Scale...", "distance = 1 known = 0.00005 pixel = 1 unit = micron");
List.setMeasurements;
x = List.getValue("X");
roiManager("rename", x);
}
roiManager("Sort");
run("Properties...",
"channels = 1 slices = 1 frames = 1 unit = micron pixel_width = [sizeX]
pixel_height = [sizeY] voxel_depth = [sizeZ]");
primroi = roiManager("count") - 1;
roiManager('select', primroi);
# enlarge rois to not miss anything
run("Enlarge...", "enlarge = 10");
run("Fit Ellipse");
roiManager('update');
```
##### Rosette detection
To analyze cells within rosettes resp. within a certain radius of rosettes we first need to know where the rosettes are. At rosette centers we observe an increase in signal intensity since here the membranes of many cells come together in a very small area. This effect we can use to utilize a maximum finder algorithm together with a threshold that is defined individually for each image.
```{r anallzr_macro_mod_3, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# [...] registration
run("Gaussian Blur...", "sigma = 4 scaled");
List.setMeasurements;
mean = List.getValue("Mean");
pointthresh = mean/2.5;
pointthresh = round(pointthresh);
run("Find Maxima...", "noise = "+ pointthresh +" output = [Point Selection]");
run("Point Tool...", "type = Dot color = Green size = [Extra Large] label counter = 0");
getSelectionCoordinates(xpoints, ypoints);
roiManager("Add");
# measure intensities along horizontal midline
# [...] registration
Rlx = lengthOf(xpoints); # collect Arrays
RX = Array.sort(xpoints); # put xpoints in right order
RX = Array.invert(RX);
# fill ypoints with mean values of all y coordinates
Array.getStatistics(ypoints, min, max, mean, stdDev);
meanline = mean;
Array.fill(ypoints, meanline);
RY = ypoints;
getDimensions(width, height, channels, slices, frames);
makeLine(0, meanline, width, meanline, 1);
run("Clear Results");
profile = getProfile();
for (a = 0; a < profile.length; a++) {
setResult("Value", a, profile[a]);
updateResults();
}
```
##### Segmentation
For image segmentation we use the MorphoLibJ's[@Legland2016] _Morpholigical Segmentation_ plugin. Function calls and arguments are defined in the publication documentation.
```{r anallzr_macro_mod_4, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# 3D gaussian blur
run("Gaussian Blur 3D...", "x=2 y=2 z=0.5");
resetMinAndMax();
# run segmentation
run("Morphological Segmentation");
selectWindow("Morphological Segmentation");
call("inra.ijpb.plugins.MorphologicalSegmentation.setInputImageType", "border");
call("inra.ijpb.plugins.MorphologicalSegmentation.segment", "tolerance = " + tol + "",
"calculateDams = true", "connectivity = 6");
# wait till segmentation is done
initTime = getTime();
oldTime = initTime;
while (isOpen("Morphological Segmentation")) {
elapsedTime = getTime() - initTime;
newTime = getTime() - oldTime;
if (newTime > 10000) {
oldTime = getTime();
newTime = 0;
loginfo = getInfo("log");
loginfo = split(loginfo, " ");
loginfo = Array.reverse(loginfo);
loginfo = Array.trim(loginfo, 5);
loginfo = Array.reverse(loginfo);
loginfo = split(loginfo[0], ".");
loginfo = Array.reverse(loginfo);
loginfo = loginfo[0];
if (loginfo == "\nWhole") {
call("inra.ijpb.plugins.MorphologicalSegmentation.setDisplayFormat",
"Catchment basins");
call("inra.ijpb.plugins.MorphologicalSegmentation.createResultImage");
run("Grays");
selectWindow("Morphological Segmentation");
close();
}
}
}
run("Properties...", "channels = 1 slices = " + n + " frames = 1 unit =
microns pixel_width = " + sizeX + " pixel_height = " + sizeY + "voxel_depth = "+ sizeZ);
```
##### Filter and clearing
Remove segments below a certain volume threshold defined in the startup dialog and clear blank slices in Z.
```{r anallzr_macro_mod_5, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# erase objects V < vmin and V > vmax
run("3D Manager Options", "volume surface compactness fit_ellipse 3d_moments
feret centroid_(pix) centroid_(unit) distance_to_surface centre_of_mass_(unit)
bounding_box radial_distance surface_contact closest exclude_objects_on_edges_xy
sync distance_between_centers = 10 distance_max_contact = 1.80");
run("3D Manager");
selectWindow(OMap);
Ext.Manager3D_AddImage();
# get number of objects
Ext.Manager3D_Count(nb);
Ext.Manager3D_MultiSelect();
# loop through all the objects and erase by filter settings
for(k = 0; k < nb; k++) {
showStatus("Processing "+ k +"/"+ nb);
Ext.Manager3D_Measure3D(k, "Vol",V);
if (V < vmin) {
Ext.Manager3D_Select(k);
Ext.Manager3D_Erase();
if (V > vmax) {
Ext.Manager3D_Select(k);
Ext.Manager3D_Erase();
}
}
}
# clean blank slices from bottom and top
getDimensions(width, height, channels, slices, frames);
var done = false;
for(l = 1; l < slices &&!done; l++) {
setSlice(l);
getStatistics(area, mean, min, max, std, histogram);
if(max > 0) {
amax = l-1;
run("Slice Remover", "first = 1 last = "+ amax +" increment = 1");
run("Reverse");
getDimensions(width, height, channels, slices, frames);
for(l = 1; l < slices &&!done; l++) {
setSlice(l);
getStatistics(area, mean, min, max, std, histogram);
if(max > 0) {
bmax = l-1;
run("Slice Remover", "first = 1 last = "+ bmax +" increment = 1");
run("Reverse");
done = true;
}
}
}
}
```
##### Apical Constriction
Next we navigate to the calculated relative distance from the apical site and take measurements.
```{r anallzr_macro_mod_6, echo = T, eval = F, size = "scriptsize", attr.source = '.numberLines'}
# define Z values
if (rosAC) {
cellum = rosum; # I copy pasted the code, so rosette um would be cell um
Zslice = rosum/sizeZ;
round(Zslice);
}
# cell Constriction