AFM-SPM / TopoStats

An AFM image analysis program to batch process data and obtain statistics from images
https://afm-spm.github.io/TopoStats/
GNU Lesser General Public License v3.0
55 stars 10 forks source link

[feature] : Remove smallest_grain_size_nm2 as a parameter from config (superfluous) #805

Open SylviaWhittle opened 4 months ago

SylviaWhittle commented 4 months ago

Is your feature request related to a problem?

The smallest_grain_size_nm2 parameter of the grains section of the config is superfluous, as absolute_area_threshold covers this and has understandably recently caused confusion within a group of users.

It previously hadn't been removed since it was doing no harm, however we now have reason to action this given the aforementioned confusion.

Describe the solution you would like.

Remove smallest_grain_size_nm2 and allow the area thresholding step to take on the responsibility of removing very small objects from images, as it would already do if the noise wasn't already removed by the remove noise function.

Describe the alternatives you have considered.

Keeping it in would likely cause continued confusion in the user-base.

Additional context

No response

ns-rse commented 3 months ago

Remove from the following files...

The main work required is in topostats/grains.py where smallest_grain_size_nm2 is used in a few places...

Grains.remove_noise()

Defined on line 151 this method uses the value directly as an argument to skimage.morphology.remove_small_objects().

The method is only ever defined and is not used anywhere so can be safely removed. There is no test for this method in tests/test_grains.py.

Lower threshold for area thresholding.

Line 324

            self.directions[direction]["removed_noise"] = self.area_thresholding(
                self.directions[direction]["tidied_border"],
                [self.smallest_grain_size_nm2, None],
            )

I propose replacing the lower area threshold (self.smallest_grain_size_nm2) with self.minimum_grain_size / (self.pixel_to_nm_scaling) ** 2.

            # Minimum area is based on minimum
            self.directions[direction]["removed_noise"] = self.area_thresholding(
                self.directions[direction]["tidied_border"],
                # Minimum area is based on minimum_grain_size (median - 1.5 * Interquartile range) scaled to nm^2
                [self.minimum_grain_size / (self.pixel_to_nm_scaling) ** 2, None],
            )

However this introduces a circular problem, currently the Grains.calc_minimum_grain_size() is only calculated after Grains.area_thresholding() has been used to remove noise (and saved to Grains.directions[directon]["removed_noise"]

This introduces a chicken and egg situation.

Could make two calls to Grains.calc_minimum_grain_size() the first with all noise to set Grains.minimum_grain_size attribute which is calculated as note above the Median grains size minus 1.5 * Inter-Quartile Range, and then a second time recalculating the Grains.minimum_grain_size after the removal of items below the first threshold.

Thoughts and comments welcome /cc @SylviaWhittle @Jean-Du @MaxGamill-Sheffield @llwiggins