Skip to content

Commit

Permalink
fix: count_ratio on non overlapping partitions (#886)
Browse files Browse the repository at this point in the history
* fix: count_ratio on non overlapping partitions

* fix: parallel array placement with offset #889

* docs: add first round of documentation on Placement and Indicators based on #858

* fix: re implement the original count_ratio strategy as a new local one.
  • Loading branch information
drodarie authored Oct 12, 2024
1 parent 6b73dbe commit ab77c6f
Show file tree
Hide file tree
Showing 9 changed files with 525 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build Status](https://github.com/dbbs-lab/bsb-core/actions/workflows/build.yml/badge.svg)](https://github.com/dbbs-lab/bsb-core/actions/workflows/build.yml)
[![Build Status](https://github.com/dbbs-lab/bsb-core/actions/workflows/main.yml/badge.svg)](https://github.com/dbbs-lab/bsb-core/actions/workflows/main.yml)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Documentation Status](https://readthedocs.org/projects/bsb/badge/?version=latest)](https://bsb.readthedocs.io/en/latest/?badge=latest)
[![codecov](https://codecov.io/gh/dbbs-lab/bsb-core/branch/main/graph/badge.svg)](https://codecov.io/gh/dbbs-lab/bsb-core)
Expand Down
2 changes: 1 addition & 1 deletion bsb/placement/arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def place(self, chunk, indicators):
cells[(i * len(x)) : ((i + 1) * len(x)), 1] = y
cells[(i * len(x)) : ((i + 1) * len(x)), 2] = z
# Place all the cells in 1 batch (more efficient)
positions = cells[cells[:, 0] < width - radius]
positions = cells[cells[:, 0] < prt.data.ldc[0] + width - radius]

# Determine in which chunks the cells must be placed
cs = self.scaffold.configuration.network.chunk_size
Expand Down
17 changes: 16 additions & 1 deletion bsb/placement/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PlacementIndications:
density: float = config.attr(type=float)
planar_density: float = config.attr(type=float)
count_ratio: float = config.attr(type=float)
local_count_ratio: float = config.attr(type=float)
density_ratio: float = config.attr(type=float)
relative_to: "CellType" = config.ref(refs.cell_type_ref)
count: int = config.attr(type=int)
Expand Down Expand Up @@ -87,6 +88,7 @@ def guess(self, chunk=None, voxels=None):
relative_to = self.indication("relative_to")
density_ratio = self.indication("density_ratio")
count_ratio = self.indication("count_ratio")
local_count_ratio = self.indication("local_count_ratio")
if count is not None:
estimate = self._estim_for_chunk(chunk, count)
if density is not None:
Expand All @@ -96,13 +98,26 @@ def guess(self, chunk=None, voxels=None):
if relative_to is not None:
relation = relative_to
if count_ratio is not None:
# The total counts of cell of the current strategy is a ratio
# of the total number of cell placed by the target strategy.
# This number is uniformly distributed across the current
# strategy's partition(s).
strats = self._strat.scaffold.get_placement_of(relation)
estimate = self._estim_for_chunk(
chunk,
sum(PlacementIndicator(s, relation).guess() for s in strats)
* count_ratio,
)
elif local_count_ratio is not None:
# This count estimate is the ratio of the number of cell of the
# target strategy that were placed in the current chunk.
strats = self._strat.scaffold.get_placement_of(relation)
estimate = (
sum(
PlacementIndicator(s, relation).guess(chunk, voxels)
for s in strats
)
* count_ratio
* local_count_ratio
)
elif density_ratio is not None:
# Create an indicator based on this strategy for the related CT.
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
"sphinx.ext.autodoc",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.imgmath",
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
Expand Down
141 changes: 141 additions & 0 deletions docs/placement/intro.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
=========
Placement
=========
This block in the configuration is responsible for placing cells into partitions.
All placement strategies derive from the :class:`~.placement.strategy.PlacementStrategy` class,
and should provide functions to define the positions of each ``CellType`` within a ``Partition`` volume.

BSB offers several built-in strategies (here is a :doc:`list </placement/placement-strategies>`),
or you can implement your own.
The placement data is stored in :doc:`PlacementSets </placement/placement-set>` for each cell type.

Add a placement strategy
========================

In the ``placement`` block, all placement strategies are defined. For each strategy,
it is necessary to specify the references to their related :guilabel:`partitions` and :guilabel:`cell_types`
with the corresponding attributes.

.. tab-set-code::

.. code-block:: json
"placement": {
"place_A_in_my_layer": {
"strategy": "bsb.placement.RandomPlacement",
"partitions": [
"my_layer"
],
"cell_types": [
"A_type"
]
}
}
.. code-block:: python
config.placement.add(
"place_A_in_my_layer",
strategy="bsb.placement.RandomPlacement",
partitions=["my_layer"],
cell_types=["A_type"],
)
Use indications
===============

When a cell type is created, it is possible to define spatial attributes called
:doc:`placement indications</placement/placement-indicators>`.
These attributes are used by the placement strategy to determine the distribution of cells within the volume.

.. tab-set-code::

.. code-block:: json
"cell_types": {
"A_type": {
"spatial": {
"density": 0.005,
"radius": 2.5
}
},
"B_type": {
"spatial": {
"count": 50,
"radius": 5
}
}
}
"placement": {
"place_A_and_B_in_my_layer": {
"strategy": "bsb.placement.RandomPlacement",
"partitions": [
"my_layer"
],
"cell_types": [
"A_type","B_type"
]
}
}
.. code-block:: python
config.cell_types.add(
"A_type",
spatial=dict(radius=2.5, density=0.005)
)
config.cell_types.add(
"B_type",
spatial=dict(radius=5, count=50)
)
config.placement.add(
"place_A_and_B_in_my_layer",
strategy="bsb.placement.RandomPlacement",
partitions=["my_layer"],
cell_types=["A_type","B_type"],
)
In this example, type A cells are placed with a density of 0.005 cells/µm^3,
while we place 50 type B cells with a radius of 5 µm.


Add dependencies to Placement Strategies
========================================

It may be necessary to place a set of cells only after specific strategies have been executed.
In such cases, you can define a list of strategies as dependencies.
For example, you can create a :guilabel:`secondary_placement` that is executed only after the
:guilabel:`place_A_and_B_in_my_layer` placement has been completed.


.. tab-set-code::

.. code-block:: json
"placement": {
"secondary_placement": {
"strategy": "bsb.placement.RandomPlacement",
"partitions": [
"my_layer"
],
"cell_types": [
"C_type"
],
"depends_on": ["place_A_and_B_in_my_layer"]
}
}
.. code-block:: python
config.placement.add(
"secondary_placement",
strategy="bsb.placement.RandomPlacement",
partitions=["my_layer"],
cell_types=["C_type"],
depends_on=["place_A_and_B_in_my_layer"],
)
159 changes: 159 additions & 0 deletions docs/placement/placement-indicators.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#####################
Placement Indications
#####################

`Placement indications` are a subpart of the a ``CellType`` configuration (attribute ``spatial`` of :doc:`/cells/intro`)
and is leveraged to specify properties during `placement strategies` (see :doc:`/placement/placement-strategies`).
These indications can be either related to the cell type itself (e.g. its soma radius) or
allow for the estimation of the number of cells to place in their respective partitions.


General cell properties
-----------------------
These attributes are related to the ``CellType``; among them, only ``radius`` is mandatory:

* :guilabel:`radius`: The radius of the sphere used to approximate cell soma volume.
* :guilabel:`geometry`: dict = config.dict(type=types.any_())
* :guilabel:`morphologies`: List of morphologies references.

Cell counts estimation properties
---------------------------------
The following attributes allow you to set the strategy used to estimate the counts of cell to place in each partition.
You can choose either to compute the counts independently or with respect to another placement counts.

.. warning::
BSB rounds the number of cells to place in each partition's chunk. The rounding is stochastic, therefore you
might not get the same counts of cells for two similar reconstructions.

Independent counts estimation strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can choose one of the following attributes to estimate the number of cells to place in its partition(s).

* :guilabel:`count`: Set directly the number of cells to place.
* :guilabel:`density`: Use a density of cell per unit of volume (in cell/um^-3). This will be converted to counts
based on the partition(s) total volume.
* :guilabel:`planar_density`: Use a density of cell along the `xy` plane per unit of area (in cell/um^-2). Here too,
density is converted to counts thanks to the partition(s) area along the `xy` plane.
* :guilabel:`density_key`: Leverage a density file, defined here with a reference to a `partitions.keys`
(see `Voxels` section in :doc:`/topology/partitions`). The number of cells is derived from this volumetric density
file for each of its voxels.

Relative counts estimation strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

These strategies derives the counts of cells to place from on another ``CellType`` placement. You would need therefore
to specify the cell name as a reference:

* :guilabel:`relative_to`: Reference to a ``CellType``.

And then, you can choose one of the following attributes:

* :guilabel:`count_ratio`: Compute the number of cells to place from the ratio between the current cell type
and a reference cell type.
* :guilabel:`local_count_ratio`: Same as ``count_ratio`` but applied only on chunks that the current cell type and the
reference cell type have in common.
* :guilabel:`density_ratio`: Similar to ``count_ratio`` but use density instead of cell count.

.. note::
You can mix counts and densities:
For instance, you can have a cell A which counts is relative to the one of another cell type B, and B being defined
from a density value.


Full example
~~~~~~~~~~~~

.. tab-set-code::

.. code-block:: json
"regions": {
"my_region": {
"type": "stack",
"children": ["my_layer", "my_second_layer"],
}
}
"partitions": {
"my_layer": {
"thickness": 100
},
"my_second_layer": {
"thickness": 200
},
}
"cell_types": {
"A": {
"spatial": {
"count": 10,
"radius": 2
}
},
"B": {
"spatial": {
"relative_to": "A",
"density_ratio": 1.5,
"radius": 3
}
}
},
"placement": {
"place_A":{
"strategy": "bsb.placement.RandomPlacement",
"partitions": ["my_layer"],
"cell_types": ["A"],
},
"place_B":{
"strategy": "bsb.placement.RandomPlacement",
"partitions": ["my_second_layer"],
"cell_types": ["B"],
}
},
.. code-block:: python
config.partitions.add("my_layer", type="layer", thickness=100)
config.partitions.add("my_second_layer", type="layer", thickness=200)
config.regions.add(
"my_region",
type="stack",
children=["my_layer", "my_second_layer"]
)
config.cell_types.add(
"A",
spatial=dict(radius=2, count=10)
)
config.cell_types.add(
"B",
spatial=dict(radius=3, relative_to="A", density_ratio=1.5)
)
config.placement.add(
"place_A",
strategy="bsb.placement.RandomPlacement",
partitions=["my_layer"],
cell_types=["A"],
)
config.placement.add(
"place_B",
strategy="bsb.placement.RandomPlacement",
partitions=["my_second_layer"],
cell_types=["B"],
)
The example configuration above creates two Layer partitions (``my_layer`` and ``my_second_layer``)
and assigns a random position to ``A`` and ``B`` within their respective layer.
Assuming that ``my_layer`` is big enough to contains both cells
(see :doc:`RandomPlacement strategy</placement/placement-strategies>`), this will place 10 ``A`` and 30
``B`` because the volume of ``my_second_layer`` :math:`v_2` is twice the one of ``my_layer`` :math:`v_1`
and so:

.. math::
\begin{split}
counts_{B} & = density_{A} \cdot 1.5 \cdot v_2 \\
& = \dfrac{10}{v_1} \cdot 1.5 \cdot 2 \cdot v_1 \\
& = 10 \cdot 1.5 \cdot 2 \\
& = 30
\end{split}
Loading

0 comments on commit ab77c6f

Please sign in to comment.