diff --git a/.github/workflows/main-tag-release.yml b/.github/workflows/main-tag-release.yml new file mode 100644 index 0000000..0f8d2bb --- /dev/null +++ b/.github/workflows/main-tag-release.yml @@ -0,0 +1,18 @@ +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: read + +jobs: + release-on-push: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: rymndhng/release-on-push-action@master + with: + bump_version_scheme: minor \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1979f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Windows default autosave extension +*.asv + +# OSX / *nix default autosave extension +*.m~ + +# Compiled MEX binaries (all platforms) +*.mex* + +# Packaged app and toolbox files +*.mlappinstall +*.mltbx + +# Generated helpsearch folders +helpsearch*/ + +# Simulink code generation folders +slprj/ +sccprj/ + +# Matlab code generation folders +codegen/ + +# Simulink autosave extension +*.autosave + +# Simulink cache files +*.slxc + +# Octave session info +octave-workspace + +# VSCode workspace files +.vscode + +# Executable files +gui/TDSFT \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8cf38a3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +unibo.ds4h@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE b/LICENSE index f288702..28e227a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,20 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +Copyright (C) 2023, +Filippo Piccinini (E-mail: filippo.piccinini85@gmail.com) +Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +University of Bologna, Italy. +All rights reserved. + +Redistribution and use of the material, with or without modification, is provided +for academic research purpose only and if the following conditions are met: + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3 (or higher) +as published by the Free Software Foundation. This program is +distributed WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. diff --git a/LICENSE_STAPLE b/LICENSE_STAPLE new file mode 100644 index 0000000..3b55a22 --- /dev/null +++ b/LICENSE_STAPLE @@ -0,0 +1,24 @@ +Copyright (c) 2016, AndreasH +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 3e5955b..44255d0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,58 @@ -# 2DSFT -2 dimensional segmentation fusion tool +# TDSFT ![](https://img.shields.io/github/forks/UniBoDS4H/TDSFT?style=social) ![](https://img.shields.io/github/stars/UniBoDS4H/TDSFT?style=social) ![](https://img.shields.io/github/watchers/UniBoDS4H/TDSFT?style=social)
+ +![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) +![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) +![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0) +[![Latest Release](https://img.shields.io/github/v/release/UniBoDS4H/TDSFT?style=for-the-badge&color=blue)](https://img.shields.io/github/v/release) + +> "TDSFT (Two-Dimensional Segmentation Fusion Tool): \ +> an extensible and open-source tool for combining different bidimensional annotations." + + + +1. [Description](#description) +2. [Download](#download) +3. [Documentation](#documentation) +4. [License](#license) +5. [Contact Us](#contact-us) + +## Description ## +*TDSFT* is an open-source tool developed in MATLAB and also distributed as a Standalone application for `MAC`, `Linux`, and `Windows`, which offers a simple and extensible interface where numerous algorithms are proposed to "*mediate*" (*e.g.*, process and fuse) multiple segmentations. + +*TDSFT* is: +- `open-source`: everyone can access and use it. Even though it is developed in Matlab, it is distributed as a standalone application, so **you don't need a Matlab license to use it**; +- `easy-to-use`: it is developed to support and help medical specialists during their work; +- `extensible`: it is designed to be easily extendible with new algorithms thanks to a dedicated graphical interface for configuring new parameters. + +The following algorithms are implemented: +- [X] Average Smallest And Largest +- [X] Average Target From Input +- [X] Average Target Largest +- [X] Average Target Smallest +- [X] Largest +- [X] Middle +- [X] Smallest +- [X] [STAPLE](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1283110/) + +For any further information please read the [documentation](#documentation). + +## Download ## +*TDSFT* is distributed as a Standalone application for `Windows`, `macOS` (only ARM), and `Linux`. You can download it from `github releases` or from the following [link](https://sourceforge.net/p/tdsft/). + +In addition to the standalone application, at the link above, you can find also a useful **video tutorial**. + +## Documentation ## +See the documentation file for the details. You can find this by going to `github releases` or to the following [link](https://sourceforge.net/p/tdsft/). Furthermore, the documentation file is inside the root folder of every standalone application. + +## License ## +See the [license file](LICENSE_GENERAL) for the details. \ +The [STAPLE implementation](api/fusionAlgorithms/include/STAPLE.m) has its [license file](LICENSE_STAPLE). + +## Contact Us ## +- Filippo Piccinini, Istituto Scientifico Romagnolo per lo Studio e la Cura dei Tumori (IRST) IRCCS, Meldola (FC), Italy \ + email: filippo.piccinini@irst.emr.it + +- Lorenzo Drudi, Bachelor's Degree Student in Computer Sciences, University of Bologna, Italy \ + email:   lorenzodrudi11@gmail.com \ + github: [@LorenzoDrudi](https://github.com/LorenzoDrudi) \ + LinkedIn: [@drudilorenzo](https://www.linkedin.com/in/drudilorenzo/) diff --git a/api/closingLineAlgorithms/closing_ChanVese.m b/api/closingLineAlgorithms/closing_ChanVese.m new file mode 100644 index 0000000..2899c1a --- /dev/null +++ b/api/closingLineAlgorithms/closing_ChanVese.m @@ -0,0 +1,37 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close line that needs to be closed. +% - inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% - res (Matrix: [height, width]): +% the resulting segmentation after the closing process. +% +% DESCRIPTION: +% Use the Chan-Vese algorithm to close a result of the fusion process which is not closed. +% Use the input segmentations to get the nearest mask to the border. +% +% The Chan-Vese algorithm is a level set method that differs from classical snake and active contour methods +% because it does not require a stopping edge-function. It is a model which can detect contours both with or +% without gradient, for instance, objects with very smooth boundaries or even with discontinuous boundaries +% +% REFERENCES: +% T. F. Chan and L. A. Vese, +% "Active contours without edges" +% IEEE Transactions on Image Processing, +% vol. 10, no. 2, pp. 266-277, Feb. 2001, doi: 10.1109/83.902291. +function res = closing_ChanVese(fusionResult, inputSegmentations) + mask = getSegmentationsMask(inputSegmentations); + nIterations = 3000; + smoothfactor = 0.6; + incrementFactor = 25; + res = activecontour(fusionResult .* incrementFactor, mask, nIterations, "chan-vese", "SmoothFactor", smoothfactor); + res = imfill(res, "holes"); + res = bwperim(res); +end \ No newline at end of file diff --git a/api/closingLineAlgorithms/closing_GeodesicActiveContour.m b/api/closingLineAlgorithms/closing_GeodesicActiveContour.m new file mode 100644 index 0000000..d0f86a9 --- /dev/null +++ b/api/closingLineAlgorithms/closing_GeodesicActiveContour.m @@ -0,0 +1,34 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation that needs to be closed. +% - inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% - res (Matrix: [height, width]): +% the resulting segmentation after the closing process. + +% DESCRIPTION: +% Use the Geodesic Active Contour algorithm to close a result of the fusion process which is not closed. +% +% This technique is based on active contours evolving in time according to intrinsic geometric measures of the image. +% The evolving contours naturally split and merge, allowing the simultaneous detection of several objects and both interior +% and exterior boundaries. The proposed approach is based on the relation between active contours and the computation of geodesics +% or minimal distance curves. +% +% REFERENCES: +% Caselles V., Kimmel R. and Sapiro G, +% "Geodesic Active Contours", +% International Journal of Computer Vision 22, 61–79 (1997). +function res = closing_GeodesicActiveContour(fusionResult, inputSegmentations) + mask = getSegmentationsMask(inputSegmentations); + incrementFactor = 25; + res = activecontour(fusionResult .* incrementFactor, mask, "edge"); + res = imfill(res, "holes"); + res = bwperim(res); +end \ No newline at end of file diff --git a/api/closingLineAlgorithms/closing_Linear.m b/api/closingLineAlgorithms/closing_Linear.m new file mode 100644 index 0000000..884f7a9 --- /dev/null +++ b/api/closingLineAlgorithms/closing_Linear.m @@ -0,0 +1,20 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 17, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation that needs to be closed. +% - inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% - res (Matrix: [height, width]): +% the resulting segmentation after the closing process. +% +% DESCRIPTION: +% Use the Linear interpolation to close the fusion result. +function res = closing_Linear(fusionResult, inputSegmentations) + res = closingWithInterpolation(fusionResult, inputSegmentations, "linear"); +end \ No newline at end of file diff --git a/api/closingLineAlgorithms/closing_NoClosing.m b/api/closingLineAlgorithms/closing_NoClosing.m new file mode 100644 index 0000000..ec71527 --- /dev/null +++ b/api/closingLineAlgorithms/closing_NoClosing.m @@ -0,0 +1,18 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 10, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% +% OUTPUT: +% - fusionResult (Matrix: [height, width]): +% same as input. + +% DESCRIPTION: +% Simple function to permit to see the result of the fusion process +% without applying the closing operation. +function fusionResult = closing_NoClosing(fusionResult, ~) + % Do nothing. +end \ No newline at end of file diff --git a/api/closingLineAlgorithms/closing_ShapePreserving.m b/api/closingLineAlgorithms/closing_ShapePreserving.m new file mode 100644 index 0000000..1a3b89e --- /dev/null +++ b/api/closingLineAlgorithms/closing_ShapePreserving.m @@ -0,0 +1,26 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation that needs to be closed. +% - inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% - res (Matrix: [height, width]): +% the resulting segmentation after the closing process. + +% DESCRIPTION: +% Use the Shape-Preserving Piecewise Cubic Hermite interpolation (PCHIP) to +% close the segmentation. +% +% REFERENCES: +% ] Fritsch, F. N. and R. E. Carlson, +% "Monotone Piecewise Cubic Interpolation", +% SIAM Journal on Numerical Analysis. Vol. 17, 1980, pp.238–246. +function res = closing_ShapePreserving(fusionResult, inputSegmentations) + res = closingWithInterpolation(fusionResult, inputSegmentations, "pchip"); +end \ No newline at end of file diff --git a/api/closingLineAlgorithms/utils/closingWithInterpolation.m b/api/closingLineAlgorithms/utils/closingWithInterpolation.m new file mode 100644 index 0000000..4eb2bab --- /dev/null +++ b/api/closingLineAlgorithms/utils/closingWithInterpolation.m @@ -0,0 +1,58 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 17, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation that needs to be closed. +% - inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% - method (String): +% the interpolation method to use +% (see the available methods here https://www.mathworks.com/help/matlab/ref/interp1.html#btwp6lt-1-method). +% +% OUTPUT: +% - res (Matrix: [height, width]): +% the resulting segmentation after the closing process. + +% DESCRIPTION: +% Convert the coordinates to polar coordinates, sort them by angular position +% and use the specified interpolation method to interpolate the missing pixels. +function res = closingWithInterpolation(fusionResult, inputSegmentations, method) + % Get the centroid of the largest segmentation. + largest = fusion_Largest(inputSegmentations); + cn = regionprops(largest, "Centroid").Centroid; % centroid + s = size(fusionResult); + + % Find locations of outline pixels. + [idxy, idxx] = find(fusionResult); % in cart coord + [idxth, idxr] = cart2pol(idxx-cn(1), idxy-cn(2)); % in polar coord (theta, rho) + + % Sort pixel locations by angular position with reference to largest segmentation center + [idxth, sortmap] = sort(idxth, "ascend"); + idxr = idxr(sortmap); + + % Query points for interpolation. + nQueryPoints = 100000; + newth = linspace(-pi, pi, nQueryPoints).'; + + % Use the specified interpolation method to interpolate the missing pixels. + % Specify Nan as the value for query points outside the domain. + newr = interp1(idxth, idxr, newth, method, NaN); + + % Remove NaNs, interpolation method produce NaNs for query points outside the domain. + nanp = ~isnan(newr); + newr = newr(nanp); + newth = newth(nanp); + + % Construct output image. + [newx, newy] = pol2cart(newth,newr); + res = false(s); + + res(sub2ind(s, round(newy + cn(2)), round(newx + cn(1)))) = true; + + % Fill holes and get the perimeter to remove interior pixels. + res = imfill(res, "holes"); + res = bwperim(res); +end \ No newline at end of file diff --git a/api/functions/getCentroid.m b/api/functions/getCentroid.m new file mode 100644 index 0000000..1af7ffd --- /dev/null +++ b/api/functions/getCentroid.m @@ -0,0 +1,62 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 20, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - points (one-dimensional array of integers pairs, [numPoints, 2]): +% the points used to compute the centroid +% +% OUTPUT: +% - row (int): +% the row (y coordinate) of the centroid +% - col (int): +% the col (x coordinate) of the centroid +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the points array is empty +% +% DESCRIPTION: +% Get the centroid of a set of points. +% If the number of points is 1, the centroid is the point itself. +% If the number of points is 2, the centroid is the middle point. +% If the number of points is >= 3, use the centroid Matlab built-in function +% (if the points are collinear is computed the middle point of the segment between +% the two extreme points). +function [row, col] = getCentroid(points) + if isempty(points) + throw (MException("TDSFT:fusionProcess", "The points array is empty")); + end + + % Remove duplicates. + points = unique(points, "rows"); + + if size(points, 1) == 1 + row = points(1, 1); + col = points(1, 2); + % Not using (a+b)/2 because it could overflow. + elseif size(points, 1) == 2 + [row, col] = getSegmentMiddlePoint(points); + else + % Disable polyshape warnings. + % Polyshape throws warnings when the points are collinear or when the + % polygon is not valid. + % These situations are handled by the code at line 50. + warning("off", "MATLAB:polyshape:repairedBySimplify"); + warning("off", "MATLAB:polyshape:boolOperationFailed"); + pgon = polyshape(points, "KeepCollinearPoints", true); + warning("on", "MATLAB:polyshape:repairedBySimplify"); + warning("on", "MATLAB:polyshape:boolOperationFailed"); + + % Check if the points are collinear. + if isequal(area(pgon), 0) + [row, col] = getSegmentMiddlePoint(points); + else + [row, col] = centroid(pgon); + end + end + + % Round the coordinates to the nearest integer. + row = round(row); + col = round(col); +end \ No newline at end of file diff --git a/api/functions/segmentations/getSegmentMiddlePoint.m b/api/functions/segmentations/getSegmentMiddlePoint.m new file mode 100644 index 0000000..4623aa0 --- /dev/null +++ b/api/functions/segmentations/getSegmentMiddlePoint.m @@ -0,0 +1,42 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 27, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - points (one-dimensional array of pairs of integers, [numPoints, 2]): +% the collinear points from which the middle point is computed +% +% OUTPUT: +% - row (int): +% the row (y coordinate) of the middle point +% - col (int): +% the col (x coordinate) of the middle point +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the points array is empty +% +% DESCRIPTION: +% Get the middle point of a set of collinear points. +% If there are more than 2 points, the middle point is computed as the +% middle point between the two extreme points. +function [x, y] = getSegmentMiddlePoint(points) + + if length(points) < 2 + throw(MException("TDSFT:fusionProcess", "Insert at least 2 points")); + end + + % If there are more than 2 points search for the extreme points. + if length(points) > 2 + extremePoints = zeros(2,2); + [~, minIdx] = min(points(:,1)); + [~, maxIdx ] = max(points(:,1)); + extremePoints(1, :) = points(minIdx, :); + extremePoints(2, :) = points(maxIdx, :); + else + extremePoints = points; + end + + x = extremePoints(1,1) + ( extremePoints(2,1) - points(1, 1) ) / 2; + y = extremePoints(1,2) + ( extremePoints(2,2) - points(1, 2) ) / 2; +end \ No newline at end of file diff --git a/api/functions/segmentations/multiple/getAverageSegmentation.m b/api/functions/segmentations/multiple/getAverageSegmentation.m new file mode 100644 index 0000000..8da9347 --- /dev/null +++ b/api/functions/segmentations/multiple/getAverageSegmentation.m @@ -0,0 +1,101 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 23, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% - target (Integer || Matrix: [height, width]): +% the target segmentation from which the average is computed. +% It can be: +% - integer: the index of one of the input segmentations; +% - matrix: the segmentation to start the fusion with. +% +% OUTPUT: +% - averageSeg (Matrix: [height, width]): +% the average segmentation. +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the input array 'segmentations' is empty. +% +% DESCRIPTION: +% The average segmentation is computed by averaging the segmentations as follows: +% For each pixel with value 1 of the target segmentation is searched for the closest pixel +% with value 1 in the other segmentations. Then it is computed the average pixel: +% - if the segmentations are 2, the average pixel is the middle point between the two pixels; +% - if the segmentations are more than 2, the average pixel is the centroid of the pixels. +function averageSeg = getAverageSegmentation(segmentations, target) + if isempty(segmentations) + throw(MException("TDSFT:fusionProcess", "Segmentations array empty")); + end + + % Number of input segmentations. + nSeg = length(segmentations); + + + % If there is only one segmentation, return it. + if nSeg == 1 + averageSeg = segmentations{1}; + return; + end + + % Number of segmentations to consider in the average process. + % If startSegmentation is a number, the number of segmentations is the length of the input array. + % Else, the number of segmentations is the length of the input array + 1. + arrayLength = nSeg; + + % Get the main segmentation. + % If startSegmentation is a number, get the segmentation from the array 'segmentations'. + if isnumeric(target) + mainSeg = segmentations{target}; + else + mainSeg = target; + arrayLength = arrayLength + 1; + end + + % Get the size of the main segmentation. + [height, width] = size(mainSeg); + + % Preallocate the output segmentation matrix. + averageSeg = zeros(height, width); + + % For each pixel of the main segmentation. + try + for i = 1:height + for j = 1:width + % If the pixel value is 1 then, for every other segmentation, search for the nearest pixel. + if mainSeg(i, j) == 1 + + % Array containing the pixel himself and the nearest pixels of the other segmentations. + nearestPixels = zeros(arrayLength, 2); + % Current index of the nearestPixels array. + idx = 1; + % Add the pixel of the main segmentation to the array. + nearestPixels(idx, :) = [i, j]; + idx = idx + 1; + + for k = 1:nSeg + % If the segmentation is the start segmentation, skip it. + if isnumeric(target) && k == target + continue; + end + % The index of the array is the index of the segmentation + 1 because the first + % element is the main segmentation pixel. + [row, col] = getNearestNonZeroPixel(segmentations{k}, i, j); + nearestPixels(idx, :) = [row, col]; + idx = idx + 1; + end + + % Get the centroid of the points. + [row, col] = getCentroid(nearestPixels); + + % Set the pixel to 1. + averageSeg(row, col) = 1; + end + end + end + catch ME + rethrow(ME); + end +end \ No newline at end of file diff --git a/api/functions/segmentations/multiple/getCommonArea.m b/api/functions/segmentations/multiple/getCommonArea.m new file mode 100644 index 0000000..3551524 --- /dev/null +++ b/api/functions/segmentations/multiple/getCommonArea.m @@ -0,0 +1,36 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 11, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - overlap(Matrix: [height, width]: +% the overlap between the binary filled segmentations. +% - nSeg(int): +% the number of segmentations. +% +% OUTPUT: +% - seg((Matrix: [height, width]): +% the segmentation of the common area. +% - area: +% the common area between all the segmentations. +% +% DESCRIPTION: +% Get the common area between all the segmentations. +% The common area is the area covered by all the segmentations. +function [area, perimeter] = getCommonArea(overlap, nSeg) + area = overlap; + [m, n] = size(overlap); + + % If the pixel is covered by all the segmentations, it is set to 1. + for i = 1:m + for j = 1:n + if area(i,j) == nSeg + area(i,j) = 1; + else + area(i,j) = 0; + end + end + end + + perimeter = bwperim(area); +end \ No newline at end of file diff --git a/api/functions/segmentations/multiple/getFilledSegmentations.m b/api/functions/segmentations/multiple/getFilledSegmentations.m new file mode 100644 index 0000000..89d01b1 --- /dev/null +++ b/api/functions/segmentations/multiple/getFilledSegmentations.m @@ -0,0 +1,21 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations(Cell array: [1, nSeg] (Cells: matrix [height, width]): +% the segmentations to fill. +% +% OUTPUT: +% - filledSegmentations(Cell array: [1, nSeg] (Cells: matrix [height, width]): +% the filled input segmentations. +% +% DESCRIPTION: +% Fill (make dense) all the input segmentations. +function filledSegmentations = getFilledSegmentations(segmentations) + % Preallocate the output. + filledSegmentations = cell(1, length(segmentations)); + for i = 1:length(segmentations) + filledSegmentations{i} = imfill(segmentations{i}, "holes"); + end +end \ No newline at end of file diff --git a/api/functions/segmentations/multiple/getLargestArea.m b/api/functions/segmentations/multiple/getLargestArea.m new file mode 100644 index 0000000..254024f --- /dev/null +++ b/api/functions/segmentations/multiple/getLargestArea.m @@ -0,0 +1,26 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - overlappedSegmentations(Matrix: [height, width]: +% the sum (overlap) of all the segmentations. +% +% OUTPUT: +% - largestSegmentation(Matrix: [height, width]): +% the perimeter of the total area covered by the segmentations. +% - largestArea(Matrix: [height, width]): +% the total area covered by the segmentations. +% +% DESCRIPTION: +% Return the largest possible area (and its perimeter) as a matrix height * width. +% The largest area is the total area covered by the segmentations. +function [area, perimeter] = getLargestArea(overlappedSegmentations) + + % Binarize the overlapped segmentations. + overlappedSegmentations = uint8( overlappedSegmentations > 0 ); + + % Fill the resulting segmentation and get the perimeter. + area = imfill(overlappedSegmentations, "holes"); + perimeter = bwperim(area); +end \ No newline at end of file diff --git a/api/functions/segmentations/multiple/getSegmentationsMask.m b/api/functions/segmentations/multiple/getSegmentationsMask.m new file mode 100644 index 0000000..47779f8 --- /dev/null +++ b/api/functions/segmentations/multiple/getSegmentationsMask.m @@ -0,0 +1,22 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations of which compute the mask. +% +% OUTPUT: +% - mask(Matrix: [height, width]): +% the mask of the segmentations. +% +% +% DESCRIPTION: +% Mask of the segmentations used by the close line algorithms. +% In order to obtain faster and more accurate segmentation results, is important to specify an initial +% contour position that is close to the desired object boundaries. For that reason, we use as a mask the +% largest segmentation area. +function mask = getSegmentationsMask(segmentations) + mask = fusion_Largest(segmentations); + mask = imfill(mask, "holes"); +end \ No newline at end of file diff --git a/api/functions/segmentations/multiple/overlapSegmentations.m b/api/functions/segmentations/multiple/overlapSegmentations.m new file mode 100644 index 0000000..4aa4e6b --- /dev/null +++ b/api/functions/segmentations/multiple/overlapSegmentations.m @@ -0,0 +1,40 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, nSeg] (Cells: matrix [height, width]): +% the segmentations to overlap. +% +% OUTPUT: +% - overlap (Matrix [height, width]): +% the overlapped segmentations. +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the input is empty. +% +% DESCRIPTION: +% Overlap (sum) the input segmentations and return the resulting matrix. +function overlap = overlapSegmentations(segmentations) + % Check if the input is empty. + if isempty(segmentations) + throw(MException("TDSFT:fusionProcess", "Segmentations array empty")); + end + + % If there is only one segmentation, return it. + if length(segmentations) == 1 + overlap = segmentations{1}; + return; + end + + % Initialize the overlap. + [height, width] = size(segmentations{1}); + overlap = zeros(height, width, "uint8"); + + % Overlap all the segmentations. + for i=1:length(segmentations) + seg = uint8(segmentations{i}); + overlap = overlap + seg; + end +end \ No newline at end of file diff --git a/api/functions/segmentations/single/getBinaryImageBackground.m b/api/functions/segmentations/single/getBinaryImageBackground.m new file mode 100644 index 0000000..10e6807 --- /dev/null +++ b/api/functions/segmentations/single/getBinaryImageBackground.m @@ -0,0 +1,27 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% -img (Matrix [height, width]): +% a black and white image. +% +% OUTPUT: +% - bg (int): +% get the background of a black and white image. +% 1 if the background is white, 0 otherwise. +% +% DESCRIPTION: +% Returns the background color of a binary black and white image. +function bg = getBinaryImageBackground(img) + [h, w] = size(img); + + % Get the sum of the first and last row and column. + firstRow = sum( img(1, :) ); + lastRow = sum( img(h, :) ); + firstCol = sum( img(:, 1) ); + lastCol = sum( img(:, w) ); + + % Since objects can't touch the border, if they are all 0, the background is black. + bg = firstRow || lastRow || firstCol || lastCol; +end \ No newline at end of file diff --git a/api/functions/segmentations/single/getNearestNonZeroPixel.m b/api/functions/segmentations/single/getNearestNonZeroPixel.m new file mode 100644 index 0000000..489024c --- /dev/null +++ b/api/functions/segmentations/single/getNearestNonZeroPixel.m @@ -0,0 +1,55 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 23, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentation (Matrix [height, width]): +% the segmentation where to find the nearest non-zero pixel. +% - startRow (int): +% the row (y coordinate) of the pixel from which to start the search. +% - startCol (int): +% the col (x coordinate) of the pixel from which to start the search. +% +% OUTPUT: +% - row (int): +% the row (x coordinate) of the nearest non-zero pixel. +% - col (int): +% the col (y coordinate) of the nearest non-zero pixel. +% +% THROWS: +% 'TDSFT:fusionProcess': +% if the pixel index is out of bounds. +% 'TDSFT:fusionProcess': +% if no non-zero pixel is found. +% +% DESCRIPTION: +% Get the nearest non-zero pixel from the given pixel index. +function [row, col] = getNearestNonZeroPixel(segmentation, startRow, startCol) + [height, width] = size(segmentation); + + %Check if the pixel index is out of bounds. + if startRow < 1 || startRow > height || startCol < 1 || startCol > width + throw(MException("TDSFT:fusionProcess", "Pixel index out of bounds")); + end + + % If the pixel is already non-zero, return it. + if segmentation(startRow, startCol) ~= 0 + row = startRow; + col = startCol; + return; + end + + % Use the bwdist function to get the nearest non-zero pixel. + [~, nearestPixelArray] = bwdist(segmentation); + + % Get the linear index of the nearest non-zero pixel. + nearestPixelLinearIndex = nearestPixelArray(startRow, startCol); + + % Check if no non-zero pixel is found. + if nearestPixelLinearIndex == 0 + throw(MException("TDSFT:fusionProcess", "No non-zero pixel found")); + end + + % Convert the linear index to the row and col index. + [row, col] = ind2sub([height, width], nearestPixelLinearIndex); +end \ No newline at end of file diff --git a/api/functions/segmentations/single/getOnePixelSegmentation.m b/api/functions/segmentations/single/getOnePixelSegmentation.m new file mode 100644 index 0000000..9522b96 --- /dev/null +++ b/api/functions/segmentations/single/getOnePixelSegmentation.m @@ -0,0 +1,43 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - seg (Matrix [height, width]): +% black and white segmentation. +% - line (string): +% the line to use if the segmentation is of more than one pixel +% (see onePixelSegmentation.m for more details). +% +% OUTPUT: +% - opSeg (Matrix [height, width]): +% the one-pixel segmentation. +% +% DESCRIPTION: +% Gets the one-pixel segmentation. Often the line of the segmentation is not of one pixel, but of more. +% If the segmentation is of more than one pixel, it is possible to get the external, internal, or middle line. +% (see OnePixelLineTypes.m for more details) +function opSeg = getOnePixelSegmentation(seg, line) + % Fill the holes. + segFill = imfill(seg, "holes"); + + % If the segmentation is dense, only the external line is allowed. + if isequal(seg, segFill) && line ~= "external" + throw(MException("TDSFT:processImage", ... + "Dense object with line type different from external.")); + end + + % Get the one-pixel segmentation using the specified line type. + switch line + case "internal" + intArea = segFill - seg; + opSeg = bwperim(intArea); + case "middle" + opSeg = bwskel(seg); + opSeg = imfill(opSeg, "holes"); + opSeg = bwperim(opSeg); + % Default is external line. + otherwise + opSeg = bwperim(segFill); + end +end \ No newline at end of file diff --git a/api/functions/segmentations/single/imTo8bit.m b/api/functions/segmentations/single/imTo8bit.m new file mode 100644 index 0000000..53d5b58 --- /dev/null +++ b/api/functions/segmentations/single/imTo8bit.m @@ -0,0 +1,44 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - img (Matrix [height, width]): +% image to convert. +% +% OUTPUT: +% - cImg (Matrix [height, width]): +% converted image. +% +% THROWS: +% - 'TDSFT:processImage': +% if the image is not converted to 8-bit. +% +% DESCRIPTION: +% Converts an image to 8-bit format. +% Supported formats: 12-bit, 16-bit, 32-bit, 64-bit. +function cImg = imTo8bit(img) + if isa(img, ImagesStoringMethods.INT_16.string) + % 16-bit + if max(img(:)) > 2^12 % 12-bit images are stored as 16-bit + cImg = uint8( 255.*double(img)./(2^16-1) ); + % 12-bit + else + cImg = uint8( 255.*double(img)./(2^12-1) ); + end + % 32-bit + elseif isa(img, ImagesStoringMethods.INT_32.string) + cImg = uint8( 255.*double(img)./(2^32-1) ); + % 64-bit + elseif isa(img, ImagesStoringMethods.INT_64.string) + cImg = uint8( 255.*double(img)./(2^64-1) ); + else + cImg = img; + end + + % Check if the storing method of the converted image is 'uint8' + % else throws an error. + if ~isa(cImg, ImagesStoringMethods.INT_8.string) + throw(MException("TDSFT:processImage", "Image not converted to 8 bit")); + end +end \ No newline at end of file diff --git a/api/functions/segmentations/single/isSegmentationClosed.m b/api/functions/segmentations/single/isSegmentationClosed.m new file mode 100644 index 0000000..7c6f9be --- /dev/null +++ b/api/functions/segmentations/single/isSegmentationClosed.m @@ -0,0 +1,52 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - img (Matrix [height, width]): +% black and white image. +% - flag (boolean): +% true if a dense object is present and more tests are required, false otherwise. +% It adds more checks to the segmentation to be able to better recognize open lines and close dense lines. +% Be careful, it is difficult to recognize a dense object from an open line of more than one pixel. +% Since an open line of more than one pixel is, by definition, a dense object we use some statistics to +% recognize a dense object from an open line of more than one pixel. +% So if you are not sure that the object is dense, set dense to false. +% +% OUTPUT: +% - check: +% true (1) => Line closed +% false (0) => Line opened +% +% DESCRIPTION: +% Checks if the segmentation is closed or not. Returns true if it is closed. +function check = isSegmentationClosed(img, flag) + imgFill = imfill(img, "holes"); + + % If the image can be filled it means the object in the image is not dense + % and there is a hole to fill so the segmentation is closed. + if ~isequal(img, imgFill) + check = true; + else + % Here there are two options: + % 1) The object in the image is a dense object; + % 2) The object in the image is an open line of more than one pixel. + + % Check if it is an open segmentation of 1 pixel. + perim = bwperim(img); + check = ~isequal(perim, img); + + if ~check || ~flag + return; + end + + % If dense option is true do a statistical test to check to recognize a dense object from an open line of more than one pixel. + % ATTENTION: it is not a perfect check since it is not possible to separate a dense object (close dense line) + % from a line of more than one pixel. + fillCells = nnz(imgFill); + perimCells = nnz(perim); + + check = fillCells > 2 * perimCells; + end + +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_AverageSmallestAndLargest.m b/api/fusionAlgorithms/fusion_AverageSmallestAndLargest.m new file mode 100644 index 0000000..e268296 --- /dev/null +++ b/api/fusionAlgorithms/fusion_AverageSmallestAndLargest.m @@ -0,0 +1,61 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 27, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% varargin: The function accepts both 1 or 2 parameters as follows: +% - 1 parameter: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% The smallest and the largest segmentation are obtained from this array. +% - 2 parameters (in order): +% - smallest (Matrix [height, width]): +% the smallest segmentation. +% - largest (Matrix [height, width]): +% the largest segmentation. +% +% OUTPUT: +% - averageSeg (matrix [height, width]): +% the average segmentation. +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the input param number is wrong. +% +% DESCRIPTION: +% Get the average segmentation between the smallest and the largest one. +% The average segmentation is obtained by taking the 1-pixel line in the middle of +% the area between the two segmentations. +% The function accepts as input both an array of segmentations or (in order) the smallest +% and the largest segmentation. +function averageSeg = fusion_AverageSmallestAndLargest(varargin) + % If it is passed an array of segmentations, get the smallest and the largest. + if nargin == 1 + segmentations = varargin{1}; + try + smallest = fusion_Smallest(segmentations); + largest = fusion_Largest(segmentations); + catch ME + rethrow(ME); + end + elseif nargin == 2 + smallest = varargin{1}; + largest = varargin{2}; + else + throw(MException("TDSFT:fusionProcess", "Wrong number of parameters")); + end + + % Overlap the smallest and the largest segmentations. + averageSeg = smallest + largest; + + % Fill the holes and get the area between the two segmentations. + averageSeg = imfill(averageSeg, "holes") - imfill(smallest, "holes"); + averageSeg = imbinarize(averageSeg); + + % Get 1-pixel line in the middle of the area between the two segmentations. + averageSeg = bwskel(logical(averageSeg)); + + % Fill the holes and get the perimeter to get the perfect average segmentation. + averageSeg = imfill(averageSeg, "holes"); + averageSeg = bwperim(averageSeg); +end diff --git a/api/fusionAlgorithms/fusion_AverageTargetFromInput.m b/api/fusionAlgorithms/fusion_AverageTargetFromInput.m new file mode 100644 index 0000000..0b99a34 --- /dev/null +++ b/api/fusionAlgorithms/fusion_AverageTargetFromInput.m @@ -0,0 +1,38 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 23, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% - target (int): +% the index of the segmentation to be used as the main segmentation. +% +% OUTPUT: +% - averageSeg (matrix [height, width]): +% the average segmentation. +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the start segmentation index is greater than the number of segmentations. +% +% DESCRIPTION: +% The average segmentation is computed by averaging the segmentations as follows: +% for each pixel with value 1 of the start segmentation is searched for the closest pixel +% with value 1 in the other segmentations. +% Then, it is computed the average pixel: +% - if the segmentations are 2, the average pixel is the middle point between the two pixels; +% - if the segmentations are more than 2, the average pixel is the centroid of the pixels +% (if the points are collinear is computed the middle point of the segment between the +% two extreme points). +function averageSeg = fusion_AverageTargetFromInput(segmentations, target) + try + % Check if the target segmentation is in the range. + if target > length(segmentations) || target < 1 + throw(MException("TDSFT:fusionProcess", "Wrong start segmentation index")); + end + averageSeg = getAverageSegmentation(segmentations, target); + catch ME + rethrow(ME); + end +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_AverageTargetLargest.m b/api/fusionAlgorithms/fusion_AverageTargetLargest.m new file mode 100644 index 0000000..93138c5 --- /dev/null +++ b/api/fusionAlgorithms/fusion_AverageTargetLargest.m @@ -0,0 +1,28 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 23, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% +% OUTPUT: +% - averageSeg (matrix [height, width]): +% the average segmentation. +% +% DESCRIPTION: +% The average segmentation is computed by averaging the segmentations as follows: +% for each pixel with value 1 of the largest segmentation is searched for the closest pixel +% with value 1 of every input segmentation. Then it is computed the average pixel: +% - if the segmentations are 2, the average pixel is the middle point between the two pixels; +% - if the segmentations are more than 2, the average pixel is the centroid of the pixels +% (if the points are collinear is computed the middle point of the segment between the +% two extreme points). +function averageSeg = fusion_AverageTargetLargest(segmentations) + try + largest = fusion_Largest(segmentations); + averageSeg = getAverageSegmentation(segmentations, largest); + catch ME + rethrow(ME); + end +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_AverageTargetSmallest.m b/api/fusionAlgorithms/fusion_AverageTargetSmallest.m new file mode 100644 index 0000000..84bf304 --- /dev/null +++ b/api/fusionAlgorithms/fusion_AverageTargetSmallest.m @@ -0,0 +1,28 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 23, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% +% OUTPUT: +% - averageSeg (matrix [height, width]): +% the average segmentation. +% +% DESCRIPTION: +% The average segmentation is computed by averaging the segmentations as follows: +% for each pixel with value 1 of the smallest segmentation is searched for the closest pixel +% with value 1 of every input segmentation. Then it is computed the average pixel: +% - if the segmentations are 2, the average pixel is the middle point between the two pixels; +% - if the segmentations are more than 2, the average pixel is the centroid of the pixels +% (if the points are collinear is computed the middle point of the segment between the +% two extreme points). +function averageSeg = fusion_AverageTargetSmallest(segmentations) + try + smallest = fusion_Smallest(segmentations); + averageSeg = getAverageSegmentation(segmentations, smallest); + catch ME + rethrow(ME); + end +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_Largest.m b/api/fusionAlgorithms/fusion_Largest.m new file mode 100644 index 0000000..862f44a --- /dev/null +++ b/api/fusionAlgorithms/fusion_Largest.m @@ -0,0 +1,30 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 20, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width] || Matrix [height, width]): +% - array (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse; +% - matrix (Matrix [height, width]): +% the segmentations are already overlapped in a matrix. +% (The algorithm need NO dense segmentations, so to overlap the segmentations do not have to fill the holes) +% +% OUTPUT: +% - largestSegmentation (Matrix [height, width]): +% the largest segmentation. +% +% DESCRIPTION: +% Fuse all the segmentations together overlapping them and getting the largest possible segmentation. +% The largest segmentation is the smallest segmentation possible which contains each input segmentation. +% To obtain it, first the segmentations are overlapped and then it is returned the perimeter of the total +% area covered by them. +function largestSegmentation = fusion_Largest(segmentations) + if iscell(segmentations) + overlap = overlapSegmentations(segmentations); + else + overlap = segmentations; + end + + [~, largestSegmentation] = getLargestArea(overlap); +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_Middle.m b/api/fusionAlgorithms/fusion_Middle.m new file mode 100644 index 0000000..8dbdf18 --- /dev/null +++ b/api/fusionAlgorithms/fusion_Middle.m @@ -0,0 +1,85 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% - algorithm: +% algorithm to use for the last two segmentations left if the segmentations are even. +% Available algorithms: +% - 'Largest segmentation' +% - 'Smallest segmentation' +% - 'Average smallest and largest segmentation' +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the number of segmentations is even and the algorithm is not specified. +% - 'TDSFT:fusionProcess': +% if the algorithm specified is not available. +% +% OUTPUT: +% - middleSegmentation (Matrix [height, width])): +% the middle segmentation. +% +% DESCRIPTION: +% Get the middle segmentation. +% Discard outliers until the middle segmentation is reached. To do that iterate over the segmentations and +% discard the largest and the smallest segmentation at each iteration. +% If the number of segmentations is even specify a method for the last two segmentations left. +% The available methods are: +% - 'Largest segmentation': use the largest segmentation; +% - 'Smallest segmentation': use the smallest segmentation; +% - 'Average smallest and largest segmentation': use the average between the smallest and the largest segmentation. +% (for more details see functions related to algorithms) +function middleSegmentation = fusion_Middle(segmentations, algorithm) + % Check if the number of segmentations is even and the algorithm is specified. + if mod(length(segmentations), 2) == 0 && nargin < 2 + throw(MException("TDSFT:fusionProcess",... + "If the number of segmentations is even, the algorithm must be specified")); + end + + overlap = overlapSegmentations(segmentations); + + nSeg = length(segmentations); % number of segmentations + nIt = floor( (nSeg - 1) / 2 ); % total number of iterations + + % Fill segmentations and overlap. + filledSegmentations = getFilledSegmentations(segmentations); + overlapFilled = overlapSegmentations(filledSegmentations); + + % At each iteration discard the largest and the smallest segmentation. + for i=1:nIt + largest = uint8( fusion_Largest(overlap) ); + smallest = uint8( fusion_Smallest(overlapFilled, nSeg) ); + + overlap = overlap - largest; + overlap = overlap - smallest; + + filledLargest = imfill(largest, "holes"); + filledSmallest = imfill(smallest, "holes"); + overlapFilled = overlapFilled - filledLargest; + overlapFilled = overlapFilled - filledSmallest; + + nSeg = nSeg - 2; + end + + % If the number of segmentations is even, use the specified algorithm for the last two segmentations left. + if isequal(nSeg, 2) + algorithm = StringsUtils.fromSpacedToFusionAlgorithmFullName(algorithm); + if strcmp(algorithm, strcat(Constants.ALGORITHM_NAMES_FILE_ROOT, "Largest")) + middleSegmentation = fusion_Largest(overlap); + elseif strcmp(algorithm, strcat(Constants.ALGORITHM_NAMES_FILE_ROOT, "Smallest")) + middleSegmentation = fusion_Smallest(overlapFilled, nSeg); + elseif strcmp(algorithm, strcat(Constants.ALGORITHM_NAMES_FILE_ROOT, "AverageSmallestAndLargest")) + smallest = fusion_Smallest(overlapFilled, nSeg); + largest = fusion_Largest(overlap); + middleSegmentation = fusion_AverageSmallestAndLargest(smallest, largest); + else + throw(MException("TDSFT:fusionProcess", "Algorithm not available")); + end + else + middleSegmentation = imbinarize(overlap); + end + +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_STAPLE.m b/api/fusionAlgorithms/fusion_STAPLE.m new file mode 100644 index 0000000..534ab3a --- /dev/null +++ b/api/fusionAlgorithms/fusion_STAPLE.m @@ -0,0 +1,40 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% +% OUTPUT: +% - gtSegmentation (Matrix [height, width]): +% the ground truth segmentation computed with STAPLE algorithm. +% +% DESCRIPTION: +% Use STAPLE algorithm to get the ground truth segmentation. +% +% REFERENCES: +% Warfield, Simon K., Kelly H. Zou, and William M. Wells. +% "Simultaneous truth and performance level estimation (STAPLE): +% an algorithm for the validation of image segmentation." +% Medical Imaging, IEEE Transactions on 23.7 (2004): 903-921. +function gtSegmentation = fusion_STAPLE(segmentations) + % Convert to the right format for STAPLE + % (See STAPLE file (include folder) for more details). + + % Get segmentations dimensions. + imageDims = size(segmentations{1}); + nSeg = length(segmentations); + + % Preallocate the array. + stapleParam = zeros(imageDims(1) * imageDims(2), nSeg); + for i=1:length(segmentations) + seg = segmentations{i}; + stapleParam(:, i) = seg(:); + end + [W, ~, ~] = STAPLE(stapleParam); + + % Reshape to get the ground truth segmentation. + threshold = .5; + gtSegmentation = reshape((W >= threshold), imageDims); +end \ No newline at end of file diff --git a/api/fusionAlgorithms/fusion_Smallest.m b/api/fusionAlgorithms/fusion_Smallest.m new file mode 100644 index 0000000..eb9d3d4 --- /dev/null +++ b/api/fusionAlgorithms/fusion_Smallest.m @@ -0,0 +1,45 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% varargin: The function accept both 1 or 2 parameters as follows: +% - 1 parameter: +% segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% - 2 parameters: +% - overlap (Matrix [height, width]): +% the overlap of the segmentations (the segmentations are already overlapped). +% (The algorithm needs DENSE segmentation, so before the overlap you have to fill them) +% - nSeg (Integer): +% the number of segmentations. +% +% OUTPUT: +% - smallestSegmentation (Matrix [height, width]): +% the smallest segmentation. +% +% THROWS: +% - 'TDSFT:fusionProcess': +% if the input param number is wrong. +% +% DESCRIPTION: +% Fuse all the segmentations together overlapping them if needed and getting the smallest segmentation possible. +% The smallest segmentation is the perimeter of the common area covered by every segmentation (the common area between every segmentation). +function smallestSegmentation = fusion_Smallest(varargin) + % If the segmentations are not overlapped, overlap them. + if nargin == 1 + segmentations = varargin{1}; + nSeg = length(segmentations); + + % Fill and overlap the segmentations. + filledSegmentations = getFilledSegmentations(segmentations); + overlap = overlapSegmentations(filledSegmentations); + elseif nargin == 2 + overlap = varargin{1}; + nSeg = varargin{2}; + else + throw(MException("TDSFT:fusionProcess", "Wrong number of input arguments")); + end + + [~, smallestSegmentation] = getCommonArea(overlap, nSeg); +end \ No newline at end of file diff --git a/api/fusionAlgorithms/include/STAPLE.m b/api/fusionAlgorithms/include/STAPLE.m new file mode 100644 index 0000000..26f5198 --- /dev/null +++ b/api/fusionAlgorithms/include/STAPLE.m @@ -0,0 +1,95 @@ +%% Vectorized MATLAB implementation of the STAPLE algorithm by Warfield et al. +% This code currently only supports the case of binary segmentations. +% +% Function: [W, p, q] = STAPLE(D) +% Parameter: D, data matrix of segmentations, dimensions VOXELS x RATERS +% Returns: W, est. weight matrix for each voxel +% p, est. vector of sensitivities for each expert +% q, est. vector of specificities for each expert +% +% You can simply threshold the resulting matrix W to get an estimated ground truth segmentation, +% e.g. T = (W >= .5) +% +% Literature: Warfield, Simon K., Kelly H. Zou, and William M. Wells. +% "Simultaneous truth and performance level estimation (STAPLE): +% an algorithm for the validation of image segmentation." +% Medical Imaging, IEEE Transactions on 23.7 (2004): 903-921. +% +% Andreas Husch +% 2013-07-10, 2015-07-06, 2016-05-10 +% mail@andreashusch.de +%% Example for usage: +% %Using test data (2D prostate segmentations) from original publication +% s1 = nrrdread('seg001.nrrd'); +% s2 = nrrdread('seg002.nrrd'); +% s3 = nrrdread('seg003.nrrd'); +% s4 = nrrdread('seg004.nrrd'); +% s5 = nrrdread('seg005.nrrd'); +% imageDims = size(s1); +% D = [s1(:), s2(:), s3(:), s4(:), s5(:)]; % pixels in rows, raters in columns +% [W, p, q]= STAPLE(D); +% % p,q values of your raters: +% p +% q +% Estimated ground truth image: +% gtImage = reshape((W >= .5), imageDims); +% figure, imagesc(gtImage) + + +%% Vectorized implementation of the classical STAPLE-Algorithm by Warfield et al. for binary segmentations +function [W, p, q] = STAPLE(D) + %% Inputs & Checks + if(size(D,2) > size(D,1)) + warning('Number of raters is larger than the number of voxels! Is the input transposed correctly?'); + end + D = double(D); + N = size(D,2); %Number of raters + + %% Parameters + MAX_ITERATIONS = 100; + EPSILON = 0.00001; % convergence criterion + + % Initial sensitivity and specificity parameter p(j),q(j) for all + % raters j + p(1:N) = 0.99999; + q(1:N) = 0.99999; + Tprior = (sum(D(:))/length(D(:))); % note dependence on (sub)volume size, final result depends on this prior (which is not an implementation issue but a basic limitation of the EM approach) + + avgW = 1; + W = zeros(1,length(D)); + + %% EM + %E-Schritt + for step=1:MAX_ITERATIONS + % The following code is equivalent to this loop by MUCH faster + % for i = 1:length(D) + % W(i) = ((prod(p(D(i,:))) * prod(1 - p(~D(i,:)))) * Tprior) / ... + % ((prod(p(D(i,:))) * prod(1 - p(~D(i,:)))) * Tprior + (prod(q(~D(i,:))) * prod(1 - q(D(i,:))))) * (1- Tprior) ; + % %NOTE that prod([]) = 1 + % end + P = repmat(p,length(D), 1); + Q = repmat(q,length(D), 1); + P_given_D = P .* D; %TODO: use bsxfun instead of repmat? + P_given_D(P_given_D(:)== 0) = 1; % + Q_given_D = 1 - Q .* D; + Q_given_D(Q_given_D(:)== 0) = 1; % alternative: initialise with 1 and set Q_given_D(D) = 1- P(D) + compP_given_not_D = 1 - P .* ~D; + compP_given_not_D(compP_given_not_D(:)== 0) = 1; + compQ_given_not_D = Q .* ~D; + compQ_given_not_D(compQ_given_not_D(:)== 0) = 1; + + % W(i) can be interpreted as the prob. of voxel i being true (i.e. is part of the ground-truth y) for given p(1:N), q(1:N) + W = (prod(P_given_D') .* prod(compP_given_not_D') * Tprior) ./ ... + ((prod(P_given_D') .* prod(compP_given_not_D') * Tprior) + (prod(Q_given_D') .* prod(compQ_given_not_D') * (1 - Tprior))); %#ok + + % Convergence? + if(abs(avgW - sum(W) / length(W)) < EPSILON) + break; + end + avgW = sum(W) / length(W); + + % M-Step + p = (W * D) / sum(W(:)); % W * D = sum(W(D)) + q = ((1 - W) * ~D) / sum(1 - W(:)); + end +end \ No newline at end of file diff --git a/api/fusionAlgorithms/inputs/fusion_AverageTargetFromInput.json b/api/fusionAlgorithms/inputs/fusion_AverageTargetFromInput.json new file mode 100644 index 0000000..802bb2b --- /dev/null +++ b/api/fusionAlgorithms/inputs/fusion_AverageTargetFromInput.json @@ -0,0 +1,9 @@ +{ + "inputs": [ + { + "name": "Target Segmentation Index", + "type": "InputSegmentationsSelector", + "help": "The segmentation to use as the target for the algorithm. It is the index of one of the segmentations in the input list." + } + ] +} \ No newline at end of file diff --git a/api/fusionAlgorithms/inputs/fusion_Middle.json b/api/fusionAlgorithms/inputs/fusion_Middle.json new file mode 100644 index 0000000..08c9355 --- /dev/null +++ b/api/fusionAlgorithms/inputs/fusion_Middle.json @@ -0,0 +1,14 @@ +{ + "inputs": [ + { + "name": "Algorithm", + "type": "DropDown", + "value": [ + "Average Smallest And Largest", + "Largest", + "Smallest" + ], + "help": "Select the algorithm to use to fuse the last two remaining segmentations. For more details about each algorithm see the documentation." + } + ] +} \ No newline at end of file diff --git a/controller/Controller.m b/controller/Controller.m new file mode 100644 index 0000000..fab3d09 --- /dev/null +++ b/controller/Controller.m @@ -0,0 +1,80 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 19, 2023 +% NAME: TDSFT (version 1.0) +% +% Controller of the fusion process. +% It gets the parameters of the fusion process from the gui and +% calls the specified algorithm. +classdef Controller + methods (Abstract) + % Execute the fusion process. + % + % Parameters: + % - segmentations (Cell array: [1, nSeg], Cells: matrix [height, width]): + % the segmentations to be fused. + % - fusionAlgorithm (string): + % the algorithm to be used for the fusion process. + % (The algorithm must be in the folder "api/fusionAlgorithms") + % - closingLineAlgorithm (string): + % the method to use to close the result segmentation if it + % is not already a close line. + % (The algorithm must be in the folder "api/closingLineAlgorithms") + % - varargin (Cell array: [1, nVarargin]): + % the additional parameters of the algorithm. + % (Some algorithms require additional parameters) + % + % Output: + % - resSeg (Matrix [height, width]): + % the resulting segmentation of the fusion process. + % + % Throws: + % - 'TDSFT:fusionProcess': + % if the number of input segmentations is less than 2. + resultSegmentation = executeFusion(obj, segmentations, fusionAlgorithm, closingLineAlgorithm, varargin) + + % Process the image. + % + % The following steps are performed: + % - Convert the image to 8-bit; + % - Convert the image to black and white; + % - Convert to the required configuration (black background and white segmentation); + % - If there are more than one object, select the largest one; + % - Get the one pixel segmentation of the object contained in the image using the specified method. + % + % If dense is true only external line can be used. If the object is dense there is no middle + % or internal line!. + % + % Parameters: + % - img (Matrix [height, width]): + % the image to convert. + % - dense (boolean): + % true (1) if a dense object is present and more test are wanted/neeeded, false (0) otherwise. + % It adds more checks to the segmentation to be able to better recognize open lines and close dense lines. + % Be careful, it is difficult to recognize a dense object from an open line of more than one pixel. + % Since an open line of more than one pixel is, by definition, a dense object we use some statistics to + % recognize a dense object from an open line of more than one pixel. + % So if you are not sure that the object is dense, set dense to false. + % If dense is true only external line can be used since a dense object have no middle or internal line! + % - line (string): + % the line to use if the segmentation is more than one pixel (external, middle or internal). + % (see onePixelLineTypes.m for more details) + % + % Output: + % - blackAndWhiteImg (Matrix [height, width]): + % original image converted to black and white. + % - segmentation (Matrix [height, width]): + % segmentation of the object. + % - wrn (boolean): + % true if the input image had more than one channel but was not an rgb image. + % If it is the case it will be used only the first channel + % + % Throws: + % - 'TDSFT:processImage': + % if the segmentation dense is true and line is not external. + % - 'TDSFT:processImage': + % if the segmentation is an open line. + % - 'TDSFT:processImage': + % if the segmentation is empty. + [blackAndWhiteImg, segmentation, wrn] = processImage(obj, img, dense, line) + end +end \ No newline at end of file diff --git a/controller/ControllerImpl.m b/controller/ControllerImpl.m new file mode 100644 index 0000000..c9dc08e --- /dev/null +++ b/controller/ControllerImpl.m @@ -0,0 +1,103 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 19, 2023 +% NAME: TDSFT (version 1.0) +% +% Implementation of the FusionController interface. +classdef ControllerImpl < Controller + methods + function resultSegmentation = executeFusion(~, segmentations, fusionAlgorithm, closingLineAlgorithm, varargin) + printName = erase(fusionAlgorithm, 'fusion_'); + printName = StringsUtils.fromCamelCaseToSpacedString(printName); + fprintf('Computing %s...\n', printName); + + % Throw an exception if the number of segmentations is less than 2. + if length(segmentations) < 2 + throw(MException("TDSFT:fusionProcess", "The number of segmentations must be at least 2")); + end + + try + fun = str2func(fusionAlgorithm); + if nargin > 4 + resultSegmentation = feval(fun, segmentations, varargin{:}); + else + resultSegmentation = feval(fun, segmentations); + end + + % Check if the resulting segmentation is already a close line. + % If not, use the specified method to close it. + if ~isSegmentationClosed(resultSegmentation, true) + fun = str2func(closingLineAlgorithm); + resultSegmentation = feval(fun, resultSegmentation, segmentations); + end + + % Fill and get the perimeter of the resulting segmentation + % to remove not needed internal lines. + segFill = imfill(resultSegmentation, "holes"); + resultSegmentation = bwperim(segFill); + catch ME + rethrow(ME); + end + end + + function [blackAndWhiteImg, segmentation, wrn] = processImage(~, img, dense, line) + % Check the parameters. + % If dense is true only external line can be used. + if dense && ~strcmp(line, "external") + throw(MException("TDSFT:processImage", "Dense object can only have external line")); + end + + % Check the channels of the image. + % If the image has more than one channel but is not an rgb image, use only the first channel. + % Use the wrn variable to warn the user about that. + if size(img,3) ~= 1 && size(img,3) ~= 3 + img = img(:,:,1); + wrn = true; + else + wrn = false; + end + + try + % Convert to 8 bit image. + cImg = imTo8bit(img); + + % Convert to grayscale. + grayImg = im2gray(cImg); + + % Convert to black and white. + blackAndWhiteImg = imbinarize(grayImg); + + % Convert to black background if needed. + % We want the background to be black and the segmentation to be white. + if getBinaryImageBackground(blackAndWhiteImg) + blackAndWhiteImg = ~blackAndWhiteImg; + end + + % If there are more than one object, select the largest one. + [~, numBlobs] = bwlabel(blackAndWhiteImg); + if numBlobs > 1 + blackAndWhiteImg = bwareafilt(blackAndWhiteImg, 1); + end + + % Check if the segmentation is an open line. + if ~isSegmentationClosed(blackAndWhiteImg, dense) + throw(MException("TDSFT:processImage", "Not closed segmentation uploaded")); + end + + % Check if the segmentation is empty. + if ~sum(blackAndWhiteImg(:)) + throw(MException("TDSFT:processImage", "Empty segmentation uploaded")); + end + + % Check if the segmentation is already of one pixel. + if isequal(blackAndWhiteImg, bwperim(blackAndWhiteImg)) + segmentation = blackAndWhiteImg; + else + % Get the one pixel segmentation. + segmentation = getOnePixelSegmentation(blackAndWhiteImg, line); + end + catch ME + rethrow(ME); + end + end + end +end \ No newline at end of file diff --git a/data/seg1.tif b/data/seg1.tif new file mode 100644 index 0000000..fdfa2fb Binary files /dev/null and b/data/seg1.tif differ diff --git a/data/seg2.tif b/data/seg2.tif new file mode 100644 index 0000000..8581e34 Binary files /dev/null and b/data/seg2.tif differ diff --git a/data/seg3.tif b/data/seg3.tif new file mode 100644 index 0000000..0441fd6 Binary files /dev/null and b/data/seg3.tif differ diff --git a/data/seg4.tif b/data/seg4.tif new file mode 100644 index 0000000..286351e Binary files /dev/null and b/data/seg4.tif differ diff --git a/gui/TDSFT.prj b/gui/TDSFT.prj new file mode 100644 index 0000000..4a04061 --- /dev/null +++ b/gui/TDSFT.prj @@ -0,0 +1,172 @@ + + + TDSFT + + + 1.0 + Lorenzo Drudi - Filippo Piccinini + lorenzodrudi11@gmail.com - filippo.piccinini85@gmail.com + University of Bologna + TDSFT: "Two-Dimensional Segmentation Fusion Tool" + TDSFT is an open-source tool that offers several algorithms (such as the famous STAPLE) to fuse multiple bi-dimensional segmentation. +See the documentation for more details. + + + /University_of_Bologna/TDSFT/ + option.installpath.user + + + In the following directions, replace MR/R2022b by the directory on the target machine where MATLAB is installed, or MR by the directory where the MATLAB Runtime is installed. + +(1) Set the environment variable XAPPLRESDIR to this value: + +MR/R2022b/X11/app-defaults + + +(2) If the environment variable LD_LIBRARY_PATH is undefined, set it to the following: + +MR/R2022b/runtime/glnxa64:MR/R2022b/bin/glnxa64:MR/R2022b/sys/os/glnxa64:MR/R2022b/sys/opengl/lib/glnxa64 + +If it is defined, set it to the following: + +${LD_LIBRARY_PATH}:MR/R2022b/runtime/glnxa64:MR/R2022b/bin/glnxa64:MR/R2022b/sys/os/glnxa64:MR/R2022b/sys/opengl/lib/glnxa64 + ${PROJECT_ROOT}/TDSFT/for_testing + ${PROJECT_ROOT}/TDSFT/for_redistribution_files_only + ${PROJECT_ROOT}/TDSFT/for_redistribution + ${PROJECT_ROOT}/TDSFT + false + + subtarget.standalone + + true + false + false + TDSFT_installer_web + MyAppInstaller_mcr + MyAppInstaller_app + false + false + log.txt + false + false + + Syntax + -? + + Input Arguments + -? print help on how to use the application + input arguments + Description + TDSFT is an open-source tool that offers several algorithms (such as the famous STAPLE) to fuse multiple bi-dimensional segmentation. +See the documentation for more details. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${PROJECT_ROOT}/app_main.mlapp + + + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/closingLineAlgorithms/closing_ChanVese.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/closingLineAlgorithms/closing_GeodesicActiveContour.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/closingLineAlgorithms/closing_Linear.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/closingLineAlgorithms/closing_NoClosing.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/closingLineAlgorithms/closing_ShapePreserving.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/closingLineAlgorithms/utils/closingWithInterpolation.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/getCentroid.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/getSegmentMiddlePoint.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/multiple/getAverageSegmentation.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/multiple/getCommonArea.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/multiple/getFilledSegmentations.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/multiple/getLargestArea.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/multiple/getSegmentationsMask.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/multiple/overlapSegmentations.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/single/getBinaryImageBackground.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/single/getNearestNonZeroPixel.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/single/getOnePixelSegmentation.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/single/imTo8bit.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/functions/segmentations/single/isSegmentationClosed.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_AverageSmallestAndLargest.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_AverageTargetFromInput.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_AverageTargetLargest.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_AverageTargetSmallest.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_Largest.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_Middle.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_STAPLE.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/fusion_Smallest.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/include/STAPLE.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/inputs/fusion_AverageTargetFromInput.json + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api/fusionAlgorithms/inputs/fusion_Middle.json + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/controller/Controller.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/controller/ControllerImpl.m + ${PROJECT_ROOT}/logo/icon_nobg.png + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/utils/Constants.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/utils/ImagesStoringMethods.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/utils/OnePixelLineTypes.m + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/utils/StringsUtils.m + + + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/api + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/data + + + ${PROJECT_ROOT}/app_about.mlapp + ${PROJECT_ROOT}/app_advancedFeatures.mlapp + ${PROJECT_ROOT}/app_image.mlapp + ${PROJECT_ROOT}/app_input.mlapp + ${PROJECT_ROOT}/app_result.mlapp + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/runStartup.m + + + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/gui/TDSFT/for_testing/splash.png + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/gui/TDSFT/for_testing/run_TDSFT.sh + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/gui/TDSFT/for_testing/readme.txt + /home/drudao/Desktop/University/BachelorThesis/Tool/TDSFT/gui/TDSFT/for_testing/TDSFT + + + + /home/drudao/MATLAB/R2022b + + + + true + false + false + false + false + false + true + false + 5.19.0-42-generic + false + true + glnxa64 + true + + + \ No newline at end of file diff --git a/gui/app_about.mlapp b/gui/app_about.mlapp new file mode 100644 index 0000000..7de158a Binary files /dev/null and b/gui/app_about.mlapp differ diff --git a/gui/app_advancedFeatures.mlapp b/gui/app_advancedFeatures.mlapp new file mode 100644 index 0000000..f549a01 Binary files /dev/null and b/gui/app_advancedFeatures.mlapp differ diff --git a/gui/app_image.mlapp b/gui/app_image.mlapp new file mode 100644 index 0000000..d5cd80f Binary files /dev/null and b/gui/app_image.mlapp differ diff --git a/gui/app_input.mlapp b/gui/app_input.mlapp new file mode 100644 index 0000000..3c7151c Binary files /dev/null and b/gui/app_input.mlapp differ diff --git a/gui/app_main.mlapp b/gui/app_main.mlapp new file mode 100644 index 0000000..c0be70a Binary files /dev/null and b/gui/app_main.mlapp differ diff --git a/gui/app_result.mlapp b/gui/app_result.mlapp new file mode 100644 index 0000000..1e70280 Binary files /dev/null and b/gui/app_result.mlapp differ diff --git a/gui/logo/icon_nobg.png b/gui/logo/icon_nobg.png new file mode 100644 index 0000000..40a81a3 Binary files /dev/null and b/gui/logo/icon_nobg.png differ diff --git a/gui/logo/logo_blackbg.svg b/gui/logo/logo_blackbg.svg new file mode 100644 index 0000000..6042bb0 --- /dev/null +++ b/gui/logo/logo_blackbg.svg @@ -0,0 +1 @@ + diff --git a/gui/logo/logo_nobg.svg b/gui/logo/logo_nobg.svg new file mode 100644 index 0000000..59b9396 --- /dev/null +++ b/gui/logo/logo_nobg.svg @@ -0,0 +1 @@ + diff --git a/runStartup.m b/runStartup.m new file mode 100644 index 0000000..7d31bb3 --- /dev/null +++ b/runStartup.m @@ -0,0 +1,16 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: March 17, 2023 +% NAME: TDSFT (version 1.0) +% +% DESCRIPTION: +% This function is used to add the folders of the project to the Matlab search path. +% It is necessary for running the project inside appdesigner. +function runStartup() + addpath( ... + ['.' filesep 'utils'], ... + ['.' filesep 'controller'], ... + ['.' filesep 'gui'], ... + ['.' filesep 'gui' filesep 'logo'], ... + ['.' filesep genpath('api')] ... + ); +end \ No newline at end of file diff --git a/template/closing_closingLineAlgorithmTemplate.m b/template/closing_closingLineAlgorithmTemplate.m new file mode 100644 index 0000000..979f27e --- /dev/null +++ b/template/closing_closingLineAlgorithmTemplate.m @@ -0,0 +1,23 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 24, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close line that needs to be closed. +% - inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% - res (Matrix: [height, width]): +% the resulting segmentation after the closing process. +% +% DESCRIPTION: +% Function template for the implementation of a closing line algorithm. +% Copy this file to the folder "api/closingLineAlgorithms" and rename it +% with the name of the algorithm you want to implement (the name has to start +% with "closing_"). Then, implement the function. +function res = closing_closingLineAlgorithmTemplate(fusionResult, inputSegmentations) + % Insert here your code +end \ No newline at end of file diff --git a/template/componentsOverview.json b/template/componentsOverview.json new file mode 100644 index 0000000..2251121 --- /dev/null +++ b/template/componentsOverview.json @@ -0,0 +1,35 @@ +{ + "inputs": [ + { + "name": "Test DropDown", + "type": "DropDown", + "value": [ + "Value 1", + "Value 2" + ], + "help": "This is a help text for the dropdown" + }, + { + "name": "Test Check", + "type": "Check", + "help": "This is a help text for the check" + }, + { + "name": "Test Text", + "type": "Text", + "help": "This is a help text for the text" + }, + { + "name": "Target Segmentation Index", + "type": "InputSegmentationsSelector", + "help": "This is a help text for the text" + }, + { + "name": "Test Numeric Text", + "type": "Number", + "value": 1, + "limits": [1, 5], + "help": "This is a help text for the text" + } + ] +} diff --git a/template/fusion_fusionAlgorithmTemplate.m b/template/fusion_fusionAlgorithmTemplate.m new file mode 100644 index 0000000..c4c31c3 --- /dev/null +++ b/template/fusion_fusionAlgorithmTemplate.m @@ -0,0 +1,25 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 24, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% - segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% +% OUTPUT: +% - fusionResult (matrix [height, width]): +% the resulting segmentation. +% +% DESCRIPTION: +% Function template for the implementation of a fusion algorithm. +% Copy this file to the folder "api/fusionAlgorithms" and rename it +% with the name of the algorithm you want to implement (the name has to start +% with "fusion_"). Then, implement the function. +% +% If you need to add some parameters to the function, you can add them +% in the function definition (e.g. fusion_fusionAlgorithmTemplate(segmentations, arg1, arg2)). +% If you do so, remember to create also the input file inside the folder "api/fusionAlgorithms/input". +% See the documentation for more details. +function fusionResult = fusion_fusionAlgorithmTemplate(segmentations) + % Insert here your code +end diff --git a/test/utils/StringsUtilsTest.m b/test/utils/StringsUtilsTest.m new file mode 100644 index 0000000..959264a --- /dev/null +++ b/test/utils/StringsUtilsTest.m @@ -0,0 +1,53 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 19, 2023 +% NAME: TDSFT (version 1.0) +% +% DESCRIPTION: +% Test class for `StringsUtils`. +classdef StringsUtilsTest < matlab.unittest.TestCase + properties (TestParameter) + camelCaseAlgorithmName = {"AverageTargetLargest"}; % Camel case algorithm name + algorithmFullName = {"fusion_AverageTargetLargest"}; % Algorithm full name + spacedAlgorithmName = {"Average Target Largest"}; % Spaced algorithm name + + camelCaseClosingName = {"ChanVese"}; % Camel case closing algorithm name + closingFullName = {"closing_ChanVese"}; % Closing algorithm full name + spacedClosingName = {"Chan Vese"}; % Spaced closing algorithm name + + fileName = {"fusion_AverageTargetLargest.m"}; % File name + fileNameWithoutExtension = {"fusion_AverageTargetLargest"}; % File name without extension + end + + methods (Test) + % Test function fromCamelCaseToFusionAlgorithmFullName + function fromCamelCaseToFusionAlgorithmFullNameTest(testCase, camelCaseAlgorithmName, algorithmFullName) + funRes = StringsUtils.fromCamelCaseToFusionAlgorithmFullName(camelCaseAlgorithmName); + testCase.verifyEqual(funRes, algorithmFullName); + end + + % Test function fromCamelCaseToSpacedString + function fromCamelCaseToSpacedStringTest(testCase, camelCaseAlgorithmName, spacedAlgorithmName) + funRes = StringsUtils.fromCamelCaseToSpacedString(camelCaseAlgorithmName); + testCase.verifyEqual(funRes, spacedAlgorithmName); + end + + % Test function fromSpacedToClosingAlgorithmFullName + function fromSpacedToClosingAlgorithmFullNameTest(testCase, spacedClosingName, closingFullName) + funRes = StringsUtils.fromSpacedToClosingAlgorithmFullName(spacedClosingName); + testCase.verifyEqual(funRes, closingFullName); + end + + % Test function fromSpacedToFusionAlgorithmFullName + function fromSpacedToFusionAlgorithmFullNameTest(testCase, spacedAlgorithmName, algorithmFullName) + funRes = StringsUtils.fromSpacedToFusionAlgorithmFullName(spacedAlgorithmName); + testCase.verifyEqual(funRes, algorithmFullName); + end + + % Test function fromSpacedToFusionAlgorithmFullName + function removeFileExtensionTest(testCase, fileName, fileNameWithoutExtension) + funRes = StringsUtils.removeFileExtension(fileName); + testCase.verifyEqual(funRes, fileNameWithoutExtension); + end + end + +end \ No newline at end of file diff --git a/test/utils/api/functions/segmentations/SingleSegmentationTest.m b/test/utils/api/functions/segmentations/SingleSegmentationTest.m new file mode 100644 index 0000000..fc0689e --- /dev/null +++ b/test/utils/api/functions/segmentations/SingleSegmentationTest.m @@ -0,0 +1,98 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 19, 2023 +% NAME: TDSFT (version 1.0) +% +% DESCRIPTION: +% Test class for functions operating on single segmentations. +classdef SingleSegmentationTest < matlab.unittest.TestCase + properties (Constant) + height = 100; % Image height. + width = 100; % Image width. + end + + properties (TestParameter) + % 8-bit image. + image_8bit = { (power(2, 8) - 1) * ones(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8") }; + % 12-bit image. + image_12bit = { (power(2, 12) - 1) * ones(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8") }; + % 16-bit image. + image_16bit = { (power(2, 16) - 1) * ones(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8") }; + % 32-bit image. + image_32bit = { (power(2, 32) - 1) * ones(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8") }; + % 64-bit image. + image_64bit = { (power(2, 64) - 1) * ones(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8") }; + % Binary image. + binaryImage = { zeros(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8") }; + end + + methods (Test) + % Test imTo8bit with 8-bit image. + function imTo8bitTest_8bitimage(testCase, image_8bit) + % Test that imTo8bit returns an 8-bit image. + funRes = imTo8bit(image_8bit); + testCase.verifyClass(funRes, "uint8") + end + + % Test imTo8bit with 12-bit image. + function imTo8bitTest_12bitimage(testCase, image_12bit) + % Test that imTo8bit returns an 8-bit image. + funRes = imTo8bit(image_12bit); + testCase.verifyClass(funRes, "uint8") + end + + % Test imTo8bit with 16-bit image. + function imTo8bitTest_16bitimage(testCase, image_16bit) + % Test that imTo8bit returns an 8-bit image. + funRes = imTo8bit(image_16bit); + testCase.verifyClass(funRes, "uint8") + end + + % Test imTo8bit with 32-bit image. + function imTo8bitTest_32bitimage(testCase, image_32bit) + % Test that imTo8bit returns an 8-bit image. + funRes = imTo8bit(image_32bit); + testCase.verifyClass(funRes, "uint8") + end + + % Test imTo8bit with 64-bit image. + function imTo8bitTest_64bitimage(testCase, image_64bit) + % Test that imTo8bit returns an 8-bit image. + funRes = imTo8bit(image_64bit); + testCase.verifyClass(funRes, "uint8") + end + + % Test getBinaryImageBackground with a binary image. + function getBinaryImageBackgroundTest(testCase, binaryImage) + funRes = getBinaryImageBackground(binaryImage); + testCase.verifyFalse(funRes) + + binaryImage(1, 1) = 1; + funRes = getBinaryImageBackground(binaryImage); + testCase.verifyTrue(funRes) + end + + % Test getOnePixelSegmentation. + function getOnePixelSegmentationTest(testCase) + image = zeros(SingleSegmentationTest.height,... + SingleSegmentationTest.width, "uint8"); + + % create a 10*10 square with a border of 3-pixel in the middle of the image + image(45:55, 45:55) = 1; + binaryImageThreePixel = image; + binaryImageThreePixel(48:52, 48:52) = 0; + + % get the 1-pixel external perimeter + binaryImageOnePixelExternal = binaryImageThreePixel; + binaryImageOnePixelExternal(46:54, 46:54) = 0; + + funRes = getOnePixelSegmentation(binaryImageThreePixel, OnePixelLineTypes.EXTERNAL); + testCase.verifyEqual(uint8(funRes), binaryImageOnePixelExternal) + end + end +end \ No newline at end of file diff --git a/utils/Constants.m b/utils/Constants.m new file mode 100644 index 0000000..f47eeb6 --- /dev/null +++ b/utils/Constants.m @@ -0,0 +1,21 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 19, 2023 +% NAME: TDSFT (version 1.0) +% +% DESCRIPTION: +% Class containing the constants used in the project. +classdef Constants + properties (Constant) + % The root of the algorithm names. + ALGORITHM_NAMES_FILE_ROOT = "fusion_"; + + % The root of the closing algorithm names. + CLOSING_NAMES_FILE_ROOT = "closing_"; + + % The extension of source files. + SRC_EXTENSION = ".m"; + + % The file extension for the input files. + INPUT_FILE_EXTENSION = ".json"; + end +end \ No newline at end of file diff --git a/utils/ImagesStoringMethods.m b/utils/ImagesStoringMethods.m new file mode 100644 index 0000000..c2e139f --- /dev/null +++ b/utils/ImagesStoringMethods.m @@ -0,0 +1,34 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 20, 2023 +% NAME: TDSFT (version 1.0) +% +% Enumeration class for image storing methods. +% Used to be able to use pretty names for +% the different storing methods. +classdef ImagesStoringMethods + properties + string % string representation of the storing method + end + + methods + function storing_method = ImagesStoringMethods(s) + % Constructor + % + % Parameters: + % s:string representing the type of the image + % Return: + % the image storing method + + storing_method.string = s; + end + end + + % Enumeration values with their corresponding string. + enumeration + INT_8 ("uint8") % 8-bit image + INT_16 ("uint16") % 16-bit image + INT_32 ("uint32") % 32-bit image + INT_64 ("uint64") % 64-bit image + end +end + diff --git a/utils/OnePixelLineTypes.m b/utils/OnePixelLineTypes.m new file mode 100644 index 0000000..bd985fb --- /dev/null +++ b/utils/OnePixelLineTypes.m @@ -0,0 +1,34 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% Enumeration class for one-pixel line types. +% Used to specify which one-pixel line the user +% wants to use if the segmentation is of more than one pixel. +classdef OnePixelLineTypes + properties + string % String representation of the line type + end + + methods + + function line_type = OnePixelLineTypes(s) + % Constructor + % + % Parameters: + % s: string representing the type of the image + % Return: + % the one pixel line type + + line_type.string = s; + end + end + + % Enumeration values with their corresponding string. + enumeration + EXTERNAL ('external') % External line + MIDDLE ('middle') % Middle line + INTERNAL ('internal') % Internal line + end +end + diff --git a/utils/StringsUtils.m b/utils/StringsUtils.m new file mode 100644 index 0000000..3a94410 --- /dev/null +++ b/utils/StringsUtils.m @@ -0,0 +1,115 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 19, 2023 +% NAME: TDSFT (version 1.0) +% +% Utils class for strings. +classdef StringsUtils + methods (Static) + + function fullName = fromCamelCaseToFusionAlgorithmFullName(algorithm) + % Get the full name of the camel case in. + % Fullname is needed in the standalone version to recognize algorithm files. + % Example: 'fusion_' + 'AverageTargetLargest' = 'fusion_AverageTargetLargest' + % + % Parameters: + % - algorithm (string): + % an algorithm name (already in camelcase). + % (e.g. 'AverageTargetLargest'). + % + % Output: + % - fullName (string): + % fullName of the algorithm. + % (e.g. 'fusion_AverageTargetLargest'). + + fullName = strcat(Constants.ALGORITHM_NAMES_FILE_ROOT, algorithm); + end + + function result = fromCamelCaseToSpacedString(s) + % Convert from camelcase string to spaced string. + % Example: 'ThisIsTheExampleString' = 'This Is The Example String'. + % + % Parameters: + % - s (string): + % the camel case string. + % (e.g. 'ThisIsACamelCaseString'). + % + % Output: + % - result (string): + % the spaced string. + % (e.g. 'This Is A Spaced String'). + + result = regexprep(s, "([a-z])([A-Z])", "$1 $2"); + end + + function result = fromSpacedToCamelCaseString(s) + % Convert from spaced string to camelcase string. + % Example: 'This Is The Example String' = 'ThisIsTheExampleString'. + % + % Parameters: + % - s (string): + % string with spaces. + % (e.g. 'This Is A String With Spaces'). + % + % Output: + % - result (string): + % the string where spaces are replaced by camel case separation. + % (e.g. 'ThisIsACamelCaseString'). + + result = regexprep(s, "([a-z]) ([A-Z])", "$1$2"); + end + + function result = fromSpacedToClosingAlgorithmFullName(s) + % Convert from spaced string to closing algorithm fullname. + % Example: 'Chan Vese' = 'closing_ChanVese'. + % + % Parameters: + % - s (string): + % the spaced string. + % (e.g. 'Chan Vese'). + % + % Output: + % - result (string): + % the camelcase fullname. + % (e.g. 'closing_ChanVese'). + + result = StringsUtils.fromSpacedToCamelCaseString(s); + result = strcat(Constants.CLOSING_NAMES_FILE_ROOT, result); + end + + function fullName = fromSpacedToFusionAlgorithmFullName(algorithm) + % Get, from a spaced algorithm name, its full name. + % Example: 'Average Target Largest' = 'fusion_AverageTargetLargest'. + % + % Parameters: + % - algorithm (string): + % an algorithm spaced name. + % (e.g. 'Average Target Largest'). + % + % Output: + % - fullName (string): + % fullName of the input algorithm. + % (e.g. 'fusion_AverageTargetLargest'). + + fullName = StringsUtils.fromSpacedToCamelCaseString(algorithm); + fullName = StringsUtils.fromCamelCaseToFusionAlgorithmFullName(fullName); + end + + function result = removeFileExtension(filename) + % Returns the filename without the extension. + % Example: 'filename.m' = 'filename'. + % + % Parameters: + % - filename (string): + % a filename. + % + % Output: + % - result (string): + % the filename without the extension. + + % Regex expression, matches everything before the last dot. + expression = ".*(?=\.)"; + result = regexpi(filename, expression, "match"); + result = string(result); + end + end +end \ No newline at end of file