Skip to main content

Simulation of a microscopic image with round (beads) and elongated fluorescent objects

Project description

'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 advanced projects (for instance, Synthetic objects generation of the DeconvTest project) that address the task of simulating objects commonly found in fluorescence microscopy images, and bead simulation may seem trivial, I have not yet found 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 four border pixels positioned at 90° intervals (along the cardinal directions) are included because their distance from the circle's center is exactly 1 pixel, matching the circle's radius.

Normal Circle

# 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()
  1. "Oversampled" Circle: all pixels within the circle's boundary are included in the projection, each assigned the maximum (but normalized) intensity.

Oversampled Circle

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

  1. "Undersampled" Circle: only pixels that lie entirely within the circle's boundary are included in the projection.

Undersampled Circle

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 addressed either by calculating the area of intersection between each pixel and the circle's boundary or by using a bell-shaped analytical function to describe the object's shape (more information on these functions).

To illustrate this, the following shapes could be plotted:

  1. A shape based on the calculated area of intersection between each pixel and the circular boundary, referred to as the "Precise" Circle:

Precise Circle 2

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

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

Precise Circle 4.8

from fluoscenepy import FluorObj
flobj = FluorObj(typical_size=4.8); flobj.get_shape(); flobj.plot_shape()
  1. 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

from fluoscenepy import FluorObj
flobj = FluorObj(typical_size=4.8, border_type="co", shape_method="bump3")
flobj.get_shape(); flobj.plot_shape()

The challenge of accurately projecting a circle onto a pixel grid becomes even more significant when the circle's center is shifted from the center of a pixel. To illustrate this, here are several examples of circles shifted by (0.24, 0.0):

Shifted "Normal" Circle:

Normal Circle

Shifted "Precise" Circle:

Shifted Precise Circle

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:

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

Generated scene with additional noise calculated with default method parameters:

Scene w/t 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 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.64.0 - most recent for Python 3.11 and 3.12 on 26-03-2026).

import numpy as np
from fluoscenepy import FluorObj, precompile_fluoscene, clean_fluoscene_cache
precompile_fluoscene()  # 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 
clean_fluoscene_cache()  # optional cleaning saved by numba cached, compiled artifacts

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:

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.

Casting of images

The target image (2D numpy array) can be cast or transformed with various possible options: "neg.norm.", "int8", "int16", "norm", "uint8", "uint16". All conversion implies that the input image contains some signal (not only noise presented or the image is flat - contains mostly same pixel values). Check autoreport formed after calling cast_image(...) method.

Compatibility with numpy >= 2.0.0

Even though all tests have been passed for the latest on 26-03-2026 numpy ver. 2.4.3, please create an issue if something turns not compatible with the latest versions of numpy.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fluoscenepy-0.1.0.tar.gz (50.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fluoscenepy-0.1.0-py3-none-any.whl (50.0 kB view details)

Uploaded Python 3

File details

Details for the file fluoscenepy-0.1.0.tar.gz.

File metadata

  • Download URL: fluoscenepy-0.1.0.tar.gz
  • Upload date:
  • Size: 50.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for fluoscenepy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eece56aaf7c331fb60542f9dd71064598bd53d7f16550f141a6236322a09f698
MD5 48ac477b24eb0c6bf28c18ff95e5cbf6
BLAKE2b-256 ea4bbb0fbbfd272af69c0011208d3b0d96928af1db96263dd0c61fdbcd9a7489

See more details on using hashes here.

File details

Details for the file fluoscenepy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fluoscenepy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 50.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for fluoscenepy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 18d8ff658ffb1bd3d2471c1429daacdf722f88ff1def0c160e6fc8a5fc82d045
MD5 8b917a4872bc19130430d6d3ddbe67e8
BLAKE2b-256 11403f94420a1003f2234417468fe55b9ad032dad5707fa2c00c4344963827a5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page