Awesome
Yet Another Connected Components Labeling Benchmark
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<!-- ALL-CONTRIBUTORS-BADGE:END --> <table> <thead> <tr> <th>OS</th> <th>Build</th> <th>Compiler</th> <th>OpenCV</th> <th>CMake</th> <th>GPU</th> <!--<th width="200px">Travis CI</th>--> <th width="200px">GitHub Actions</th> <th width="200px">Jenkins</th> </tr> <thead> <tbody> <!-- <tr> <td align="center">Ubuntu<br/>16.04 LTS</td> <td align="center">x32</td> <td align="center">gcc 5.4.0</td> <td align="center">3.0.0</td> <td align="center">3.13.5</td> <td align="center">None</td> <td align="center"><a href="https://github.com/prittt/YACCLAB/actions"><img src="https://github.com/prittt/YACCLAB/workflows/linux32/badge.svg?branch=master" alt="Build Status"/></a></td> <td align="center">N/A</td> </tr> --> <tr> <td align="center">Ubuntu<br/>18.04 LTS</td> <td align="center">x64</td> <td align="center">gcc 9.3.0</td> <td align="center">4.1.2</td> <td align="center">3.13.5</td> <td align="center">None</td> <td align="center"><a href="https://github.com/prittt/YACCLAB/actions"><img src="https://github.com/prittt/YACCLAB/workflows/linux64/badge.svg?branch=master" alt="Build Status"/></a></td> <td align="center">N/A</td> </tr> <tr> <td align="center">MacOS<br/>(Darwin 19.6.0)</td> <td align="center">x64</td> <td align="center">AppleClang 12.0.0<br/>(Xcode-12)</td> <td align="center">3.1.0</td> <td align="center">3.13.0</td> <td align="center">None</td> <td align="center"><a href="https://github.com/prittt/YACCLAB/actions"><img src="https://github.com/prittt/YACCLAB/workflows/macos/badge.svg?branch=master" alt="Build Status"/></a></td> <td align="center">N/A</td> </tr> <tr> <td align="center">Ubuntu<br/>16.04 LTS</td> <td align="center">x64</td> <td align="center">gcc 5.4.0</td> <td align="center">4.4</td> <td align="center">3.10.3</td> <td align="center">2080Ti, CUDA 9.2</td> <td align="center">N/A</td> <td align="center"><a href="https://jenkins-master-deephealth-unix01.ing.unimore.it/job/YACCLAB/job/master/"><img src="https://jenkins-master-deephealth-unix01.ing.unimore.it/badge/job/YACCLAB/job/master/ubuntu16_gpu_end?" alt="Action Status"/></a></td> </tr> <tr> <td align="center">Ubuntu<br/>20.04.02 LTS</td> <td align="center">x64</td> <td align="center">gcc 9.3.0</td> <td align="center">4.4</td> <td align="center">3.10.3</td> <td align="center">2080Ti, CUDA 11.4</td> <td align="center">N/A</td> <td align="center"><a href="https://jenkins-master-deephealth-unix01.ing.unimore.it/job/YACCLAB/job/master/"><img src="https://jenkins-master-deephealth-unix01.ing.unimore.it/badge/job/YACCLAB/job/master/ubuntu20_gpu_end?" alt="Action Status"/></a></td> </tr> </tbody> </table> <p align="justify">Please include the following references when citing the YACCLAB project/dataset:</p>- <p align="justify"> Allegretti, Stefano; Bolelli, Federico; Grana, Costantino "Optimized Block-Based Algorithms to Label Connected Components on GPUs." IEEE Transactions on Parallel and Distributed Systems, 2019. <a title="BibTex" href="https://federicobolelli.it/pub_files/2019tpds.html">BibTex</a>. <a title="Download" href="https://federicobolelli.it/pub_files/2019tpds.pdf">PDF</a>.</p>
- <p align="justify"> Bolelli, Federico; Cancilla, Michele; Baraldi, Lorenzo; Grana, Costantino "Towards Reliable Experiments on the Performance of Connected Components Labeling Algorithms" Journal of Real-Time Image Processing, 2018. <a title="BibTex" href="https://federicobolelli.it/pub_files/2018jrtip.html">BibTex</a>. <a title="Download" href="https://federicobolelli.it/pub_files/2018jrtip.pdf">PDF</a>.</p>
- <p align="justify"> Grana, Costantino; Bolelli, Federico; Baraldi, Lorenzo; Vezzani, Roberto "YACCLAB - Yet Another Connected Components Labeling Benchmark" Proceedings of the 23rd International Conference on Pattern Recognition, Cancun, Mexico, 4-8 Dec 2016. <a title="BibTex" href="https://federicobolelli.it/pub_files/2016icpr.html">BibTex</a>. <a title="Download" href="https://federicobolelli.it/pub_files/2016icpr.pdf">PDF</a>.</p>
Notice that 8-connectivity is always used in the project.
</p>Reproducible Research
<p align="justify">This project follows the Reproducible Research paradigms and received the <a href="https://github.com/RLPR">Reproducible Label in Pattern Recognition (RLPR)</a>.</p><img style="float: right;" src="/doc/EPDT/RRLPR.png">Requirements
<p align="justify"> To correctly install and run YACCLAB following packages, libraries and utilities are needed:- CMake 3.18 or higher (https://cmake.org);
- OpenCV 3.0 or higher (http://opencv.org), required packages are
core
,imgcodecs
,imgproc
; - Gnuplot (http://www.gnuplot.info/);
- One of your favourite IDE/compiler with C++14 support.
GPU algorithms also require:
- CUDA Toolkit 9.2 or higher (https://developer.nvidia.com/cuda-toolkit) and OpenCV
cudafeatures2d
package (as of OpenCV 4.5.3, package dependencies entail that required packages for CUDA algorithms arecore
,cudafeatures2d
,cudaarithm
,cudafilters
,cudaimgproc
,cudawarping
,cudev
,features2d
,imgcodecs
,imgproc
).
Notes for gnuplot:
- on Windows system: be sure add gnuplot to system path if you want YACCLAB automatically generates charts.
- on MacOS system: 'pdf terminal' seems to be not available due to old version of cairo, 'postscript' is used instead.
Installation (refer to the image below)
- <p align="justify">Clone the GitHub repository (HTTPS clone URL: https://github.com/prittt/YACCLAB.git) or simply download the full master branch zip file and extract it (e.g YACCLAB folder).</p>
- <p align="justify">Install software in YACCLAB/bin subfolder (suggested) or wherever you want using CMake (point 2 of the example image). Note that CMake should automatically find the OpenCV path whether correctly installed on your OS (3), download the YACCLAB Dataset (be sure to check the box if you want to download it (4a) and (4b) or to select the correct path if the dataset is already on your file system (7)), and create a C++ project for the selected IDE/compiler (9-10). Moreover, if you want to test 3D or GPU algorithms tick the corresponding boxes (5) and (6). </p>
- <p align="justify">Set the <a href="#conf">configuration file (config.yaml)</a> placed in the installation folder (bin in this example) in order to select desired tests.</p>
- <p align="justify">Open the project, compile and run it: the work is done!</p>
CMake Configuration Variables
Name | Meaning | Default |
---|---|---|
YACCLAB_DOWNLOAD_DATASET | whether to automatically download the 2D YACCLAB dataset or not | OFF |
YACCLAB_DOWNLOAD_DATASET_3D | whether to automatically download the 3D YACCLAB dataset or not | OFF |
YACCLAB_ENABLE_3D | enable/disable the support for 3D algorithms | OFF |
YACCLAB_ENABLE_CUDA | enable/disable CUDA support | OFF |
YACCLAB_ENABLE_EPDT_19C | enable/disable the EPDT_19C 3D algorithm which is based on a heuristic decision tree generated from a 3D mask with 19 conditions (may noticeably increase compilation time), it has no effect when YACCLAB_ENABLE_3D is OFF | OFF |
YACCLAB_ENABLE_EPDT_22C | enable/disable the EPDT_22C 3D algorithm which is based on a heuristic decision tree generated from a 3D mask with 22 conditions (may noticeably increase compilation time), it has no effect when YACCLAB_ENABLE_3D is OFF | OFF |
YACCLAB_ENABLE_EPDT_26C | enable/disable the EPDT_26C 3D algorithm which is based on a heuristic decision tree generated from a 3D mask with 26 conditions (may noticeably increase compilation time), it has no effect when YACCLAB_ENABLE_3D is OFF | OFF |
YACCLAB_FORCE_CONFIG_GENERATION | whether to force the generation of the default configuration file (config.yaml ) or not. When this flag is turned OFF any existing configuration file will not be overwritten | OFF |
YACCLAB_INPUT_DATASET_PATH | path to the input dataset folder, where to find test datasets | ${CMAKE_INSTALL_PREFIX}/input |
YACCLAB_OUTPUT_RESULTS_PATH | path to the output folder, where to save output results | ${CMAKE_INSTALL_PREFIX}/output |
OpenCV_DIR | OpenCV installation path | - |
How to include a YACCLAB algorithm into your own project?
<p align="justify">If your project requires a Connected Components Labeling algorithm and you are not interested in the whole YACCLAB benchmark you can use the <i>connectedComponent</i> function of the OpenCV library which implements the BBDT and SAUF algorithms since version 3.2., Spaghetti Labeling algorithm and BKE (for GPU only) since version 4.6.</p> <p align="justify">Anyway, when the <i>connectedComponents</i> function is called, a lot of additional code will be executed together with the core function. If your project requires the best performance you can include an algorithm implemented in YACCLAB adding the following files to your project:</p> <ol> <li><i>labeling_algorithms.h</i> and <i>labeling_algorithms.cc</i> which define the base class from which every algorithm derives from;</li> <li><i>yacclab_tensor.h</i>, <i>yacclab_tensor.cc</i> which define input and output data tensors;</li> <li><i>label_solver.h</i> and <i>label_solver.cc</i> which cointain the implementation of labels solving algorithms;</li> <li><i>memory_tester.h</i>, <i>performance_evaluator.h</i>, <i>volume_util.h</i>, <i>volume_util.cc</i>, <i>utilities.h</i>, <i>utilities.cc</i>, <i>system_info.h</i>, <i>system_info.cc</i>, <i>check_labeling.h</i>, <i>check_labeling.cc</i>, <i>file_manager.h</i>, <i>file_manager.cc</i>, <i>stream_demultiplexer.h</i>, <i>config_data.h</i>, <i>register.h</i>, <i>yacclab_test.h</i>, <i>progress_bar.h</i>, <i>cuda_mat3.hpp</i>, <i>cuda_types3.hpp</i>, and <i>cuda_mat3.inl.hpp</i> just to make things work without changing the code;</li> <li><i>headers</i> and <i>sources</i> files of the required algorithm/s. The association between algorithms and headers/sources files is reported in the tables below.</li> </ol>2D/3D CPU Algorithms
<table> <tr> <th>Algorithm Name</th> <th width="130">Authors</th> <th>Year</th> <th>Acronym</th> <th>Required Files</th> <th>Templated on Labels Solver</th> </tr> <tr> <td align="center">-</td> <td align="center">L. Di Stefano,<br>A. Bulgarelli <a href="#DiStefano">[3]</a></td> <td align="center">1999</td> <td align="center">DiStefano</td> <td align="center"><i>labeling_distefano_1999.h</i></td> <td align="center">❌</td> </tr> <tr> <td align="center">Contour Tracing</td> <td align="center">F. Chang,</br>C.J. Chen,</br>C.J. Lu <a href="#CT">[1]</a></td> <td align="center">1999</td> <td align="center">CT</td> <td align="center"><i>labeling_fchang_2003.h</i></td> <td align="center">❌</td> </tr> <tr> <td align="center">Run-Based Two-Scan</td> <td align="center">L. He,</br>Y. Chao,</br>K. Suzuki <a href="#RBTS">[30]</a></td> <td align="center">2008</td> <td align="center">RBTS</td> <td align="center"><i>labeling_he_2008.h</i></td> <td align="center"> ✔</td> </tr> <tr> <td align="center">Scan Array-based with Union Find</td> <td align="center">K. Wu,</br>E. Otoo,</br>K. Suzuki <a href="#SAUF">[6]</a></td> <td align="center">2009</td> <td align="center">SAUF</td> <td align="center"><i>labeling_wu_2009.h</i>, <i>labeling_wu_2009_tree.inc</i></td> <td align="center"> ✔</td> </tr> <tr> <td align="center">Stripe-Based Labeling Algorithm</td> <td align="center">H.L. Zhao,</br>Y.B. Fan,</br>T.X. Zhang,</br>H.S. Sang <a href="#SBLA">[8]</a></td> <td align="center">2010</td> <td align="center">SBLA</td> <td align="center"><i>labeling_zhao_2010.h</i></td> <td align="center">❌</td> </tr> <tr> <td align="center">Block-Based with Decision Tree</td> <td align="center">C. Grana,</br>D. Borghesani,</br>R. Cucchiara <a href="#BBDT">[4]</a></td> <td align="center">2010</td> <td align="center">BBDT</td> <td align="center"><i>labeling_grana_2010.h</i>, <i>labeling_grana_2010_tree.inc</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Configuration Transition Based</td> <td align="center">L. He,</br>X. Zhao,</br>Y. Chao,</br>K. Suzuki <a href="#CTB">[7]</a></td> <td align="center">2014</td> <td align="center">CTB</td> <td align="center"><i>labeling_he_2014.h</i>, <i>labeling_he_2014_graph.inc</i> <td align="center">✔</td> </tr> <tr> <td align="center">Block-Based with Binary Decision Trees</td> <td align="center">W.Y. Chang,</br>C.C. Chiu,</br>J.H. Yang <a href="#CCIT">[2]</a></td> <td align="center">2015</td> <td align="center">CCIT</td> <td align="center"><i>labeling_wychang_2015.h</i>, <i>labeling_wychang_2015_tree.inc</i>, <i>labeling_wychang_2015_tree_0.inc</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Light Speed Labeling</td> <td align="center">L. Cabaret,</br>L. Lacassagne,</br>D. Etiemble <a href="#LSL_STD">[5]</a></td> <td align="center">2016</td> <td align="center">LSL_STD<a href="#I"><sup>I</sup></a></br>LSL_STDZ<a href="#II"><sup>II</sup></a></br>LSL_RLE<a href="#III"><sup>III</sup></a></td> <td align="center"><i>labeling_lacassagne_2016.h</i>, <i>labeling_lacassagne_2016_code.inc</i></td> <td align="center">✔<a href="#IV"><sup>IV</sup></a></td> </tr> <tr> <td align="center">Pixel Prediction</td> <td align="center">C.Grana,</br>L. Baraldi,</br>F. Bolelli <a href="#PRED">[9]</a></td> <td align="center">2016</td> <td align="center">PRED</td> <td align="center"><i>labeling_grana_2016.h</i>, <i>labeling_grana_2016_forest.inc</i>, <i>labeling_grana_2016_forest_0.inc</i> <td align="center">✔</td> </tr> <tr> <td align="center">Directed Rooted Acyclic Graph</td> <td align="center">F. Bolelli,</br>L. Baraldi,</br>M. Cancilla,</br>C. Grana <a href="#DRAG">[23]</a></td> <td align="center">2018</td> <td align="center">DRAG</td> <td align="center"><i>labeling_bolelli_2018.h</i>, <i>labeling_grana_2018_drag.inc</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Spaghetti Labeling</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>L. Baraldi,</br>C. Grana <a href="#SPAGHETTI">[26]</a></td> <td align="center">2019</td> <td align="center">Spaghetti</td> <td align="center"><i>labeling_bolelli_2019.h</i>, <i>labeling_bolelli_2019_forest.inc</i>, <i>labeling_bolelli_2019_forest_firstline.inc</i>, <i>labeling_bolelli_2019_forest_lastline.inc</i>, <i>labeling_bolelli_2019_forest_singleline.inc</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">PRED++</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#DAG">[33]</a></td> <td align="center">2021</td> <td align="center">PREDpp</td> <td align="center"><i>labeling_PREDpp_2021.h</i>, <i>labeling_PREDpp_2021_center_line_forest_code.inc.h</i>, <i>labeling_PREDpp_2021_first_line_forest_code.inc.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Tagliatelle Labeling</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#DAG">[33]</a></td> <td align="center">2021</td> <td align="center">Tagliatelle</td> <td align="center"><i>labeling_tagliatelle_2021.h</i>, <i>labeling_tagliatelle_2021_center_line_forest_code.inc.h</i>, <i>labeling_tagliatelle_2021_first_line_forest_code.inc.h</i>, <i>labeling_tagliatelle_2021_last_line_forest_code.inc.h</i>, <i>labeling_tagliatelle_2021_single_line_forest_code.inc.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Bit-Run Two Scan</td> <td align="center">W. Lee,</br>F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#BRTS">[32]</a></td> <td align="center">2021</td> <td align="center">BRTS<a href="#VII"><sup>VII</sup></a></td> <td align="center"><i>labeling_lee_2021_brts.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Bit-Merge-Run Scan</td> <td align="center">W. Lee,</br>F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#BRTS">[32]</a></td> <td align="center">2021</td> <td align="center">BMRS<a href="#VII"><sup>VII</sup></a></td> <td align="center"><i>labeling_lee_2021_bmrs.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Null Labeling</td> <td align="center">F. Bolelli,</br>M. Cancilla,</br>L. Baraldi,</br>C. Grana <a href="#YACCLAB_JRTIP">[13]</a></td> <td align="center">-</td> <td align="center">NULL<a href="#V"><sup>V</sup></a></td> <td align="center"><i>labeling_null.h</i></td> <td align="center">❌</td> </tr> <tr style="border-top:5px solid black; !important"> <td align="center">SAUF 3D</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#DAG">[33]</a></td> <td align="center">2021</td> <td align="center">SAUF_3D</td> <td align="center"><i>labeling3D_SAUF_2021.h</i>, <i>labeling3D_SAUF_2021_tree_code.inc.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">SAUF++ 3D</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#DAG">[33]</a></td> <td align="center">2021</td> <td align="center">SAUFpp_3D</td> <td align="center"><i>labeling3D_SAUFpp_2021.h</i>, <i>labeling3D_SAUFpp_2021_tree_code.inc.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">PRED 3D</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#DAG">[33]</a></td> <td align="center">2021</td> <td align="center">PRED_3D</td> <td align="center"><i>labeling3D_PRED_2021.h</i>, <i>labeling3D_PRED_2021_center_line_forest_code.inc.h</i>, <i>labeling3D_PRED_2021_first_line_forest_code.inc.h</i>, <i>labeling3D_PRED_2021_last_line_forest_code.inc.h</i>, <i>labeling3D_PRED_2021_single_line_forest_code.inc.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">PRED++ 3D</td> <td align="center">F. Bolelli,</br>S. Allegretti,</br>C. Grana <a href="#DAG">[33]</a></td> <td align="center">2021</td> <td align="center">PREDpp_3D</td> <td align="center"><i>labeling3D_PREDpp_2021.h</i>, <i>labeling3D_PREDpp_2021_center_line_forest_code.inc.h</i>, <i>labeling3D_PREDpp_2021_first_line_forest_code.inc.h</i>, <i>labeling3D_PREDpp_2021_last_line_forest_code.inc.h</i>, <i>labeling3D_PREDpp_2021_single_line_forest_code.inc.h</i></td> <td align="center">✔</td> </tr> <tr> <td align="center">Entropy Partitioning Decision Tree <a href="https://github.com/prittt/YACCLAB/tree/master/doc/EPDT">RLPR</a></td> <td align="center">M. Söchting,</br>S. Allegretti,</br>F. Bolelli,</br>C. Grana <a href="#EPDT">[31]</a></td> <td align="center">2021</td> <td align="center">EPDT_19c and EPDT_22c<a href="#VI"><sup>VI</sup></a></td> <td align="center"><i>labeling3D_BBDT_2019.h</i>, <i>labeling_bolelli_2019_forest.inc</i>, <i>labeling_bolelli_2019_forest_firstline.inc</i>, <i>labeling_bolelli_2019_forest_lastline.inc</i>, <i>labeling_bolelli_2019_forest_singleline.inc</i></td> <td align="center">✔</td> </tr> </table><a name="I"><sup>I</sup></a> standard version. </br>
<a name="II"><sup>II</sup></a> with zero-offset optimization. </br>
<a name="III"><sup>III</sup></a> with RLE compression. </br>
<a name="IV"><sup>IV</sup></a> only on TTA and UF. </br>
<a name="V"><sup>V</sup></a> it only copies the pixels from the input image to the output one simply defining a lower bound limit for the execution time of CCL algorithms on a given machine and dataset.</br>
<a name="VI"><sup>VI</sup></a> EPDT_19c and EPDT_22c algorithms are based on very big decision trees that translate to many lines of C++ code. They may thus noticeably increase the build time. For this reason, a special flag (YACCLAB_ENABLE_EPDT_ALGOS
) to enable/disable such algorithms is provided in the CMake file. By default the flag is OFF.</br>
<a name="VII"><sup>VII</sup></a> CCL algorithm for images in bitonal (1 bit per pixel) format. When applied to these algorithms, the <i>average</i> tests also consider the time for 1 byte to 1 bit per pixel conversion. On the other hand, when performing <i>average with steps</i> tests conversion time is ignored.
2D/3D GPU Algorithms
<table> <tr> <th>Algorithm Name</th> <th width="130">Authors</th> <th>Year</th> <th>Acronym</th> <th>Required Files</th> <th>2D/3D</th> </tr> <tr> <td align="center">Union Find</td> <td align="center">V. Oliveira,</br>R. Lotufo <a href="#UF">[18]</a></td> <td align="center">2010</td> <td align="center">UF</td> <td align="center"><i>labeling_oliveira_2010.cu</i></td> <td align="center">2D and 3D</td> </tr> <tr> <td align="center">Optimized</br>Label Equivalence</td> <td align="center">O. Kalentev,</br>A. Rai,</br>S. Kemnitz,</br>R. Schneider <a href="#OLE">[19]</a></td> <td align="center">2011</td> <td align="center">OLE</td> <td align="center"><i>labeling_kalentev_2011.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Block-run-based</td> <td align="center">P. Chen,</br>H.L. Zhao,</br>C. Tao,</br>H.S. Sang <a href="#BRB">[25]</a></td> <td align="center">2011</td> <td align="center">BRB</td> <td align="center"><i>labeling_chen_2011.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Stava</td> <td align="center">O. Stava,</br>B. Benes <a href="#STAVA">[38]</a></td> <td align="center">2011</td> <td align="center">STAVA</td> <td align="center"><i>labeling_stava_2011.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Rasmusson</td> <td align="center">A. Rasmusson,</br>T.S. Sørensen,</br>G. Ziegler <a href="#RASMUSSON">[37]</a></td> <td align="center">2013</td> <td align="center">RASMUSSON</td> <td align="center"><i>labeling_rasmusson_2013.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Accelerated CCL</td> <td align="center">F. N. Paravecino,</br>D. Kaeli <a href="#ACCL">[34]</a></td> <td align="center">2014</td> <td align="center">ACCL</td> <td align="center"><i>labeling_paravecino_2014.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">8-Directional Label Selection</td> <td align="center">Y. Soh,</br>H. Ashraf,</br>Y. Hae,</br>I. Kim <a href="#8DLS">[36]</a></td> <td align="center">2014</td> <td align="center">DLS</td> <td align="center"><i>labeling_soh_2014_8DLS.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Modified 8-Directional Label Selection</td> <td align="center">Y. Soh,</br>H. Ashraf,</br>Y. Hae,</br>I. Kim <a href="#8DLS">[36]</a></td> <td align="center">2014</td> <td align="center">M8DLS</td> <td align="center"><i>labeling_soh_2014_M8DLS.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Line-based Union-Find</td> <td align="center">K. Yonehara,</br>K. Aizawa <a href="#LBUF">[39]</a></td> <td align="center">2015</td> <td align="center">LBUF</td> <td align="center"><i>labeling_yonehara_2015.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Block Equivalence</td> <td align="center">S. Zavalishin,</br>I. Safonov,</br>Y. Bekhtin,</br>I. Kurilin <a href="#BE">[20]</a></td> <td align="center">2016</td> <td align="center">BE</td> <td align="center"><i>labeling_zavalishin_2016.cu</i></td> <td align="center">2D and 3D</td> </tr> <tr> <td align="center">Distanceless</br>Label Propagation</td> <td align="center">L. Cabaret,</br>L. Lacassagne,</br>D. Etiemble <a href="#DLP">[21]</a></td> <td align="center">2017</td> <td align="center">DLP</td> <td align="center"><i>labeling_cabaret_2017.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Komura Equivalence (8-conn)</td> <td align="center">S. Allegretti,</br>F. Bolelli,</br>M. Cancilla,</br>C. Grana <a href="#KE">[22]</a></td> <td align="center">2018</td> <td align="center">KE</td> <td align="center"><i>labeling_allegretti_2018.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Hardware Accelerated</br>4-connected</td> <td align="center">A. Hennequin,</br>L. Lacassagne,</br>L. Cabaret,</br>Q. Meunier <a href="#HA4">[35]</a></td> <td align="center">2018</td> <td align="center">HA4</td> <td align="center"><i>labeling_hennequin_2018_HA4.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Hardware Accelerated</br>8-connected</td> <td align="center">A. Hennequin,</br>L. Lacassagne,</br>L. Cabaret,</br>Q. Meunier <a href="#HA4">[35]</a></td> <td align="center">2018</td> <td align="center">HA8</td> <td align="center"><i>labeling_hennequin_2018_HA8.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">CUDA SAUF</td> <td align="center">S. Allegretti,</br>F. Bolelli,</br>M. Cancilla,</br>C. Grana <a href="#CAIP">[29]</a></td> <td align="center">2019</td> <td align="center">C-SAUF</td> <td align="center"><i>labeling_allegretti_2019_SAUF.cu</i>,</br><i>labeling_wu_2009_tree.inc</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">CUDA BBDT</td> <td align="center">S. Allegretti,</br>F. Bolelli,</br>M. Cancilla,</br>C. Grana <a href="#CAIP">[29]</a></td> <td align="center">2019</td> <td align="center">C-BBDT</td> <td align="center"><i>labeling_allegretti_2019_BBDT.cu</i>, <i>labeling_grana_2010_tree.inc</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">CUDA DRAG</td> <td align="center">S. Allegretti,</br>F. Bolelli,</br>M. Cancilla,</br>C. Grana <a href="#CAIP">[29]</a></td> <td align="center">2019</td> <td align="center">C-DRAG</td> <td align="center"><i>labeling_allegretti_2019_DRAG.cu</i></td> <td align="center">2D</td> </tr> <tr> <td align="center">Block-based Union Find</td> <td align="center">S. Allegretti,</br>F. Bolelli,</br>C. Grana <a href="#BUF_BKE">[24]</a></td> <td align="center">2019</td> <td align="center">BUF</td> <td align="center"><i>labeling_allegretti_2019_BUF.cu</i></td> <td align="center">2D and 3D</td> </tr> <tr> <td align="center">Block-based Komura Equivalence</td> <td align="center">S. Allegretti,</br>F. Bolelli,</br>C. Grana <a href="#BUF_BKE">[24]</a></td> <td align="center">2019</td> <td align="center">BKE</td> <td align="center"><i>labeling_allegretti_2019_BKE.cu</i></td> <td align="center">2D and 3D</td> </tr> </table>Example of Algorithm Usage Outside the Benchmark
#include "labels_solver.h"
#include "labeling_algorithms.h"
#include "labeling_grana_2010.h" // To include the algorithm code (BBDT in this example)
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
BBDT<UFPC> BBDT_UFPC; // To create an object of the desired algorithm (BBDT in this example)
// templated on the labels solving strategy. See the README for the
// complete list of the available labels solvers, available algorithms
// (N.B. non all the algorithms are templated on the solver) and their
// acronyms.
BBDT_UFPC.img_ = imread("test_image.png", IMREAD_GRAYSCALE); // To load into the CCL object
// the BINARY image to be labeled
threshold(BBDT_UFPC.img_, BBDT_UFPC.img_, 100, 1, THRESH_BINARY); // Just to be sure that the
// loaded image is binary
BBDT_UFPC.PerformLabeling(); // To perform Connected Components Labeling!
Mat1i output = BBDT_UFPC.img_labels_; // To get the output labeled image
unsigned n_labels = BBDT_UFPC.n_labels_; // To get the number of labels found in the input img
return EXIT_SUCCESS;
}
<a name="conf"></a>
Configuration File
<p align="justify">A <tt>YAML</tt> configuration file placed in the installation folder lets you specify which kinds of tests should be performed, on which datasets and on which algorithms. Four categories of algorithms are supported: 2D CPU, 2D GPU, 3D CPU and 3D GPU. For each of them, the configuration parameters are reported below. </p>- <i>execute</i> - boolean value which specifies whether the current category of algorithms will be tested:
execute: true
- <i>perform</i> - dictionary which specifies the <a href="#conf">kind of tests</a> to perform:
perform:
correctness: false
average: true
average_with_steps: false
density: false
granularity: false
memory: false
blocksize: false
- <i>correctness_tests</i> - dictionary indicating the kind of correctness tests to perform:
correctness_tests:
eight_connectivity_standard: true
eight_connectivity_steps: true
eight_connectivity_memory: true
eight_connectivity_blocksize: true
- <i>tests_number</i> - dictionary which sets the number of runs for each test available:
tests_number:
average: 10
average_with_steps: 10
density: 10
granularity: 10
- <i>algorithms</i> - list of algorithms on which apply the chosen tests:
algorithms:
- SAUF_RemSP
- SAUF_TTA
- BBDT_RemSP
- BBDT_UFPC
- CT
- labeling_NULL
- <i>check_datasets</i>, <i>average_datasets</i>, <i>average_ws_datasets</i>, <i>memory_datasets</i> and <i>blocksize_datasets</i>- lists of <a href="#conf">datasets</a> on which, respectively, correctness, average, average_ws, memory and blocksize tests should be run:
...
average_datasets: ["3dpes", "fingerprints", "hamlet", "medical", "mirflickr", "tobacco800", "xdocs"]
...
- <i>blocksize</i> - only for the 2D GPU and 3D GPU categories, this dictionary configures <a href=#blocksize_test>blocksize test</a> parameters. For each axis, a list of three values specifies [<first>, <last>, <step>]:
blocksize:
x: [2, 64, 2]
y: [2, 64, 2]
z: [2, 64, 2]
<p style=text-align: justify;>Finally, the following configuration parameters are common to all categories.</p>
- <i>paths</i> - dictionary with both input (datasets) and output (results) paths. It is automatically filled by Cmake during the creation of the project:
paths: {input: "<datasets_path>", output: "<output_results_path>"}
- <i>write_n_labels</i> - whether to report the number of connected components in the output files:
write_n_labels: false
- <i>color_labels</i> - whether to output a colored version of labeled images during tests:
color_labels: {average: false, density: false}
- <i>save_middle_tests</i> - dictionary specifying, separately for every test, whether to save the output of single runs, or only a summary of the whole test:
save_middle_tests: {average: false, average_with_steps: false, density: false, granularity: false}
How to Extend YACCLAB with New Algorithms
<p align="justify">YACCLAB has been designed with extensibility in mind, so that new resources can be easily integrated into the project. A CCL algorithm is coded with a <tt>.h</tt> header file (placed in the <tt>include</tt> folder), a <tt>.cc</tt> source file (placed in the <tt>src</tt> folder), and optional additional files containing a tree/drag definition (placed in the <tt>include</tt> folder).</p>The source file should be as follows:
#include "<header_file_name>.h"
REGISTER_LABELING_WITH_EQUIVALENCES_SOLVERS(<algorithm_name>);
// Replace the above line with "REGISTER_LABELING(<algorithm_name>);" if the algorithm
// is not template on the equivalence solver algorithm.
The header file should follows the structure below (see <tt>include/labeling_bolelli_2018.h</tt> to have a complete example):
// [...]
template <typename LabelsSolver> // Remove this line if the algorithm is not template
// on the equivalence solver algorithm
class <algorithm_name> : public Labeling2D<Connectivity2D::CONN_8> { // the class must extend one of the labeling
// classes Labeling2D, Labeling3D, .. that
// are template on the connectivity type
public:
<algorithm_name>() {}
// This member function should implement the labeling procedure reading data from the
// input image "img_" (OpenCV Mat1b) and storing labels into the output one "img_labels_"
// (OpenCV Mat1i)
void PerformLabeling()
{
// [...]
LabelsSolver::Alloc(UPPER_BOUND_8_CONNECTIVITY); // Memory allocation of the labels solver
LabelsSolver::Setup(); // Labels solver initialization
// [...]
LabelsSolver::GetLabel(<label_id>) // To get label value from its index
LabelsSolver::NewLabel(); // To create a new label
LabelsSolver::Flatten(); // To flatten the equivalence solver array
}
// This member function should implement the with step version of the labeling procedure.
// This is required to perform tests with steps.
void PerformLabelingWithSteps()
{
double alloc_timing = Alloc(); // Alloc() should be a member function responsible
// for memory allocation of the required data structures
perf_.start();
FirstScan(); // FirsScan should be a member function that implements the
// first scan step of the algorithm (if it has one)
perf_.stop();
perf_.store(Step(StepType::FIRST_SCAN), perf_.last());
perf_.start();
SecondScan(); // SecondScan should be a member function that implements the
// second scan step of the algorithm (if it has one)
perf_.stop();
perf_.store(Step(StepType::SECOND_SCAN), perf_.last());
// If the algorithm does not have a distinct firs and second scan replace the lines
// above with the following ones:
// perf_.start();
// AllScans(); // AllScans() should be a member function which implements the entire
// algorithm but the allocation/deallocation
// perf_.stop();
// perf_.store(Step(StepType::ALL_SCANS), perf_.last());
perf_.start();
Dealloc(); // Dealloc() should be a member function responsible for memory
// deallocation.
perf_.stop();
perf_.store(Step(StepType::ALLOC_DEALLOC), perf_.last() + alloc_timing);
// [...]
}
// This member function should implement the labeling procedure using the OpenCV Mat
// wrapper (MemMat) implemented by YACCLAB
void PerformLabelingMem(std::vector<uint64_t>& accesses){
// [...]
}
}
When implementing a GPU algorithm only the <tt>.cu</tt> file is required. The file should be placed in the <tt>cuda/src</tt> folder. The general structure of a GPU algorithm is the following:
// [...]
// Kernel definitions:
__global__ void <kernel_name_1>(...)
{
...
}
__global__ void <kernel_name_2>(...)
{
...
}
class <algorithm_name> : public GpuLabeling2D<Connectivity2D::CONN_8> { // the class must extend one of the labeling
// classes GpuLabeling2D, GpuLabeling3D, .. that
// are template on the connectivity type
public:
<algorithm_name>() {}
// This member function should implement the labeling procedure reading data from the
// input image "d_img_" (OpenCV cuda::GpuMat) and storing labels into the output one "d_img_labels_"
// (OpenCV cuda::GpuMat)
void PerformLabeling()
{
// Create the output image
d_img_labels_.create(d_img_.size(), CV_32SC1);
// [...]
// Call necessary kernels
<kernel_name_1> <<<...>>> (...);
<kernel_name_2> <<<...>>> (...);
// [...]
// Wait for the end of the last kernel
cudaDeviceSynchronize();
}
// This member function should implement the with step version of the labeling procedure.
// This is required to perform tests with steps.
void PerformLabelingWithSteps()
{
double alloc_timing = Alloc(); // Alloc() should be a member function responsible
// for memory allocation of the required data structures
perf_.start();
FirstScan(); // FirsScan should be a member function that implements the
// first scan step of the algorithm (if it has one)
perf_.stop();
perf_.store(Step(StepType::FIRST_SCAN), perf_.last());
perf_.start();
SecondScan(); // SecondScan should be a member function that implements the
// second scan step of the algorithm (if it has one)
perf_.stop();
perf_.store(Step(StepType::SECOND_SCAN), perf_.last());
// If the algorithm does not have a distinct first and second scan replace the lines
// above with the following ones:
// perf_.start();
// AllScans(); // AllScans() should be a member function which implements the entire
// algorithm but the allocation/deallocation
// perf_.stop();
// perf_.store(Step(StepType::ALL_SCANS), perf_.last());
perf_.start();
Dealloc(); // Dealloc() should be a member function responsible for memory
// deallocation.
perf_.stop();
perf_.store(Step(StepType::ALLOC_DEALLOC), perf_.last() + alloc_timing);
// [...]
}
void PerformLabelingBlocksize(int x, int y, int z)
{
// Create the output image
d_img_labels_.create(d_img_.size(), CV_32SC1);
// [...]
// Call necessary kernels through a macro that measures times separately
BLOCKSIZE_KERNEL(<kernel_name_1>, <grid_size>, <block_size>, <dynamic_shared_mem>, <arguments>...);
BLOCKSIZE_KERNEL(<kernel_name_2>, <grid_size>, <block_size>, <dynamic_shared_mem>, <arguments>...);
// [...]
}
}
REGISTER_LABELING(<algorithm_name>);
// Only necessary for blocksize test
REGISTER_KERNELS(<algorithm_name>, <kernel_name_1>, <kernel_name_2>, ...);
<p align="justify">Once an algorithm has been added to YACCLAB, it is ready to be tested and compared to the others. Don't forget to update the configuration file! We look at YACCLAB as a growing effort towards better reproducibility of CCL algorithms, so implementations of new and existing labeling methods are very welcome.</p>
<a name="datasets"></a>
The YACCLAB Dataset
<p align="justify">The YACCLAB dataset includes both synthetic and real images and it is suitable for a wide range of applications, ranging from document processing to surveillance, and features a significant variability in terms of resolution, image density, variance of density, and number of components. All images are provided in 1 bit per pixel PNG format, with 0 (black) being background and 1 (white) being foreground. The dataset will be automatically downloaded by CMake during the installation process as described in the <a href="#inst">installation</a> paragraph.</p>2D Datasets
-
<b>MIRflickr <a href="#MIRFLICKR">[10]</a>:</b><p align="justify"> Otsu-binarized version of the MIRflickr dataset, publicly available under a Creative Commons License. It contains 25,000 standard resolution images taken from Flickr. These images have an average resolution of 0.17 megapixels, there are few connected components (495 on average) and are generally composed of not too complex patterns, so the labeling is quite easy and fast.</p>
-
<b>Hamlet:</b><p align="justify"> A set of 104 images scanned from a version of the Hamlet found on Project Gutenberg (http://www.gutenberg.org). Images have an average amount of 2.71 million of pixels to analyze and 1447 components to label, with an average foreground density of 0.0789. </p>
-
<b>Tobacco800 <a href="#TOBACCO1">[11]</a>,<a href="#TOBACCO2">[12]</a>:</b><p align="justify"> A set of 1290 document images. It is a realistic database for document image analysis research as these documents were collected and scanned using a wide variety of equipment over time. Resolutions of documents in Tobacco800 vary significantly from 150 to 300 DPI and the dimensions of images range from 1200 by 1600 to 2500 by 3200 pixels. Since CCL is one of the initial preprocessing steps in most layout analysis or OCR algorithms, hamlet and tobacco800 allow to test the algorithm performance in such scenarios. </p>
-
<b>3DPeS <a href="#3DPES">[14]</a>:</b> <p align="justify"> It comes from 3DPeS (3D People Surveillance Dataset), a surveillance dataset designed mainly for people re-identification in multi camera systems with non-overlapped fields of view. 3DPeS can be also exploited to test many other tasks, such as people detection, tracking, action analysis and trajectory analysis. The background models for all cameras are provided, so a very basic technique of motion segmentation has been applied to generate the foreground binary masks, i.e., background subtraction and fixed thresholding. The analysis of the foreground masks to remove small connected components and for nearest neighbor matching is a common application for CCL. </p>
-
<b>Medical <a href="#MEDICAL">[15]</a>:</b><p align="justify"> This dataset is composed by histological images and allow us to cover this fundamental medical field. The process used for nuclei segmentation and binarization is described in <a href="#MEDICAL">[15]</a>. The resulting dataset is a collection of 343 binary histological images with an average amount of 1.21 million of pixels to analyze and 484 components to label. </p>
-
<b>Fingerprints <a href="#FINGERPRINTS">[16]</a>:</b><p align="justify"> This dataset counts 960 fingerprint images collected by using low-cost optical sensors or synthetically generated. These images were taken from the three Verification Competitions FCV2000, FCV2002 and FCV2004. In order to fit CCL application, fingerprints have been binarized using an adaptive threshold and then negated in order to have foreground pixel with value 255. Most of the original images have a resolution of 500 DPI and their dimensions range from 240 by 320 up to 640 by 480 pixels. </p>
- <b>Synthetic Images</b>:
- <b>Classical <a href="#BBDT">[4]</a></b>:<p align="justify"> A set of synthetic random noise images who contain black and white random noise with 9 different foreground densities (10% up to 90%), from a low resolution of 32x32 pixels to a maximum resolution of 4096x4096 pixels, allowing to test the scalability and the effectiveness of different approaches when the number of labels gets high. For every combination of size and density, 10 images are provided for a total of 720 images. The resulting subset allows to evaluate performance both in terms of scalability on the number of pixels and on the number of labels (density). </p>
- <b>Granularity <a href="#LSL">[5]</a> </b>:<p align="justify"> This dataset allows to test algorithms varying not only the pixels density but also their granularity <i>g</i> (<i>i.e.</i>, dimension of minimum foreground block), underlying the behaviour of different proposals when the number of provisional labels changes. All the images have a resolution of 2048x2048 and are generated with the Mersenne Twister MT19937 random number generator implemented in the <i>C++</i> standard and starting with a "seed" equal to zero. Density of the images ranges from 0% to 100% with step of 1% and for every density value 16 images with pixels blocks of <i>gxg</i> with <i>g</i> ∈ [1,16] are generated. Moreover, the procedure has been repeated 10 times for every couple of density-granularity for a total of 16160 images.</p>
3D Datasets
-
<b>OASIS <a href="#OASIS">[27]</a></b>: <p align="justify"> This is a dataset of medical MRI data taken from the Open Access Series of Imaging Studies (OASIS) project. It consists of 373 volumes of 256 × 256 × 128 pixels, binarized with the Otsu threshold.</p>
-
<b>Mitochondria <a href="#MIT1">[28]</a></b>: <p align="justify">It is the Electron Microscopy Dataset, which contains binary sections taken from the CA1 hippocampus for a total of three volumes composed by 165 slices with a resolution of 1024 × 768 pixels.</p>
-
<b>Hilbert <a href="#TPDS">[24]</a></b>: <p align="justify">This dataset contains six volumes of 128 × 128 × 128 pixels, filled with the 3D Hilbert curve obtained at different iterations (1 to 6) of the construction method. The Hilbert curve is a fractal space-filling curve that representsa challenging test case for the labeling algorithms.</p>
- <b>Granularity <a href="#TPDS">[24]</a></b>: <p align="justify">It contains 3D synthetic images generated as described for the 2D version. In this case, images have a resolution of 256 x 256 x 256 pixels and only three different images for every couple of density-granularity have been generated.</p>
<a name="tests"></a>
Available Tests
-
<b>Average run-time tests:</b> <p align="justify"> execute an algorithm on every image of a dataset. The process can be repeated more times in a single test, to get the minimum execution time for each image: this allows to get more reproducible results and overlook delays produced by other running processes. It is also possible to compare the execution speed of different algorithms on the same dataset: in this case, selected algorithms (see <a href="#conf">Configuration File</a> for more details) are executed sequentially on every image of the dataset. Results are presented in three different formats: a plain text file, histogram charts (.pdf/.ps), either in color or in gray-scale, and a LaTeX table, which can be directly included in research papers.</p>
-
<b>Average run-time tests with steps:</b> <p align="justify"> evaluates the performance of an algorithm separating the allocation/deallocation time from the time required to compute labeling. Moreover, if an algorithm employs multiple scans to produce the correct output labels, YACCLAB will store the time of every scan and will display them separately. To understand how YACCLAB computes the memory allocation time for an algorithm on a reference image, it is important to underline the subtleties involved in the allocation process. Indeed, all modern operating systems (not real-time, nor embedded ones, but certainly Windows and Unix) handle virtual memory exploiting a demand paging technique, <i>i.e</i> demand paging with no pre-paging for most of Unix OS and cluster demand paging for Windows OS. This means that a disk page is copied into physical memory only when it is accessed by a process the first time, and not when the allocation function is called. Therefore, it is not possible to calculate the exact allocation time required by an algorithm, which computes CCL on a reference image, but its upper bound can be estimated using the following approach:</p>
- forcing the allocation of the entire memory by reserving it (<tt>malloc</tt>), filling it with zeros (<tt>memset</tt>), and tracing the time;
- calculating the time required by the assignment operation (<tt>memset</tt>), and subtracting it from the one obtained at the previous step;
- repeating the previous points for all data structures needed by an algorithm and summing times together.
-
<b>Density and size tests:</b> <p align="justify"> check the performance of different CCL algorithms when they are executed on images with varying foreground density and size. To this aim, a list of algorithms selected by the user is run sequentially on every image of the test_random dataset. As for run-time tests, it is possible to repeat this test for more than one run. The output is presented as both plain text and charts(.pdf/.ps). For a density test, the mean execution time of each algorithm is reported for densities ranging from 10% up to 90%, while for a size test the same is reported for resolutions ranging from 32 x 32 up to 4096 x 4096.</p>
-
<b>Memory tests:</b> <p align="justify"> are useful to understand the reason for the good performances of an algorithm or in general to explain its behavior. Memory tests compute the average number of accesses to the label image (i.e the image used to store the provisional and then the final labels for the connected components), the average number of accesses to the binary image to be labeled, and, finally, the average number of accesses to data structures used to solve the equivalences between label classes. Moreover, if an algorithm requires extra data, memory tests summarize them as ``other'' accesses and return the average. Furthermore, all average contributions of an algorithm and dataset are summed together in order to show the total amount of memory accesses. Since counting the number of memory accesses imposes additional computations, functions implementing memory access tests are different from those implementing run-time and density tests, to keep run-time tests as objective as possible.</p>
-
<b>Granularity tests:</b> <p align="justify"> evaluates an algorithm varying density (from 1% to 100%, using a 1% step) and pixels granularity, but not images resolution. The output results display the average execution time over images with the same density and granularity.</p>
<a name="blocksize_test"></a>
- <b>Blocksize tests:</b> <p align="justify"> this test, which only makes sense for CUDA algorithms, is aimed at finding the best block size for each kernel with grid search parameter optimization. The range of values for each block axis can be specified in the configuration file. Given a set of CUDA algorithms, the blocksize test reports execution times of each kernel on one or multiple datasets, repeating the measurement for every different block size. Results are presented in a csv file. For every kernel, dataset and block size, the total execution time in ms is reported.</p>
Examples of YACCLAB Output Results
<table> <tr> <td align="center"><img src="doc/fingerprints.png"/></td> <td align="center"><img src="doc/xdocs.png"/></td> </tr> <tr> <td align="center">Fingerprints</td> <td align="center">XDOCS</td> </tr> </table>Contributors
Thanks goes to these wonderful people (emoji key):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> <table> <tr> <td align="center"><a href="http://www.federicobolelli.it"><img src="https://avatars3.githubusercontent.com/u/6863130?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Federico Bolelli</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/commits?author=prittt" title="Code">💻</a> <a href="#projectManagement-prittt" title="Project Management">📆</a> <a href="#maintenance-prittt" title="Maintenance">🚧</a> <a href="#infra-prittt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-prittt" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center"><a href="https://github.com/stal12"><img src="https://avatars2.githubusercontent.com/u/34423515?v=1?s=100" width="100px;" alt=""/><br /><sub><b>Stefano Allegretti</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/commits?author=stal12" title="Code">💻</a> <a href="#maintenance-stal12" title="Maintenance">🚧</a> <a href="https://github.com/prittt/YACCLAB/issues?q=author%3Astal12" title="Bug reports">🐛</a> <a href="#ideas-stal12" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-stal12" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td> <td align="center"><a href="https://github.com/CostantinoGrana"><img src="https://avatars2.githubusercontent.com/u/18437151?v=1?s=100" width="100px;" alt=""/><br /><sub><b>Costantino Grana</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/commits?author=CostantinoGrana" title="Code">💻</a> <a href="#projectManagement-CostantinoGrana" title="Project Management">📆</a> <a href="#ideas-CostantinoGrana" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-CostantinoGrana" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td> <td align="center"><a href="https://michelecancilla.github.io"><img src="https://avatars1.githubusercontent.com/u/22983812?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michele Cancilla</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/commits?author=MicheleCancilla" title="Code">💻</a> <a href="#platform-MicheleCancilla" title="Packaging/porting to new platform">📦</a> <a href="#maintenance-MicheleCancilla" title="Maintenance">🚧</a></td> <td align="center"><a href="http://www.lorenzobaraldi.com"><img src="https://avatars3.githubusercontent.com/u/8173533?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lorenzo Baraldi</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/commits?author=baraldilorenzo" title="Code">💻</a> <a href="#platform-baraldilorenzo" title="Packaging/porting to new platform">📦</a></td> <td align="center"><a href="http://msoechting.de"><img src="https://avatars1.githubusercontent.com/u/6423697?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maximilian Söchting</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/commits?author=msoechting" title="Code">💻</a></td> </tr> <tr> <td align="center"><a href="https://github.com/patrickhwood"><img src="https://avatars.githubusercontent.com/u/2100827?v=4?s=100" width="100px;" alt=""/><br /><sub><b>patrickhwood</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/issues?q=author%3Apatrickhwood" title="Bug reports">🐛</a></td> <td align="center"><a href="https://github.com/fengweichangzi"><img src="https://avatars.githubusercontent.com/u/87119815?v=4?s=100" width="100px;" alt=""/><br /><sub><b>WalnutVision</b></sub></a><br /><a href="https://github.com/prittt/YACCLAB/issues?q=author%3Afengweichangzi" title="Bug reports">🐛</a></td> </tr> </table> <!-- markdownlint-restore --> <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END -->This project follows the all-contributors specification. Contributions of any kind welcome.