Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • swain-lab/aliby/aliby-mirror
  • swain-lab/aliby/alibylite
2 results
Show changes
Showing
with 1501 additions and 3034 deletions
"""
Orchestration module and network mid-level interfaces.
"""
from .version import __version__
......@@ -22,18 +22,16 @@ from requests.exceptions import HTTPError, Timeout
################### Dask Methods ################################
def format_segmentation(segmentation, tp):
"""Format a single timepoint into a dictionary.
"""
Format BABY's results from a single time point into a dictionary.
Parameters
------------
segmentation: list
A list of results, each result is the output of the crawler, which is JSON-encoded
A list of results, each result is the output of BABY
crawler, which is JSON-encoded.
tp: int
the time point considered
Returns
--------
A dictionary containing the formatted results of BABY
The time point.
"""
# Segmentation is a list of dictionaries, ordered by trap
# Add trap information
......@@ -204,6 +202,7 @@ def choose_model_from_params(
-------
model_name : str
"""
# cameras prime95 has become sCMOS and evolve has EMCCD
valid_models = list(modelsets().keys())
# Apply modelset filter if specified
......
import itertools
import re
import typing as t
from pathlib import Path
import numpy as np
from baby import BabyCrawler, modelsets
from agora.abc import ParametersABC, StepABC
class BabyParameters(ParametersABC):
"""Parameters used for running BABY."""
def __init__(
self,
modelset_name,
clogging_thresh,
min_bud_tps,
isbud_thresh,
):
"""Initialise parameters for BABY."""
# pixel_size is specified in BABY's model sets
self.modelset_name = modelset_name
self.clogging_thresh = clogging_thresh
self.min_bud_tps = min_bud_tps
self.isbud_thresh = isbud_thresh
@classmethod
def default(cls, **kwargs):
"""Define default parameters; kwargs choose BABY model set."""
return cls(
modelset_name=get_modelset_name_from_params(**kwargs),
clogging_thresh=1,
min_bud_tps=3,
isbud_thresh=0.5,
)
def update_baby_modelset(self, path: t.Union[str, Path, t.Dict[str, str]]):
"""
Replace default BABY model and flattener.
Both are saved in a folder by our retraining script.
"""
if isinstance(path, dict):
weights_flattener = {k: Path(v) for k, v in path.items()}
else:
weights_dir = Path(path)
weights_flattener = {
"flattener_file": weights_dir.parent / "flattener.json",
"morph_model_file": weights_dir / "weights.h5",
}
self.update("modelset_name", weights_flattener)
class BabyRunner(StepABC):
"""
A BabyRunner object for cell segmentation.
Segments one time point at a time.
"""
def __init__(self, tiler, parameters=None, **kwargs):
"""Instantiate from a Tiler object."""
self.tiler = tiler
modelset_name = (
get_modelset_name_from_params(**kwargs)
if parameters is None
else parameters.modelset_name
)
tiler_z = self.tiler.shape[-3]
if f"{tiler_z}z" not in modelset_name:
raise KeyError(
f"Tiler z-stack ({tiler_z}) and model"
f" ({modelset_name}) do not match."
)
if parameters is None:
brain = modelsets.get(modelset_name)
else:
brain = modelsets.get(
modelset_name,
clogging_thresh=parameters.clogging_thresh,
min_bud_tps=parameters.min_bud_tps,
isbud_thresh=parameters.isbud_thresh,
)
self.crawler = BabyCrawler(brain)
self.brightfield_channel = self.tiler.ref_channel_index
@classmethod
def from_tiler(cls, parameters: BabyParameters, tiler):
"""Explicitly instantiate from a Tiler object."""
return cls(tiler, parameters)
def get_data(self, tp):
"""Get image and re-arrange axes."""
img_from_tiler = self.tiler.get_tp_data(tp, self.brightfield_channel)
# move z axis to the last axis; Baby expects (n, x, y, z)
img = np.moveaxis(img_from_tiler, 1, destination=-1)
return img
def _run_tp(
self,
tp,
refine_outlines=True,
assign_mothers=True,
with_edgemasks=True,
**kwargs,
):
"""Segment data from one time point."""
img = self.get_data(tp)
segmentation = self.crawler.step(
img,
refine_outlines=refine_outlines,
assign_mothers=assign_mothers,
with_edgemasks=with_edgemasks,
**kwargs,
)
res = format_segmentation(segmentation, tp)
return res
def get_modelset_name_from_params(
imaging_device="alcatras",
channel="brightfield",
camera="sCMOS",
zoom="60x",
n_stacks="5z",
):
"""Get the appropriate model set from BABY's trained models."""
# list of models - microscopy setups - for which BABY has been trained
# cameras prime95 and evolve have become sCMOS and EMCCD
possible_models = list(modelsets.remote_modelsets()["models"].keys())
# filter possible_models
params = [
str(x) if x is not None else ".+"
for x in [imaging_device, channel.lower(), camera, zoom, n_stacks]
]
params_regex = re.compile("-".join(params) + "$")
valid_models = [
res for res in filter(params_regex.search, possible_models)
]
# check that there are valid models
if len(valid_models) == 1:
return valid_models[0]
else:
raise KeyError(
"Error in finding BABY model sets matching {}".format(
", ".join(params)
)
)
def format_segmentation(segmentation, tp):
"""
Format BABY's results for a single time point into a dict.
The dict has BABY's outputs as keys and lists of the results
for each segmented cell as values.
Parameters
------------
segmentation: list
A list of BABY's results as dicts for each tile.
tp: int
The time point.
"""
# segmentation is a list of dictionaries for each tile
for i, tile_dict in enumerate(segmentation):
# assign the trap ID to each cell identified
tile_dict["trap"] = [i] * len(tile_dict["cell_label"])
# record mothers for each labelled cell
tile_dict["mother_assign_dynamic"] = np.array(
tile_dict["mother_assign"]
)[np.array(tile_dict["cell_label"], dtype=int) - 1]
# merge into a dict with BABY's outputs as keys and
# lists of results for all cells as values
merged = {
output: list(
itertools.chain.from_iterable(
tile_dict[output] for tile_dict in segmentation
)
)
for output in segmentation[0].keys()
}
# remove mother_assign
merged.pop("mother_assign", None)
# ensure that each value is a list of the same length
no_cells = min([len(v) for v in merged.values()])
merged = {k: v[:no_cells] for k, v in merged.items()}
# define time point key
merged["timepoint"] = [tp] * no_cells
return merged
"""
Command Line Interface utilities.
"""
"""
Asynchronous annotation (in one thread). Used as a base to build threading-based annotation.
Currently only works on UNIX-like systems due to using "/" to split addresses.
Usage example
From python
$ python annotator.py --image_path path/to/folder/with/h5files --results_path path/to/folder/with/images/zarr --pos position_name --ncells max_n_to_annotate
As executable (installed via poetry)
$ annotator.py --image_path path/to/folder/with/h5files --results_path path/to/folder/with/images/zarr --pos position_name --ncells max_n_to_annotate
During annotation:
- Assign a (binary) label by typing '1' or '2'.
- Type 'u' to undo.
- Type 's' to skip.
- Type 'q' to quit.
File will be saved in: ./YYYY-MM-DD_annotation/annotation.csv, where YYYY-MM-DD is the current date.
"""
import argparse
import logging
import typing as t
from copy import copy
from datetime import datetime
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import readchar
import trio
from agora.utils.cast import _str_to_int
from aliby.utils.vis_tools import _sample_n_tiles_masks
from aliby.utils.plot import stretch
# Remove logging warnings
logging.getLogger("aliby").setLevel(40)
# Defaults
essential = {"image_path": "zarr", "results_path": "h5"}
param_values = dict(
out_dir=f"./{datetime.today().strftime('%Y_%m_%d')}_annotation/",
pos=None,
ncells=100,
min_tp=100,
max_tp=150,
seed=0,
)
annot_filename = "annotation.csv"
# Parsing
parser = argparse.ArgumentParser(
prog="aliby-annot-binary",
description="Annotate cells in a binary manner",
)
for i, arg in enumerate((*essential, *param_values)):
parser.add_argument(
f"--{arg}",
action="store",
default=param_values.get(arg),
required=i < len(essential),
)
args = parser.parse_args()
for i, k in enumerate((*essential, *param_values.keys())):
# Assign essential values as-is
if i < len(essential):
param_values[k] = getattr(args, k)
# Fill additional values
if passed_value := getattr(args, k):
param_values[k] = passed_value
try:
param_values[k] = _str_to_int(passed_value)
except Exception as exc:
pass
for k, suffix in essential.items(): # Autocomplete if fullpath not provided
if not str(param_values[k]).endswith(suffix):
param_values[k] = (
Path(param_values[k]) / f"{ param_values['pos'] }.{suffix}"
)
# Functions
async def generate_image(stack, skip: bool = False):
await trio.sleep(1)
result = np.random.randint(100, size=(10, 10))
stack.append(result)
async def draw(data, drawing):
if len(drawing) > 1:
for ax, img in zip(drawing, data):
if np.isnan(img).sum(): # Stretch masked channel
img = stretch(img)
ax.set_data(img)
else:
drawing.set_data(data)
plt.draw()
plt.pause(0.1)
def annotate_image(current_key=None, valid_values: t.Tuple[int] = (1, 2)):
# Show image to annotate
while current_key is None or current_key not in valid_values:
if current_key is not None:
print(
f"Invalid value. Please try with valid values {valid_values}"
)
if (current_key := readchar.readkey()) in "qsu":
# if (current_key := input()) in "qsu":
break
current_key = _parse_input(current_key, valid_values)
return current_key
async def generate_image(
generator,
location_stack: t.List[t.Tuple[np.ndarray, t.Tuple[int, int, int]]],
):
new_location_image = next(generator)
location_stack.append((new_location_image[0], new_location_image[1]))
def _parse_input(value: str, valid_values: t.Tuple[int]):
try:
return int(value)
except:
print(
f"Non-parsable value. Please try again with valid values {valid_values}"
)
return None
def write_annotation(
experiment_position: str,
out_dir: Path,
annotation: str,
location_stack: t.Tuple[t.Tuple[int, int, int], np.ndarray],
):
location, stack = location_stack
unique_location = list(map(str, (*experiment_position, *location)))
write_into_file(
out_dir / annot_filename,
",".join((*unique_location, str(annotation))) + "\n",
)
bg_zero = copy(stack[1])
bg_zero[np.isnan(bg_zero)] = 0
tosave = np.stack((stack[0], bg_zero.astype(int)))
# np.savez(out_dir / f"{'_'.join( unique_location )}.npz", tosave)
np.save(out_dir / f"{'.'.join( unique_location )}.npy", tosave)
def write_into_file(file_path: str, line: str):
with open(file_path, "a") as f:
f.write(str(line))
async def annotate_images(
image_path, results_path, out_dir, ncells, seed, interval
):
preemptive_cache = 3
location_stack = []
out_dir = Path(out_dir)
out_annot_file = str(out_dir / annot_filename)
generator = _sample_n_tiles_masks(
image_path, results_path, ncells, seed=seed, interval=interval
)
# Fetch a few positions preemtively
async with trio.open_nursery() as nursery:
for _ in range(preemptive_cache):
nursery.start_soon(generate_image, generator, location_stack)
print("parent: waiting for first annotations.")
_, ax = plt.subplots(figsize=(10, 8))
while not location_stack: # Wait until first image is loaded
await trio.sleep(0.1)
from aliby.utils.plot import plot_overlay
# drawing = ax.imshow(location_stack[0][1])
axes = plot_overlay(*location_stack[0][1], ax=ax.axes)
plt.show(block=False)
plt.draw()
plt.pause(0.5) # May be adjusted based on display speed
try:
out_dir.mkdir(parents=True)
except:
pass
if not Path(out_annot_file).exists():
write_into_file(
out_annot_file,
",".join(
(
"experiment",
"position",
"tile",
"cell_label",
"tp",
"annotation",
)
)
+ "\n",
)
# Loop until n_max or quit
for i in range(1, ncells - preemptive_cache + 1):
# Wait for input
print("Enter a key")
annotation = str(annotate_image())
if annotation == "q":
break
elif annotation == "s":
print("Skipping...")
# continue
elif annotation == "u":
i -= 1
elif isinstance(_str_to_int(annotation), int):
write_annotation(
str(results_path).split(".")[0].split("/")[-2:],
out_dir,
annotation,
location_stack[i],
)
print(location_stack[i][0])
# Append into annotations file
async with trio.open_nursery() as nursery:
nursery.start_soon(generate_image, generator, location_stack)
nursery.start_soon(draw, location_stack[i][1], axes)
print("Annotation done!")
# if __name__ == "__main__":
def annotate():
if any([param_values.get(k) is None for k in ("min_tp", "max_tp")]):
interval = None
else:
interval = (param_values["min_tp"], param_values["max_tp"])
print(param_values)
trio.run(
annotate_images,
param_values["image_path"],
param_values["results_path"],
param_values["out_dir"],
param_values["ncells"],
param_values["seed"],
interval,
)
#!/usr/bin/env jupyter
import argparse
from agora.utils.cast import _str_to_int
from aliby.pipeline import Pipeline, PipelineParameters
def run():
"""
Run a default microscopy analysis pipeline.
Parse command-line arguments and set default parameter values for running a pipeline, then
construct and execute the pipeline with the parameters obtained. Command-line arguments can
override default parameter values. If a command-line argument is a string representation of
an integer, convert it to an integer.
Returns
-------
None
Examples
--------
FIXME: Add docs.
"""
parser = argparse.ArgumentParser(
prog="aliby-run",
description="Run a default microscopy analysis pipeline",
)
param_values = {
"expt_id": None,
"distributed": 2,
"tps": 2,
"directory": "./data",
"filter": 0,
"host": None,
"username": None,
"password": None,
}
for k in param_values:
parser.add_argument(f"--{k}", action="store")
args = parser.parse_args()
for k in param_values:
if passed_value := _str_to_int(getattr(args, k)):
param_values[k] = passed_value
params = PipelineParameters.default(general=param_values)
p = Pipeline(params)
p.run()
# parameters to stop the pipeline when exceeded
earlystop = dict(
min_tp=100,
thresh_pos_clogged=0.4,
thresh_trap_ncells=8,
thresh_trap_area=0.9,
ntps_to_eval=5,
)
# imaging properties of the microscope
imaging_specifications = {
"pixel_size": 0.236,
"z_size": 0.6,
"spacing": 0.6,
}
# possible imaging channels
possible_imaging_channels = [
"Citrine",
"GFP",
"GFPFast",
"mCherry",
"Flavin",
"Citrine",
"mKO2",
"Cy5",
"pHluorin405",
"pHluorin488",
]
# functions to apply to the fluorescence of each cell
fluorescence_functions = [
"mean",
"median",
"std",
"imBackground",
"max5px_median",
]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
"""Additional tools to fetch and handle datasets programatically.
"""
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
__version__ = "0.1.64 lite"
This diff is collapsed.