fluoscenepy


Namefluoscenepy JSON
Version 0.0.3 PyPI version JSON
download
home_pageNone
SummarySimulation of a microscopic image with round (beads) and elongated fluorescent objects
upload_time2025-02-02 17:40:44
maintainerNone
docs_urlNone
authorSergei Klykov
requires_python>=3.8
licenseMIT
keywords fluorescent microscopic image simulation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 'fluoscenepy' Project

The 'fluoscenepy' project is designed to simulate microscopic images featuring basic structures such as beads and ellipses, 
calculated using various computational approaches.  
While this may sound ambitious, please consider it as an earnest attempt to provide a useful tool for diverse applications, such 
as evaluating image processing workflows, rather than a fully established or peer-reviewed solution. It is a work in progress, 
intended to support experimental analyses rather than to represent a finalized, validated methodology.

### Rationale for Project Development
Although there are numerous similar and more advanced projects that address the task of simulating fluorescence microscopy 
images, and bead simulation may seem trivial, I have not encountered an appropriate library capable of accurately simulating 
the precise projection of a bead (or circle) onto a pixel grid.   
Specifically, projecting a circle (or "bead") with a radius of 1 pixel, perfectly centered on a pixel (e.g., at coordinates (1, 1)),
presents some challenges. This seemingly simple task can result in various projection outcomes on the pixel grid, depending on the 
approach used:

1) "Normal" Circle. The 4 pixels (on 90 degree directions) on the borders are included because 
the distance between them and the center of the circle is precisely 1 pixel and equal to the radius.   
   
![Normal Circle](./src/fluoscenepy/readme_images/Circle_rad_1px.png "Normal Circle 1px R")    

```python
# Python code snippet
from fluoscenepy import FluorObj
flobj = FluorObj(typical_size=2.0, border_type="computed", shape_method="circle")
flobj.get_shape(); flobj.plot_shape()
```
   
2) "Oversampled" Circle. All pixels within the circle border are included into the projection with the maximum 
(though normalized) intensity.

![Oversampled Circle](./src/fluoscenepy/readme_images/Oversampled_Circle_rad_1px.png "Oversampled Circle 1px R")     

The code snippet is the same as for the "normal" circle above, only the parameter should be set as: 
***shape_method="oversampled circle"***.

3) "Undersampled" Circle. Only pixels, that lay completely within the border, are included into the projection.

![Undersampled Circle](./src/fluoscenepy/readme_images/Undersampled_Circle_rad_1px.png "Undersampled Circle 1px R")  

The code snippet is the same as for the "normal" circle above, only the parameter should be set as: 
***shape_method="undersampled circle"***.   

Intuitively, the problem can be solved either by calculation the area of intersection of each pixel with the circle
border, or by using some bell-shaped analytic function for the shape description 
([more information](https://en.wikipedia.org/wiki/Bell-shaped_function) on these functions).   
To illustrate this, the following shapes could be plotted: 
1) The shape with calculated areas of the intersection of each pixel with the circular border ("Precise" Circle):      

![Precise Circle 2](./src/fluoscenepy/readme_images/Precise_bordered_circle_rad_1px.png "Precise Circle 1px R")   

The normalized intensity values in the pixels, which intersect with the circular border, is calculated from the ratio
of occupied area laying within the circular border, as on the following picture (the left center pixel):     

![Intersection](./src/fluoscenepy/readme_images/Intersection_Circle_rad_1px.png "Precise Circle 1px R")   

2) To illustrate better the effect of area intersections calculation, the shape of the bead with diameter of 4.8 pixels:      

![Precise Circle 4.8](./src/fluoscenepy/readme_images/Precise_bordered_circle_rad_4.8px.png "Precise Circle 4.8px R")
```python
from fluoscenepy import FluorObj
flobj = FluorObj(typical_size=4.8); flobj.get_shape(); flobj.plot_shape()
```

3) The "continuously" shaped bead can be calculated using implemented in the ***FluorObj*** bell-shaped 
functions, e.g. gaussian, lorentzian, and so on (full list can be printed out by calling the
***get_shaping_functions()*** method). Note that the calculation can be performed only for the parameter 
set as: ***border_type='computed'*** or ***border_type='co'***. For the illustration of the calculated
shape:    

![Bump3 Circle 4.8](./src/fluoscenepy/readme_images/Bump3_computed_circle_rad_4.8px.png "Bump3 Circle 4.8px R")
```python
from fluoscenepy import FluorObj
flobj = FluorObj(typical_size=4.8, border_type="co", shape_method="bump3")
flobj.get_shape(); flobj.plot_shape()
```

The problem of precise shape projection of the circle on the pixel grid becomes even more significant 
if its center is shifted from the origin of the pixel. To illustrate this, below are a few examples of the shifted by (0.24, 0.0)
circles.   
Shifted "Normal" Circle:    

![Normal Circle](./src/fluoscenepy/readme_images/Circle_rad_1px_shift(0.24,0).png "Shifted Normal Circle 1px R")

Shifted "Precise" Circle:   

![Shifted Precise Circle](./src/fluoscenepy/readme_images/Precise_bordered_circle_rad_1px_shift(0.24,0).png "Shifted Precise Circle 1px R") 

### Generation of a microscopic image ("fluorescence scene")
It can be achieved by placing circular of elliptical particles on the "scene". Check the API documentation for all 
available methods for making it. 
One of the straightforward way is just to use methods for generation of objects with random shapes, sizes, maximum intensities, and 
placed randomly on the scene. The code example: 
```python
from fluoscenepy import FluorObj, UscopeScene
samples = UscopeScene.get_random_objects(mean_size=(9.11, 6.56), size_std=(1.15, 0.82), 
                                         shapes='mixed', intensity_range=(185, 252), 
                                         n_objects=12, verbose_info=True)
scene = UscopeScene(width=62, height=54)
samples_pl = scene.set_random_places(samples, overlapping=False, touching=False, 
                                     only_within_scene=True, verbose_info=True)
# Placing objects randomly on the scene, without noise
scene.put_objects_on(samples_pl, save_only_objects_inside=True)
scene.add_noise()  # adding standard noise
```
For comparison, generated scene without standard for CMOS cameras additional noise:     

![Scene w/t noise](./src/fluoscenepy/readme_images/Scene_without_noise_1.png "Scene without Noise")

Generated scene with additional noise calculated with default method parameters:    

![Scene w/t noise](./src/fluoscenepy/readme_images/Scene_with_noise_1.png "Scene without Noise")

### Performance of calculations
Note that even single 'precise' shaped round object (bead) generation can take around 2 seconds for the diameter 12 pixels
because of the slow nested for loops for calculating each pixel which is partially within the circle border.   
To speed up the calculations, one can install the [numba](https://numba.pydata.org/) library in the same Python environment
and provide the according flags in calculation methods, similar to the following code snippets.    
**PLEASE NOTE:** it has been revealed during tests that the required ***numba*** version should be **>=0.57.1** 
(tested and verified for versions: 0.57.1 and 0.60.0).
````python
import numpy as np
from fluoscenepy import FluorObj, force_precompilation
force_precompilation()   # force pre-compilation of computational functions by numba
# Round shape object generation
r_obj_acc = FluorObj(typical_size=12.0)
r_obj_acc.get_shape(accelerated=True)  # takes ~ 0.7 - 1 sec 
r_obj = FluorObj(typical_size=12.0)
r_obj.get_shape()  # takes ~ 2.3 - 2.7 sec 
# Ellipse shape object generation
el_obj_acc = FluorObj(shape_type='ellipse', typical_size=(7.5, 6.0, np.pi/3))
el_obj_acc.get_shape(accelerated=True)  # takes ~ 1.1 - 1.8 sec 
el_obj = FluorObj(shape_type='ellipse', typical_size=(7.5, 6.0, np.pi/3))
el_obj.get_shape()  # takes ~ 3.6 - 5.7 sec 
````

### Acceleration of objects generation and placing 
The objects with randomly selected shape type, sizes, center pixel shifts and orientation in the case of ellipse can 
be generated by using class instance bound method:
````python
import numpy as np
from fluoscenepy import FluorObj, UscopeScene
uscene = UscopeScene(width=320, height=280, image_type=np.uint16)  # creating the scene
# Pixel and intensity ranges for random selection the parameters from
obj_mean_sizes = (16, 12); obj_sizes_std = (5, 3.5); obj_intensities = (28000, 42000)
# Instance bound compiled by numba library generation method with verbose printouts about calculation progress
fl_objs = uscene.get_objects_acc(mean_size=obj_mean_sizes, size_std=obj_sizes_std, shapes='mixed', 
                                  intensity_range=obj_intensities, image_type=uscene.img_type, 
                                  n_objects=25, verbose_info=True)
# Distribute the generated objects according to the provided flags (hopefully, with self-explanatory meaning).
# Note that acceleration by numba compilation will be automatically applied for below method if the library 'numba'
# has been installed. Recommended version for numba is >= 0.57.1
placed_objs = uscene.set_random_places(fl_objs, overlapping=False, touching=False, 
                                       only_within_scene=True, verbose_info=True)
uscene.put_objects_on(placed_objs, save_only_objects_inside=True); uscene.show_scene()
````
Please note that performance is still limited by the following factors:
1) Object Profile Intensity Calculation:   
The function requires precise calculation of the object's profile intensity distribution across the pixel grid. 
This process can be particularly time-consuming for objects with an elliptical shape, taking up to several dozen seconds 
for a single object generation.
2) Object Placement Algorithms:
When both the 'overlapping' and 'touching' flags are set to False, the algorithm performs pixel-wise checks 
to ensure that no two objects overlap or touch during random placement. For relatively large objects, this 
verification can significantly increase processing time, potentially taking several minutes for the placement 
of a single object.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "fluoscenepy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "fluorescent microscopic image simulation",
    "author": "Sergei Klykov",
    "author_email": "sergej.klykow@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/16/4e/3a5bda4c6dbdae3be73f90122d2f21cc8c901494b7c2749c266dad442f69/fluoscenepy-0.0.3.tar.gz",
    "platform": null,
    "description": "# 'fluoscenepy' Project\r\n\r\nThe 'fluoscenepy' project is designed to simulate microscopic images featuring basic structures such as beads and ellipses, \r\ncalculated using various computational approaches.  \r\nWhile this may sound ambitious, please consider it as an earnest attempt to provide a useful tool for diverse applications, such \r\nas evaluating image processing workflows, rather than a fully established or peer-reviewed solution. It is a work in progress, \r\nintended to support experimental analyses rather than to represent a finalized, validated methodology.\r\n\r\n### Rationale for Project Development\r\nAlthough there are numerous similar and more advanced projects that address the task of simulating fluorescence microscopy \r\nimages, and bead simulation may seem trivial, I have not encountered an appropriate library capable of accurately simulating \r\nthe precise projection of a bead (or circle) onto a pixel grid.   \r\nSpecifically, projecting a circle (or \"bead\") with a radius of 1 pixel, perfectly centered on a pixel (e.g., at coordinates (1, 1)),\r\npresents some challenges. This seemingly simple task can result in various projection outcomes on the pixel grid, depending on the \r\napproach used:\r\n\r\n1) \"Normal\" Circle. The 4 pixels (on 90 degree directions) on the borders are included because \r\nthe distance between them and the center of the circle is precisely 1 pixel and equal to the radius.   \r\n   \r\n![Normal Circle](./src/fluoscenepy/readme_images/Circle_rad_1px.png \"Normal Circle 1px R\")    \r\n\r\n```python\r\n# Python code snippet\r\nfrom fluoscenepy import FluorObj\r\nflobj = FluorObj(typical_size=2.0, border_type=\"computed\", shape_method=\"circle\")\r\nflobj.get_shape(); flobj.plot_shape()\r\n```\r\n   \r\n2) \"Oversampled\" Circle. All pixels within the circle border are included into the projection with the maximum \r\n(though normalized) intensity.\r\n\r\n![Oversampled Circle](./src/fluoscenepy/readme_images/Oversampled_Circle_rad_1px.png \"Oversampled Circle 1px R\")     \r\n\r\nThe code snippet is the same as for the \"normal\" circle above, only the parameter should be set as: \r\n***shape_method=\"oversampled circle\"***.\r\n\r\n3) \"Undersampled\" Circle. Only pixels, that lay completely within the border, are included into the projection.\r\n\r\n![Undersampled Circle](./src/fluoscenepy/readme_images/Undersampled_Circle_rad_1px.png \"Undersampled Circle 1px R\")  \r\n\r\nThe code snippet is the same as for the \"normal\" circle above, only the parameter should be set as: \r\n***shape_method=\"undersampled circle\"***.   \r\n\r\nIntuitively, the problem can be solved either by calculation the area of intersection of each pixel with the circle\r\nborder, or by using some bell-shaped analytic function for the shape description \r\n([more information](https://en.wikipedia.org/wiki/Bell-shaped_function) on these functions).   \r\nTo illustrate this, the following shapes could be plotted: \r\n1) The shape with calculated areas of the intersection of each pixel with the circular border (\"Precise\" Circle):      \r\n\r\n![Precise Circle 2](./src/fluoscenepy/readme_images/Precise_bordered_circle_rad_1px.png \"Precise Circle 1px R\")   \r\n\r\nThe normalized intensity values in the pixels, which intersect with the circular border, is calculated from the ratio\r\nof occupied area laying within the circular border, as on the following picture (the left center pixel):     \r\n\r\n![Intersection](./src/fluoscenepy/readme_images/Intersection_Circle_rad_1px.png \"Precise Circle 1px R\")   \r\n\r\n2) To illustrate better the effect of area intersections calculation, the shape of the bead with diameter of 4.8 pixels:      \r\n\r\n![Precise Circle 4.8](./src/fluoscenepy/readme_images/Precise_bordered_circle_rad_4.8px.png \"Precise Circle 4.8px R\")\r\n```python\r\nfrom fluoscenepy import FluorObj\r\nflobj = FluorObj(typical_size=4.8); flobj.get_shape(); flobj.plot_shape()\r\n```\r\n\r\n3) The \"continuously\" shaped bead can be calculated using implemented in the ***FluorObj*** bell-shaped \r\nfunctions, e.g. gaussian, lorentzian, and so on (full list can be printed out by calling the\r\n***get_shaping_functions()*** method). Note that the calculation can be performed only for the parameter \r\nset as: ***border_type='computed'*** or ***border_type='co'***. For the illustration of the calculated\r\nshape:    \r\n\r\n![Bump3 Circle 4.8](./src/fluoscenepy/readme_images/Bump3_computed_circle_rad_4.8px.png \"Bump3 Circle 4.8px R\")\r\n```python\r\nfrom fluoscenepy import FluorObj\r\nflobj = FluorObj(typical_size=4.8, border_type=\"co\", shape_method=\"bump3\")\r\nflobj.get_shape(); flobj.plot_shape()\r\n```\r\n\r\nThe problem of precise shape projection of the circle on the pixel grid becomes even more significant \r\nif its center is shifted from the origin of the pixel. To illustrate this, below are a few examples of the shifted by (0.24, 0.0)\r\ncircles.   \r\nShifted \"Normal\" Circle:    \r\n\r\n![Normal Circle](./src/fluoscenepy/readme_images/Circle_rad_1px_shift(0.24,0).png \"Shifted Normal Circle 1px R\")\r\n\r\nShifted \"Precise\" Circle:   \r\n\r\n![Shifted Precise Circle](./src/fluoscenepy/readme_images/Precise_bordered_circle_rad_1px_shift(0.24,0).png \"Shifted Precise Circle 1px R\") \r\n\r\n### Generation of a microscopic image (\"fluorescence scene\")\r\nIt can be achieved by placing circular of elliptical particles on the \"scene\". Check the API documentation for all \r\navailable methods for making it. \r\nOne of the straightforward way is just to use methods for generation of objects with random shapes, sizes, maximum intensities, and \r\nplaced randomly on the scene. The code example: \r\n```python\r\nfrom fluoscenepy import FluorObj, UscopeScene\r\nsamples = UscopeScene.get_random_objects(mean_size=(9.11, 6.56), size_std=(1.15, 0.82), \r\n                                         shapes='mixed', intensity_range=(185, 252), \r\n                                         n_objects=12, verbose_info=True)\r\nscene = UscopeScene(width=62, height=54)\r\nsamples_pl = scene.set_random_places(samples, overlapping=False, touching=False, \r\n                                     only_within_scene=True, verbose_info=True)\r\n# Placing objects randomly on the scene, without noise\r\nscene.put_objects_on(samples_pl, save_only_objects_inside=True)\r\nscene.add_noise()  # adding standard noise\r\n```\r\nFor comparison, generated scene without standard for CMOS cameras additional noise:     \r\n\r\n![Scene w/t noise](./src/fluoscenepy/readme_images/Scene_without_noise_1.png \"Scene without Noise\")\r\n\r\nGenerated scene with additional noise calculated with default method parameters:    \r\n\r\n![Scene w/t noise](./src/fluoscenepy/readme_images/Scene_with_noise_1.png \"Scene without Noise\")\r\n\r\n### Performance of calculations\r\nNote that even single 'precise' shaped round object (bead) generation can take around 2 seconds for the diameter 12 pixels\r\nbecause of the slow nested for loops for calculating each pixel which is partially within the circle border.   \r\nTo speed up the calculations, one can install the [numba](https://numba.pydata.org/) library in the same Python environment\r\nand provide the according flags in calculation methods, similar to the following code snippets.    \r\n**PLEASE NOTE:** it has been revealed during tests that the required ***numba*** version should be **>=0.57.1** \r\n(tested and verified for versions: 0.57.1 and 0.60.0).\r\n````python\r\nimport numpy as np\r\nfrom fluoscenepy import FluorObj, force_precompilation\r\nforce_precompilation()   # force pre-compilation of computational functions by numba\r\n# Round shape object generation\r\nr_obj_acc = FluorObj(typical_size=12.0)\r\nr_obj_acc.get_shape(accelerated=True)  # takes ~ 0.7 - 1 sec \r\nr_obj = FluorObj(typical_size=12.0)\r\nr_obj.get_shape()  # takes ~ 2.3 - 2.7 sec \r\n# Ellipse shape object generation\r\nel_obj_acc = FluorObj(shape_type='ellipse', typical_size=(7.5, 6.0, np.pi/3))\r\nel_obj_acc.get_shape(accelerated=True)  # takes ~ 1.1 - 1.8 sec \r\nel_obj = FluorObj(shape_type='ellipse', typical_size=(7.5, 6.0, np.pi/3))\r\nel_obj.get_shape()  # takes ~ 3.6 - 5.7 sec \r\n````\r\n\r\n### Acceleration of objects generation and placing \r\nThe objects with randomly selected shape type, sizes, center pixel shifts and orientation in the case of ellipse can \r\nbe generated by using class instance bound method:\r\n````python\r\nimport numpy as np\r\nfrom fluoscenepy import FluorObj, UscopeScene\r\nuscene = UscopeScene(width=320, height=280, image_type=np.uint16)  # creating the scene\r\n# Pixel and intensity ranges for random selection the parameters from\r\nobj_mean_sizes = (16, 12); obj_sizes_std = (5, 3.5); obj_intensities = (28000, 42000)\r\n# Instance bound compiled by numba library generation method with verbose printouts about calculation progress\r\nfl_objs = uscene.get_objects_acc(mean_size=obj_mean_sizes, size_std=obj_sizes_std, shapes='mixed', \r\n                                  intensity_range=obj_intensities, image_type=uscene.img_type, \r\n                                  n_objects=25, verbose_info=True)\r\n# Distribute the generated objects according to the provided flags (hopefully, with self-explanatory meaning).\r\n# Note that acceleration by numba compilation will be automatically applied for below method if the library 'numba'\r\n# has been installed. Recommended version for numba is >= 0.57.1\r\nplaced_objs = uscene.set_random_places(fl_objs, overlapping=False, touching=False, \r\n                                       only_within_scene=True, verbose_info=True)\r\nuscene.put_objects_on(placed_objs, save_only_objects_inside=True); uscene.show_scene()\r\n````\r\nPlease note that performance is still limited by the following factors:\r\n1) Object Profile Intensity Calculation:   \r\nThe function requires precise calculation of the object's profile intensity distribution across the pixel grid. \r\nThis process can be particularly time-consuming for objects with an elliptical shape, taking up to several dozen seconds \r\nfor a single object generation.\r\n2) Object Placement Algorithms:\r\nWhen both the 'overlapping' and 'touching' flags are set to False, the algorithm performs pixel-wise checks \r\nto ensure that no two objects overlap or touch during random placement. For relatively large objects, this \r\nverification can significantly increase processing time, potentially taking several minutes for the placement \r\nof a single object.\r\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Simulation of a microscopic image with round (beads) and elongated fluorescent objects",
    "version": "0.0.3",
    "project_urls": {
        "Bug Tracker": "https://github.com/sklykov/fluoscenepy/issues/",
        "Changelog": "https://github.com/sklykov/fluoscenepy/blob/main/CHANGELOG.md",
        "Documentation": "https://sklykov.github.io/fluoscenepy/api_doc/fluoscenepy/fluoscene.html",
        "Homepage": "https://sklykov.github.io/fluoscenepy/",
        "Repository": "https://github.com/sklykov/fluoscenepy/"
    },
    "split_keywords": [
        "fluorescent",
        "microscopic",
        "image",
        "simulation"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "23719600890745e653ac4121dee5b70f2fe1f5307c66be739c7ce797646690ea",
                "md5": "f99de81338b1fcfc9f63d37ed6abab41",
                "sha256": "a4bca5010632b348db88c480c8b13a39d4d983079fa5ebda546cceb4629590eb"
            },
            "downloads": -1,
            "filename": "fluoscenepy-0.0.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f99de81338b1fcfc9f63d37ed6abab41",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 199941,
            "upload_time": "2025-02-02T17:40:42",
            "upload_time_iso_8601": "2025-02-02T17:40:42.891829Z",
            "url": "https://files.pythonhosted.org/packages/23/71/9600890745e653ac4121dee5b70f2fe1f5307c66be739c7ce797646690ea/fluoscenepy-0.0.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "164e3a5bda4c6dbdae3be73f90122d2f21cc8c901494b7c2749c266dad442f69",
                "md5": "b493bd33d2c537c7f9865f5b0e851306",
                "sha256": "dc59349c96f28254f4fd84e2c77c679cd1675253ed89ff61cf0cb1c02fcf35c1"
            },
            "downloads": -1,
            "filename": "fluoscenepy-0.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "b493bd33d2c537c7f9865f5b0e851306",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 197648,
            "upload_time": "2025-02-02T17:40:44",
            "upload_time_iso_8601": "2025-02-02T17:40:44.269203Z",
            "url": "https://files.pythonhosted.org/packages/16/4e/3a5bda4c6dbdae3be73f90122d2f21cc8c901494b7c2749c266dad442f69/fluoscenepy-0.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-02-02 17:40:44",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "sklykov",
    "github_project": "fluoscenepy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "fluoscenepy"
}
        
Elapsed time: 8.92636s