Tutorial about local density analysis

A local density is computed from the number of neighboring localizations within a specified radius.

from pathlib import Path

%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt

import locan as lc
lc.show_versions(system=False, dependencies=False, verbose=False)
Locan:
   version: 0.22.0.dev32+g4bfc3ab8b

Python:
   version: 3.11.14

Synthetic data

We simulate localization data that is homogeneously Poisson distributed (also described as complete spatial randomness, csr).

rng = np.random.default_rng(seed=1)
locdata_csr = lc.simulate_Poisson(intensity=1e-1, region=((0,100), (0,100)), seed=rng)

print('Data head:')
print(locdata_csr.data.head(), '\n')
print('Summary:')
locdata_csr.print_summary()
print('Properties:')
print(locdata_csr.properties)
Jupyter environment detected. Enabling Open3D WebVisualizer.
[Open3D INFO] WebRTC GUI backend enabled.
[Open3D INFO] WebRTCWindowSystem: HTTP handshake server disabled.
Data head:
   position_x  position_y
0   14.415961   94.864945
1   31.183145   42.332645
2   82.770259   40.919914
3   54.959369    2.755911
4   75.351311   53.814331 

Summary:
identifier: "1"
comment: ""
source: SIMULATION
state: RAW
element_count: 1001
frame_count: 0
creation_time {
  2026-04-30T08:34:45.756356Z
}

Properties:
{'localization_count': 1001, 'position_x': np.float64(49.71480704304258), 'uncertainty_x': np.float64(0.8846806436903792), 'position_y': np.float64(50.34940144307567), 'uncertainty_y': np.float64(0.9309630174812346), 'region_measure_bb': np.float64(9970.499168748705), 'localization_density_bb': np.float64(0.10039617706779522), 'subregion_measure_bb': np.float64(399.40982202393604), 'region_measure': np.int64(10000), 'localization_density': np.float64(0.1001), 'subregion_measure': np.int64(400)}

We also simulate data that follows a Neyman-Scott distribution (blobs):

locdata_blob = lc.simulate_Thomas(parent_intensity=1e-3, region=((0, 100), (0, 100)), cluster_mu=100, cluster_std=5, seed=rng)

print('Data head:')
print(locdata_blob.data.head(), '\n')
print('Summary:')
locdata_blob.print_summary()
print('Properties:')
print(locdata_blob.properties)
Data head:
   position_x  position_y  cluster_label
0   74.134169    4.164725             25
1    1.755308   24.837606             20
2   38.929401   38.036252             28
3    1.465676   25.249268             20
4   74.866279   40.662736             18 

Summary:
identifier: "2"
comment: ""
source: SIMULATION
state: RAW
element_count: 1116
frame_count: 0
creation_time {
  2026-04-30T08:34:45.768812Z
}

Properties:
{'localization_count': 1116, 'position_x': np.float64(50.32464025249008), 'uncertainty_x': np.float64(0.8878228605015925), 'position_y': np.float64(41.51510460080934), 'uncertainty_y': np.float64(0.7620956001424609), 'region_measure_bb': np.float64(9969.531465793627), 'localization_density_bb': np.float64(0.11194106802602488), 'subregion_measure_bb': np.float64(399.3901705025785), 'region_measure': np.int64(10000), 'localization_density': np.float64(0.1116), 'subregion_measure': np.int64(400)}

Scatter plot

fig, ax = plt.subplots(nrows=1, ncols=2)
locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color='Blue', s=1, alpha=0.1, label='locdata_csr')
locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color='Blue', s=1, alpha=0.1, label='locdata_blobs')
plt.tight_layout()
plt.show()
../../_images/adfacb698e0606a43f1860398e9f0a2342b46d6191ae74a2d86dd1cf6d9cf2a8.png

Local densities

We determine the local density for each localization and plot the probability density function for local densities.

lc.LocalDensity?
ld_csr = lc.LocalDensity(radii=[3, 10, 30])
ld_csr.compute(locdata_csr)
ld_csr.results.describe()
3 10 30
count 1001.000000 1001.000000 1001.000000
mean 0.093348 0.090939 0.076425
std 0.054365 0.020611 0.020610
min 0.000000 0.019099 0.026526
25% 0.070736 0.079577 0.058357
50% 0.106103 0.095493 0.079224
75% 0.141471 0.105042 0.095139
max 0.282942 0.140056 0.108933
ld_blob = lc.LocalDensity(radii=[3, 10, 30])
ld_blob.compute(locdata_blob)
ld_blob.results.describe()
3 10 30
count 1116.000000 1116.000000 1116.000000
mean 0.345628 0.237877 0.089437
std 0.222333 0.111080 0.035233
min 0.000000 0.000000 0.006720
25% 0.176839 0.168704 0.064723
50% 0.318310 0.232366 0.094078
75% 0.495149 0.299211 0.113884
max 1.096401 0.547493 0.190986
radius = 3
fig, ax = plt.subplots(nrows=1, ncols=2)
locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color=ld_csr.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_csr')
locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color=ld_blob.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_blobs')
plt.tight_layout()
plt.show()
/tmp/ipykernel_877/23467711.py:3: UserWarning: 'color' and 'colormap' cannot be used simultaneously. Using 'color'
  locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color=ld_csr.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_csr')
/tmp/ipykernel_877/23467711.py:4: UserWarning: 'color' and 'colormap' cannot be used simultaneously. Using 'color'
  locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color=ld_blob.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_blobs')
../../_images/916a4d0117d32471a25ebf1cc1ca3dbeacdc880dea5b40e6ce391e36d2eae7ba.png
radius = 10
fig, ax = plt.subplots(nrows=1, ncols=2)
locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color=ld_csr.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_csr')
locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color=ld_blob.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_blobs')
plt.tight_layout()
plt.show()
/tmp/ipykernel_877/3710508265.py:3: UserWarning: 'color' and 'colormap' cannot be used simultaneously. Using 'color'
  locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color=ld_csr.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_csr')
/tmp/ipykernel_877/3710508265.py:4: UserWarning: 'color' and 'colormap' cannot be used simultaneously. Using 'color'
  locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color=ld_blob.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_blobs')
../../_images/5732ae41812ea2f0cd4ccbca016537d99508f10e82b207f91be05620335d29f4.png
radius = 30
fig, ax = plt.subplots(nrows=1, ncols=2)
locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color=ld_csr.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_csr')
locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color=ld_blob.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_blobs')
plt.tight_layout()
plt.show()
/tmp/ipykernel_877/1649477093.py:3: UserWarning: 'color' and 'colormap' cannot be used simultaneously. Using 'color'
  locdata_csr.data.plot.scatter(x='position_x', y='position_y', ax=ax[0], color=ld_csr.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_csr')
/tmp/ipykernel_877/1649477093.py:4: UserWarning: 'color' and 'colormap' cannot be used simultaneously. Using 'color'
  locdata_blob.data.plot.scatter(x='position_x', y='position_y', ax=ax[1], color=ld_blob.results[radius], s=1, colormap='viridis', alpha=1, label='locdata_blobs')
../../_images/e38ec142b9d57408b135b0fff63a5bf8e003185831ae76be2c2d99955b325b73.png
ld_csr.hist(alpha=0.5, density=True, bins=20);
../../_images/836d0b9ed0b864f7fab932a35842aa9dcc8a4b09ead3cf2e3d2c7a51a0bf69d0.png
ld_blob.hist(alpha=0.5, density=True, bins=20);
../../_images/b95abbe8de0cb55e095aad6e7600ac84150b6303b4f52f72539cc6eaa23a0886.png
bins = np.arange(0, 1, 0.05)
ld_csr.hist(alpha=0.5, bins=bins)
ld_blob.hist(alpha=0.5, bins=bins);
../../_images/814a5234bfbaf51f8f70654aef15d6d0e2030930877e6192aba094ed4ea4e27e.png

Local densities with boundary correction

We determine the local density for each localization with a boundary correction applied. For the correction local density values are normalized by the relative overlapp of the encircling region and the support region.

locdata = lc.simulate_uniform(n_samples=5_000, region=lc.Ellipse((0, 0), 100, 100))
locdata.region
../../_images/26c147956db9ac0d64c10b7f8fb1fed02d6e7b57877835747deedcec0a84f215.svg
radius = 20
ld = lc.LocalDensity(radii=[radius]).compute(locdata)
ld.hist(bins=20);
../../_images/d3710c07d7d516ccb9992988bb4a821459ebc65f29d536abc8e79b00ca9f74a9.png
ld.results.index = locdata.data.index
df = locdata.dataframe.assign(local_density=ld.results[radius])
locdata = locdata.update(dataframe=df)
locdata.data.local_density.describe()
count    5000.000000
mean        0.533202
std         0.121798
min         0.252261
25%         0.427331
50%         0.580120
75%         0.635824
max         0.710627
Name: local_density, dtype: float64
fig, ax = plt.subplots()
lc.render_2d_mpl(locdata, ax=ax, loc_properties=["position_x", "position_y"], bin_size=1, other_property='local_density', rescale=lc.Trafo.NONE, vmin=0.2, vmax=0.7);
if locdata.region:
    locdata.region.plot(ax=ax, fill=False, color='Black');
../../_images/c775c6d5ad5d2fcd9acc338509b7a2564aec04eadd8a63ccebc1dc957765477c.png
ld_2 = lc.LocalDensity(radii=[radius], boundary_correction=True).compute(locdata)
ld_2.hist(bins=20);
../../_images/717e7301ba2e4d3b225abb33ecec522a99ec82b24fdac8343b92c76e33f8427b.png
ld_2.results.index = locdata.data.index
df = locdata.dataframe.assign(local_density=ld_2.results[radius])
locdata = locdata.update(dataframe=df)
locdata.data.local_density.describe()
count    5000.000000
mean        0.639977
std         0.033068
min         0.549939
25%         0.618943
50%         0.636847
75%         0.655578
max         0.737318
Name: local_density, dtype: float64
fig, ax = plt.subplots()
lc.render_2d_mpl(locdata, ax=ax, loc_properties=["position_x", "position_y"], bin_size=1, other_property='local_density', rescale=lc.Trafo.NONE, vmin=0.2, vmax=0.7);
if locdata.region:
    locdata.region.plot(ax=ax, fill=False, color='White');
../../_images/0b8486dcd01f5bac59d20fd63bf2f56b1273727d092fc3be5edbd369b5edc8c1.png