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/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