GPU accelerated image processing for everyone
Authors: Robert Haase, Daniela Vorkel, April 2020
This script is an example of heavy GPU-accelerated processing. It is recommended to use a dedicated graphics card with at least 8 GB of GDDR6 memory. Otherwise, it may be quite slow.
Let’s initialize that graphics card and mesure the start time.
run("CLIJ2 Macro Extensions", "cl_device=[GeForce RTX 2060 SUPER]"); Ext.CLIJ2_clear(); run("Close All"); time = getTime(); Ext.CLIJ2_startTimeTracing();
The dataset is available online. It shows a Tribolium castaneum embryo, imaged by a custom light sheet microscope, at a wavelength of 488nm (Imaging credits: Daniela Vorkel, Myers lab, MPI CBG). The data set has been resampled to a voxel size of 1x1x1 microns. The embryo expresses nuclei-GFP. We will use the dataset to detect nuclei and to generate an estimated cell-segmentation.
All processing steps are performed in 3D space. For visualization purpose, we are using the maximum intensity projection in Z:
path = "C:/structure/teaching/clij2_example_data/"; open(path + "lund1051_resampled.tif"); input = getTitle(); print("Loading took " + (getTime() - time) + " msec"); Ext.CLIJ2_push(input); run("Close All"); // visualize the dataset show(input, "input");
> Loading took 265 msec
After some noise removal/smoothing, we perform a local maximum detection:
// gaussian blur sigma = 2; Ext.CLIJ2_gaussianBlur3D(input, blurred, sigma, sigma, sigma); // detect maxima radius = 2.0; Ext.CLIJ2_detectMaximaBox(blurred, detected_maxima, radius); show_spots(detected_maxima, "detected maxima");
Now, we remove spots with values below a certain intensity and label the remaining spots.
// threshold threshold = 300.0; Ext.CLIJ2_threshold(blurred, thresholded, threshold); // mask Ext.CLIJ2_mask(detected_maxima, thresholded, masked_spots); // label spots Ext.CLIJ2_labelSpots(masked_spots, labelled_spots); show_spots(labelled_spots, "selected, labelled spots"); run("glasbey_on_dark");
Let’s see how many spots are left:
Ext.CLIJ2_getMaximumOfAllPixels(labelled_spots, number_of_spots); print("Number of detected spots: " + number_of_spots);
> Number of detected spots: 1131
Next, we spatially extend the labelled spots by applying a maximum filter.
// label map closing number_of_dilations = 10; number_of_erosions = 4; Ext.CLIJ2_copy(labelled_spots, flip); for (i = 0; i < number_of_dilations; i++) { Ext.CLIJ2_onlyzeroOverwriteMaximumBox(flip, flop); Ext.CLIJ2_onlyzeroOverwriteMaximumDiamond(flop, flip); if (i % 2 == 0) { show(flip, "Extended spots after " + (i * 2) + " dilations"); run("glasbey_on_dark"); } }
Afterwards, we erode all labels in the map and get a final result of cell segementation.
Ext.CLIJ2_threshold(flip, flap, 1); for (i = 0; i < number_of_erosions; i++) { Ext.CLIJ2_erodeBox(flap, flop); Ext.CLIJ2_erodeBox(flop, flap); } Ext.CLIJ2_mask(flip, flap, labels); show(labels, "cell segmentation"); run("glasbey_on_dark");
We also save all labels to disc to use them as starting point in other notebooks, later.
Ext.CLIJ2_pull(labels); saveAs("TIF", path + "lund1051_labelled.tif"); close();
We then read out all current positions of detected nuclei as a pointlist to generate a distance matrix of all nuclei towards each other:
Ext.CLIJ2_labelledSpotsToPointList(labelled_spots, pointlist); Ext.CLIJ2_generateDistanceMatrix(pointlist, pointlist, distance_matrix); show(distance_matrix, "distance matrix");
Starting from the label map of segmented cells, we generate a touch matrix:
Ext.CLIJ2_generateTouchMatrix(labels, touch_matrix); // touch matrix: show_spots(touch_matrix, "touch matrix");
Using element by element multiplication of a distance matrix and a touch matrix, we calculate the length of each edge. We use this result to draw a mesh with a color gradient of distance (between 0 and 50 micron):
Ext.CLIJ2_multiplyImages(touch_matrix, distance_matrix, touch_matrix_with_distances); Ext.CLIJ2_getDimensions(input, width, height, depth); Ext.CLIJ2_create3D(mesh, width, height, depth, 32); Ext.CLIJ2_touchMatrixToMesh(pointlist, touch_matrix_with_distances, mesh); show(mesh, "distance mesh"); run("Green Fire Blue"); setMinAndMax(0, 50);
Next, we determine the average distance between a node and of all its neighbors. The resulting vector has as many entries as nodes in the graph. We use this vector to color-code the label map of segmented cells. This means, label 1 gets replaced by the average distance to node 1, label 2 by the average distance to node 2, et cetera.
Ext.CLIJ2_setColumn(touch_matrix, 0, 0); Ext.CLIJ2_averageDistanceOfTouchingNeighbors(distance_matrix, touch_matrix, distances_vector); // we replace NaN values with zeros so that later maximum-projections work. Ext.CLIJ2_copy(distances_vector, distances_vector1); Ext.CLIJ2_undefinedToZero(distances_vector1, distances_vector); Ext.CLIJ2_replaceIntensities(labels, distances_vector, distance_map); show(distance_map, "distance map"); run("Fire"); setMinAndMax(0, 50);
Now, we measure the mean between neighbors and visualize it as above.
Ext.CLIJ2_meanOfTouchingNeighbors(distances_vector, touch_matrix, local_mean_distances_vector); Ext.CLIJ2_replaceIntensities(labels, local_mean_distances_vector, local_mean_pixel_count_map); show(local_mean_pixel_count_map, "neighbor mean distance map"); run("Fire"); setMinAndMax(0, 50);
We can also use the minimum, median and maximum to measure distances:
Ext.CLIJ2_minimumOfTouchingNeighbors(distances_vector, touch_matrix, local_minimum_distances_vector); Ext.CLIJ2_replaceIntensities(labels, local_minimum_distances_vector, local_minimum_pixel_count_map); show(local_minimum_pixel_count_map, "neighbor minimum distance map"); run("Fire"); setMinAndMax(0, 50); Ext.CLIJ2_medianOfTouchingNeighbors(distances_vector, touch_matrix, local_median_distances_vector); Ext.CLIJ2_replaceIntensities(labels, local_median_distances_vector, local_median_pixel_count_map); show(local_median_pixel_count_map, "neighbor median distance map"); run("Fire"); setMinAndMax(0, 50); Ext.CLIJ2_maximumOfTouchingNeighbors(distances_vector, touch_matrix, local_maximum_distances_vector); Ext.CLIJ2_replaceIntensities(labels, local_maximum_distances_vector, local_maximum_pixel_count_map); show(local_maximum_pixel_count_map, "neighbor maximum distance map"); run("Fire"); setMinAndMax(0, 50); Ext.CLIJ2_standardDeviationOfTouchingNeighbors(distances_vector, touch_matrix, local_stddev_distances_vector); Ext.CLIJ2_replaceIntensities(labels, local_stddev_distances_vector, local_stddev_pixel_count_map); show(local_stddev_pixel_count_map, "neighbor standard deviation distance map"); run("Fire"); setMinAndMax(0, 50);
Finally, a time measurement. Note that performing this workflow in ImageJ macro markdown is slower, because intermediate results are saved to disc.
print("The whole workflow took " + (getTime() - time) + " msec");
> The whole workflow took 4245 msec
Ext.CLIJ2_stopTimeTracing(); Ext.CLIJ2_getTimeTracing(time_traces); print(time_traces);
> > timeTracing > > MaximumZProjection > < MaximumZProjection 2.9759 ms > > Copy > < Copy 9.0152 ms > > GaussianBlur3D > < GaussianBlur3D 44.7836 ms > > DetectMaximaBox > > Mean3DBox > < Mean3DBox 37.0359 ms > < DetectMaximaBox 53.6302 ms > > Maximum3DBox > > Copy > < Copy 8.4579 ms > < Maximum3DBox 35.7375 ms > > MaximumZProjection > < MaximumZProjection 1.5405 ms > > Threshold > > GreaterOrEqualConstant > < GreaterOrEqualConstant 7.437 ms > < Threshold 7.449 ms > > Mask > < Mask 7.8011 ms > > LabelSpots > > Set > < Set 7.2574 ms > > SumXProjection > < SumXProjection 4.1411 ms > > SumYProjection > < SumYProjection 0.6554 ms > < LabelSpots 24.7585 ms > > Maximum3DBox > > Copy > < Copy 7.7649 ms > < Maximum3DBox 32.5717 ms > > MaximumZProjection > < MaximumZProjection 1.173 ms > > GetMaximumOfAllPixels > > MaximumOfAllPixels > > MaximumZProjection > < MaximumZProjection 1.283 ms > > MaximumYProjection > < MaximumYProjection 0.3715 ms > > MaximumXProjection > < MaximumXProjection 0.3312 ms > < MaximumOfAllPixels 2.9054 ms > < GetMaximumOfAllPixels 2.9123 ms > > Copy > < Copy 7.2455 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 11.9568 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5826 ms > > MaximumZProjection > < MaximumZProjection 1.253 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 5.0572 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5588 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.7444 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.6358 ms > > MaximumZProjection > < MaximumZProjection 1.5018 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 5.0227 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5737 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.7476 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5303 ms > > MaximumZProjection > < MaximumZProjection 1.2085 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.7258 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.478 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.663 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5056 ms > > MaximumZProjection > < MaximumZProjection 1.3752 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.6049 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5639 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.6176 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5136 ms > > MaximumZProjection > < MaximumZProjection 1.1664 ms > > OnlyzeroOverwriteMaximumBox > < OnlyzeroOverwriteMaximumBox 4.4245 ms > > OnlyzeroOverwriteMaximumDiamond > < OnlyzeroOverwriteMaximumDiamond2.5655 ms > > Threshold > > GreaterOrEqualConstant > < GreaterOrEqualConstant 7.9888 ms > < Threshold 8.0028 ms > > ErodeBox > < ErodeBox 3.1491 ms > > ErodeBox > < ErodeBox 2.9736 ms > > ErodeBox > < ErodeBox 2.8717 ms > > ErodeBox > < ErodeBox 2.7934 ms > > ErodeBox > < ErodeBox 2.7161 ms > > ErodeBox > < ErodeBox 2.7864 ms > > ErodeBox > < ErodeBox 2.593 ms > > ErodeBox > < ErodeBox 2.5627 ms > > Mask > < Mask 7.6373 ms > > MaximumZProjection > < MaximumZProjection 1.6464 ms > > LabelledSpotsToPointList > < LabelledSpotsToPointList 1.3793 ms > > GenerateDistanceMatrix > < GenerateDistanceMatrix 1.9646 ms > > MaximumZProjection > < MaximumZProjection 1.1825 ms > > GenerateTouchMatrix > > Set > < Set 1.0726 ms > < GenerateTouchMatrix 2.9299 ms > > Maximum3DBox > < Maximum3DBox 2.2232 ms > > MaximumZProjection > < MaximumZProjection 0.8365 ms > > MultiplyImages > < MultiplyImages 1.0607 ms > > GetDimensions > < GetDimensions 0.0028 ms > > TouchMatrixToMesh > < TouchMatrixToMesh 7.4039 ms > > MaximumZProjection > < MaximumZProjection 0.7299 ms > > SetColumn > < SetColumn 0.4095 ms > > AverageDistanceOfTouchingNeighbors > < AverageDistanceOfTouchingNeighbors0.759 ms > > Copy > < Copy 0.2599 ms > > UndefinedToZero > < UndefinedToZero 0.2419 ms > > ReplaceIntensities > < ReplaceIntensities 7.5108 ms > > MaximumZProjection > < MaximumZProjection 0.6875 ms > > MeanOfTouchingNeighbors > < MeanOfTouchingNeighbors 0.8507 ms > > ReplaceIntensities > < ReplaceIntensities 7.505 ms > > MaximumZProjection > < MaximumZProjection 0.6716 ms > > MinimumOfTouchingNeighbors > < MinimumOfTouchingNeighbors 1.1877 ms > > ReplaceIntensities > < ReplaceIntensities 7.9356 ms > > MaximumZProjection > < MaximumZProjection 0.8287 ms > > MedianOfTouchingNeighbors > < MedianOfTouchingNeighbors 1.0459 ms > > ReplaceIntensities > < ReplaceIntensities 7.8399 ms > > MaximumZProjection > < MaximumZProjection 0.7293 ms > > MaximumOfTouchingNeighbors > < MaximumOfTouchingNeighbors 0.9384 ms > > ReplaceIntensities > < ReplaceIntensities 7.7835 ms > > MaximumZProjection > < MaximumZProjection 0.7976 ms > > ReplaceIntensities > < ReplaceIntensities 7.8999 ms > > MaximumZProjection > < MaximumZProjection 0.6905 ms > < timeTracing 4249.9027 ms >
Also, let’s see how much of GPU memory got used by this workflow. At the end, cleaning up remains important.
Ext.CLIJ2_reportMemory(); // finally, clean up Ext.CLIJ2_clear();
> GPU contains 28 images. > - CLIJ2_threshold_result102[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@34952cde] 204.8 Mb > - CLIJ2_maximumOfTouchingNeighbors_result139[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@553a28fd] 4.4 kb > - CLIJ2_generateTouchMatrix_result120[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@68258ac7] 4.9 Mb > - CLIJ2_mask_result103[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@22f19cba] 204.8 Mb > - CLIJ2_minimumOfTouchingNeighbors_result133[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@3f9fa7e6] 4.4 kb > - CLIJ2_onlyzeroOverwriteMaximumBox_result108[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@3be61d28] 204.8 Mb > - CLIJ2_standardDeviationOfTouchingNeighbors_result142[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@7fd1db21] 4.4 kb > - CLIJ2_gaussianBlur3D_result98[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@51eb8c2d] 204.8 Mb > - CLIJ2_replaceIntensities_result137[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@1389b161] 204.8 Mb > - CLIJ2_detectMaximaBox_result99[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@188df165] 204.8 Mb > - CLIJ2_replaceIntensities_result134[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@69743986] 204.8 Mb > - CLIJ2_copy_result107[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@64f35a61] 204.8 Mb > - CLIJ2_copy_result127[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@3a2fed27] 4.4 kb > - CLIJ2_replaceIntensities_result131[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@4dde25de] 204.8 Mb > - CLIJ2_threshold_result114[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@5ae9dea] 204.8 Mb > - CLIJ2_medianOfTouchingNeighbors_result136[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@7b70ff6c] 4.4 kb > - CLIJ2_create3D_result124[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@3ef363f0] 204.8 Mb > - lund1051_resampled.tif[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@2bf5125] 204.8 Mb > - CLIJ2_multiplyImages_result123[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@730fdd55] 4.9 Mb > - CLIJ2_labelSpots_result104[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@f78d7c7] 204.8 Mb > - CLIJ2_mask_result115[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@2ab1d93b] 204.8 Mb > - CLIJ2_averageDistanceOfTouchingNeighbors_result126[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@527f9331] 4.4 kb > - CLIJ2_meanOfTouchingNeighbors_result130[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@5f17195e] 4.4 kb > - CLIJ2_replaceIntensities_result128[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@16183b94] 204.8 Mb > - CLIJ2_labelledSpotsToPointList_result117[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@32c4506d] 13.3 kb > - CLIJ2_replaceIntensities_result143[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@1c0cab1f] 204.8 Mb > - CLIJ2_replaceIntensities_result140[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@7b369c29] 204.8 Mb > - CLIJ2_generateDistanceMatrix_result118[net.haesleinhuepf.clij.clearcl.ClearCLPeerPointer@2ca5dc40] 4.9 Mb > = 3.4 Gb >
Following methods are convenient for a proper visualization in a notebook:
function show(input, text) { Ext.CLIJ2_maximumZProjection(input, max_projection); Ext.CLIJ2_pull(max_projection); setColor(100000); drawString(text, 20, 20); Ext.CLIJ2_release(max_projection); } function show_spots(input, text) { Ext.CLIJ2_maximum3DBox(input, extended, 1, 1, 0); Ext.CLIJ2_maximumZProjection(extended, max_projection); Ext.CLIJ2_pull(max_projection); setColor(100000); drawString(text, 20, 20); Ext.CLIJ2_release(extended); Ext.CLIJ2_release(max_projection); }
</pre>