This notebook is aimed showcasing the iter_subplots
function. Some nice dual-color super-resolution data on a large number of cells would be perfect for this. unfortunately, I don’t have any, so we’ll first generate some.
%matplotlib notebook
import matplotlib.pyplot as plt
from colicoords import load, save, CellPlot, CellListPlot, SynthCell, IterCellPlot, iter_subplots, SynthCellList, AutoIterCellPlot, Data, Cell, CellList
from tqdm.auto import tqdm
from itertools import combinations
import os
import numpy as np
43) np.random.seed(
We start out by loading some cell-objects, these were measured and analyzed by Yichen Li
= load('starting_cells.hdf5')
cells len(cells), cells.data.names
(19, ['binary', 'brightfield', 'storm'])
= plt.subplots(1, 3, figsize=(6, 2))
fig, (ax1, ax2, ax3) = CellPlot(cells[0])
cp 'binary', ax=ax1)
cp.imshow(=ax1)
cp.plot_outline(ax'brightfield', ax=ax2)
cp.imshow(='storm', ax=ax3, method='gauss', upscale=5)
cp.plot_storm(data_name
for ax in [ax1, ax2, ax3]:
ax.set_xticks([]) ax.set_yticks([])
<IPython.core.display.Javascript object>
HBox(children=(IntProgress(value=0, max=2104), HTML(value='')))
As we can see in the cells above, we have a total of 19 cells, which have a 1-color super resolution dataset, outlining the membrane. We’d like to a) add more cells and b) add a second channel dataset.
To create more cells, we can define two functions to mirror the existing cells either horizontally or vertically:
def mirror_vertical(cell):
"""
Mirrors a Cell object verically and returns the mirrored cell
"""
= cell.copy()
c #mirroring the images by indexing
= c.data.data_dict['brightfield'][:, ::-1]
bf = c.data.data_dict['binary'][:, ::-1]
binary
= Data()
data 'binary')
data.add_data(binary, 'brightfield')
data.add_data(bf,
#Mirroring STORM data elements by inverting the x-axis
for k, v in c.data.storm_dict.items():
= v
st_m 'x'] = c.data.shape[1] - st_m['x']
st_m[
'storm', k)
data.add_data(st_m,
= Cell(data)
c_out = c.name + '_v'
c_out.name return c_out
def mirror_horizontal(cell):
"""
Mirrors a Cell object horizontally and returns the mirrored cell
"""
= cell.copy()
c #mirroring the images by indexing
= c.data.data_dict['brightfield'][::-1, :]
bf = c.data.data_dict['binary'][::-1, :]
binary
= Data()
data 'binary')
data.add_data(binary, 'brightfield')
data.add_data(bf,
#Mirroring STORM data elements by inverting the y-axis
for k, v in c.data.storm_dict.items():
= v
st_m 'y'] = c.data.shape[0] - st_m['y']
st_m[
'storm', k)
data.add_data(st_m,
= Cell(data)
c_out = c.name + '_h'
c_out.name return c_out
Next, we want to add something that could represent the second channel. For this, we’ll generate some random locations within the cell, lets say two ‘blobs’ left and right which could represent nucleoids of the dividing cell.
We define some functions below to help us do this. The general process is to generate random values along the longitinudinal and radial coordinates of the cells, and then transforming them back to Cartesian coordinates and add them to the cell Object. We check for AssertionError
s because sometimes out-of-bounds values are generated, and we simply skip those and try again.
The gen_values
function returns both negative and positive values, indicating postitions above and below the cell midline. In ColiCoords
only positive values are used and the parameter phi
is used to indicate the direction. In gen_nucleoid_storm
this information is transferred to phi
.
def gen_values(cell, l_pos, num):
"""
Generate a number of random positions in the cell centered around ``l_pos``.
"""
= l_pos*np.random.normal(1, 0.2)*cell.length
l_mu = 0.1*np.random.normal(1, 0.2)*cell.length
l_std
= 0+np.random.normal(0, 0.1)*cell.radius
r_mu = 0.15*np.random.normal(1, 0.2)*cell.radius
r_std
= np.array([[np.random.normal(l_mu, l_std), np.random.normal(r_mu, r_std)] for i in range(num)]).T
pos = pos
l, r
return l, r
def gen_nucleoid_storm(cell, l, r):
"""
Given a Cell object and some random cellular coordinates `l` and `r`, create a STORM data element.
"""
= np.zeros_like(r)
phi == 1] = 180
phi[np.sign(r)
= cell.coords.rev_transform(l, np.abs(r), phi, l_norm=False)
x_res, y_res
= np.recarray((len(x_res, )), dtype=[('x', float), ('y', float), ('frame', int), ('intensity', int)])#
storm
'x'] = x_res
storm['y'] = y_res
storm['frame'] = np.zeros_like(x_res)
storm['intensity'] = np.ones_like(x_res)
storm[
return storm
def add_nucleoids(c):
"""
Takes a Cell object and adds 'nucleoid' data elements at 0.25 l and 0.75 l.
"""
= c.copy()
c_new
while True:
try:
= gen_values(c, 0.25, int(np.random.normal(75, 15)))
l, r = gen_nucleoid_storm(c_new, l, r)
storm1 break
except AssertionError:
pass
while True:
try:
= gen_values(c, 0.75, int(np.random.normal(75, 15)))
l, r = gen_nucleoid_storm(c_new, l, r)
storm2 break
except AssertionError:
pass
'storm', 'nuc1')
c_new.data.add_data(storm1, 'storm', 'nuc2')
c_new.data.add_data(storm2,
return c_new
Combining all functions, we can write a for loop which multiplies the number of cells 4-fold and adds two nucleod datasets
= [lambda x: x, mirror_horizontal, mirror_vertical, lambda x: mirror_horizontal(mirror_vertical(x))]
functions = []
total_cells
for f in functions:
for c in cells:
= add_nucleoids(f(c))
c_n
total_cells.append(c_n)
= CellList(total_cells)
cl len(cl), cl[0].data.names
(76, ['binary', 'brightfield', 'storm', 'nuc1', 'nuc2'])
We now have 76 cells with two data elements added. We can save the resulting Cell objects and display one to have a look at the result.
'output_cells.hdf5', cl) save(
= plt.subplots( figsize=(6, 4))
fig, ax = CellPlot(cl[0])
cp ='gray')
cp.imshow(np.zeros(c.data.shape), cmap='nuc1', method='gauss', upscale=5, alpha_cutoff=0.25, cmap='inferno')
cp.plot_storm(data_name='nuc2', method='gauss', upscale=5, alpha_cutoff=0.25, cmap='inferno')
cp.plot_storm(data_name='storm', method='gauss', upscale=5, alpha_cutoff=0.25, cmap='viridis')
cp.plot_storm(data_name= ax.set_xticks([])
t = ax.set_yticks([]) t
<IPython.core.display.Javascript object>
HBox(children=(IntProgress(value=0, max=2104), HTML(value='')))
Nice! The upscale
parameter controls the number of pixels in the recontructed image. It scales the pixel size down, so setting it to 5 means that both pixel dimensions are reduced by 5 so the amount of pixels is increased by a factor 25. When setting this parameter too high the computation time for the reconstruction can get very long. The alpha_cutoff
is the fraction of maximum intensity below which the values are set to maximum alpha. This is to allow for overlap of multiple colors.
= iter_subplots(1, 2, figsize=(6.1, 2))
fig, (ax1, ax2) = IterCellPlot(cl[:20][::-1])
cp 'brightfield', ax=ax1)
cp.imshow(=ax1, data_name='storm', color='g', alpha=0.1)
cp.plot_storm(ax=ax1)
cp.plot_outline(ax
for i in range(len(cp.cell_list))]), cmap='gray', ax=ax2)
cp.imshow(np.stack([np.zeros(cp.cell_list.data.shape) ='nuc1', method='gauss', upscale=5, alpha_cutoff=0.15, cmap='inferno', ax=ax2)
cp.plot_storm(data_name='nuc2', method='gauss', upscale=5, alpha_cutoff=0.15, cmap='inferno', ax=ax2)
cp.plot_storm(data_name='storm', method='gauss', upscale=5, alpha_cutoff=0.15, cmap='viridis', ax=ax2)
cp.plot_storm(data_name
fig.display()
ax1.set_axis_off()
ax2.set_axis_off()
plt.tight_layout()
<IPython.core.display.Javascript object>
VBox(children=(HBox(children=(Button(description='First', style=ButtonStyle()), Button(description='Prev', sty…
And done! I’ve limited the amount of cells plotted to 20 because rendering all storm images takes a long time (yes I know why bother generating more). The rendering of STORM images is to be improved in a future performance update. Currently, the experimental numba
branch already provides some speedup.