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
Commits on Source (175)
Showing
with 4321 additions and 1816 deletions
...@@ -67,7 +67,7 @@ Static Type: ...@@ -67,7 +67,7 @@ Static Type:
- poetry run mypy . --exclude 'setup\.py$' - poetry run mypy . --exclude 'setup\.py$'
# We can remove the flag once this is resolved https://github.com/pypa/setuptools/issues/2345 # We can remove the flag once this is resolved https://github.com/pypa/setuptools/issues/2345
# TODO add more tests before activate automatic release # TODO add more tests before activating auto-release
# Bump_release: # Bump_release:
# stage: release # stage: release
# script: # script:
......
## Summary ## Summary
(Summarize the bug encountered concisely) {Summarize the bug encountered concisely}
I confirm that I have (if relevant):
- [ ] Read the troubleshooting guide: https://gitlab.com/aliby/aliby/-/wikis/Troubleshooting-(basic)
- [ ] Updated aliby and aliby-baby.
- [ ] Tried the unit test.
- [ ] Tried a scaled-down version of my experiment (distributed=0, filter=0, tps=10)
- [ ] Tried re-postprocessing.
## Steps to reproduce ## Steps to reproduce
(How one can reproduce the issue - this is very important) {How one can reproduce the issue - this is very important}
- aliby version: 0.1.{...}, or if development/unreleased version, commit SHA: {...}
- platform(s):
- [ ] Jura
- [ ] Other Linux, please specify distribution and version: {...}
- [ ] MacOS, please specify version: {...}
- [ ] Windows, please specify version: {...}
- experiment ID: {...}
- Any special things you need to know about this experiment: {...}
## What is the current bug behavior? ## What is the current bug behavior?
...@@ -19,6 +35,12 @@ ...@@ -19,6 +35,12 @@
(Paste any relevant logs - please use code blocks (```) to format console output, logs, and code, as (Paste any relevant logs - please use code blocks (```) to format console output, logs, and code, as
it's very hard to read otherwise.) it's very hard to read otherwise.)
```
{PASTE YOUR ERROR MESSAGE HERE!!}
```
## Possible fixes ## Possible fixes
(If you can, link to the line of code that might be responsible for the problem) (If you can, link to the line of code that might be responsible for the problem)
...@@ -11,15 +11,18 @@ End-to-end processing of cell microscopy time-lapses. ALIBY automates segmentati ...@@ -11,15 +11,18 @@ End-to-end processing of cell microscopy time-lapses. ALIBY automates segmentati
## Quickstart Documentation ## Quickstart Documentation
Installation of [VS Studio](https://visualstudio.microsoft.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2022) Native MacOS support for is under work, but you can use containers (e.g., Docker, Podman) in the meantime. Installation of [VS Studio](https://visualstudio.microsoft.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2022) Native MacOS support for is under work, but you can use containers (e.g., Docker, Podman) in the meantime.
For analysing local data To analyse local data
```bash ```bash
pip install aliby # aliby[network] if you want to access an OMERO server pip install aliby
``` ```
Add any of the optional flags `omero` and `utils` (e.g., `pip install aliby[omero, utils]`). `omero` provides tools to connect with an OMERO server and `utils` provides visualisation, user interface and additional deep learning tools.
See our [installation instructions]( https://aliby.readthedocs.io/en/latest/INSTALL.html ) for more details. See our [installation instructions]( https://aliby.readthedocs.io/en/latest/INSTALL.html ) for more details.
### CLI ### CLI
If installed via poetry, you have access to a Command Line Interface (CLI)
```bash ```bash
aliby-run --expt_id EXPT_PATH --distributed 4 --tps None aliby-run --expt_id EXPT_PATH --distributed 4 --tps None
``` ```
...@@ -31,14 +34,15 @@ And to run Omero servers, the basic arguments are shown: ...@@ -31,14 +34,15 @@ And to run Omero servers, the basic arguments are shown:
The output is a folder with the original logfiles and a set of hdf5 files, one with the results of each multidimensional inside. The output is a folder with the original logfiles and a set of hdf5 files, one with the results of each multidimensional inside.
For more information, including available options, see the page on [running the analysis pipeline](https://aliby.readthedocs.io/en/latest/PIPELINE.html)
## Using specific components ## Using specific components
### Access raw data ### Access raw data
ALIBY's tooling can also be used as an interface to OMERO servers, taking care of fetching data when needed. ALIBY's tooling can also be used as an interface to OMERO servers, for example, to fetch a brightfield channel.
```python ```python
from aliby.io.dataset import Dataset from aliby.io.omero import Dataset, Image
from aliby.io.image import Image
server_info= { server_info= {
"host": "host_address", "host": "host_address",
...@@ -73,27 +77,33 @@ in time. ...@@ -73,27 +77,33 @@ in time.
It fetches the metadata from the Image object, and uses the TilerParameters values (all Processes in aliby depend on an associated Parameters class, which is in essence a dictionary turned into a class.) It fetches the metadata from the Image object, and uses the TilerParameters values (all Processes in aliby depend on an associated Parameters class, which is in essence a dictionary turned into a class.)
#### Get a timelapse for a given trap #### Get a timelapse for a given tile (remote connection)
```python ```python
fpath = "h5/location" fpath = "h5/location"
trap_id = 9 tile_id = 9
trange = list(range(0, 30)) trange = range(0, 10)
ncols = 8 ncols = 8
riv = remoteImageViewer(fpath) riv = remoteImageViewer(fpath)
trap_tps = riv.get_trap_timepoints(trap_id, trange, ncols) trap_tps = [riv.tiler.get_tiles_timepoint(tile_id, t) for t in trange]
# You can also access labelled traps
m_ts = riv.get_labelled_trap(tile_id=0, tps=[0])
# And plot them directly
riv.plot_labelled_trap(trap_id=0, channels=[0, 1, 2, 3], trange=range(10))
``` ```
This can take several seconds at the moment. Depending on the network speed can take several seconds at the moment.
For a speed-up: take fewer z-positions if you can. For a speed-up: take fewer z-positions if you can.
#### Get the traps for a given time point #### Get the tiles for a given time point
Alternatively, if you want to get all the traps at a given timepoint: Alternatively, if you want to get all the traps at a given timepoint:
```python ```python
timepoint = 0 timepoint = (4,6)
seg_expt.get_tiles_timepoints(timepoint, tile_size=96, channels=None, tiler.get_tiles_timepoint(timepoint, channels=None,
z=[0,1,2,3,4]) z=[0,1,2,3,4])
``` ```
......
...@@ -62,7 +62,7 @@ For Windows, the simplest way to install it is using conda (or mamba). You can i ...@@ -62,7 +62,7 @@ For Windows, the simplest way to install it is using conda (or mamba). You can i
$ \PATH\TO\POETRY\LOCATION\poetry install $ \PATH\TO\POETRY\LOCATION\poetry install
- MacOS - MacOS
Under work (See issue https://github.com/ome/omero-py/issues/317) For local access and processing, follow the same instructions as Linux. Remote access to OMERO servers depends on some issues in one of our depedencies being solved (See issue https://github.com/ome/omero-py/issues/317)
### Git version ### Git version
...@@ -71,9 +71,23 @@ Install [ poetry ](https://python-poetry.org/docs/#installation) for dependency ...@@ -71,9 +71,23 @@ Install [ poetry ](https://python-poetry.org/docs/#installation) for dependency
In case you want to have local version: In case you want to have local version:
$ git clone git@gitlab.com/aliby/aliby.git $ git clone git@gitlab.com/aliby/aliby.git
$ cd aliby && poetry install --all-extras $ cd aliby
and then either
This will automatically install the [ BABY ](https://gitlab.com/aliby/baby) segmentation software. Support for additional segmentation and tracking algorithms is under development. $$ poetry install --all-extras
for everything, including tools to access OMERO servers, or
$$ poetry install
for a version with only local access, or
$$ poetry install --with dev
to install with compatible versions of the development tools we use, such as black.
These commands will automatically install the [ BABY ](https://gitlab.com/aliby/baby) segmentation software. Support for additional segmentation and tracking algorithms is under development.
## Omero Server ## Omero Server
...@@ -111,3 +125,45 @@ docker-compose stop ...@@ -111,3 +125,45 @@ docker-compose stop
Segmentation has been tested on: Mac OSX Mojave, Ubuntu 20.04 and Arch Linux. Segmentation has been tested on: Mac OSX Mojave, Ubuntu 20.04 and Arch Linux.
Data processing has been tested on all the above and Windows 11. Data processing has been tested on all the above and Windows 11.
### Detailed Windows installation
#### Create environment
Open anaconda powershell as administrator
```shell script
conda create -n devaliby2 -c conda-forge python=3.8 omero-py
conda activate devaliby2
```
#### Install poetry
You may have to specify the python executable to get this to work :
```shell script
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | C:\Users\USERNAME\Anaconda3\envs\devaliby2\python.exe -
``` Also specify full path when running poetry (there must be a way to sort this)
- Clone the repository (Assuming you have ssh properly set up)
```shell script
git clone git@gitlab.com:aliby/aliby.git
cd aliby
poetry install --all-extras
```
You may need to run the full poetry path twice - first time gave an error message, worked second time
```shell script
C:\Users\v1iclar2\AppData\Roaming\Python\Scripts\poetry install --all-extras
```
confirm installation of aliby - python...import aliby - get no error message
#### Access the virtual environment from the IDE (e.g., PyCharm)
New project
In location - navigate to the aliby folder (eg c::/Users/Public/Repos/aliby
- Select the correct python interpreter
click the interpreter name at the bottom right
click add local interpreter
on the left click conda environment
click the 3 dots to the right of the interpreter path and navigate to the python executable from the environment created above (eg C:\Users\v1iclar2\Anaconda3\envs\devaliby2\python.exe)
#### Potential Windows issues
- Sometimes the library pywin32 gives trouble, just install it using pip or conda
# Running the analysis pipeline
You can run the analysis pipeline either via the command line interface (CLI) or using a script that incorporates the `aliby.pipeline.Pipeline` object.
## CLI
On a CLI, you can use the `aliby-run` command. This command takes options as follows:
- `--host`: Address of image-hosting server.
- `--username`: Username to access image-hosting server.
- `--password`: Password to access image-hosting server.
- `--expt_id`: Number ID of experiment stored on host server.
- `--distributed`: Number of distributed cores to use for segmentation and signal processing. If 0, there is no parallelisation.
- `--tps`: Optional. Number of time points from the beginning of the experiment to use. If not specified, the pipeline processes all time points.
- `--directory`: Optional. Parent directory to save the data files (HDF5) generated, `./data` by default; the files will be stored in a child directory whose name is the name of the experiment.
- `--filter`: Optional. List of positions to use for analysis. Alternatively, a regex (regular expression) or list of regexes to search for positions. **Note: for the CLI, currently it is not able to take a list of strings as input.**
- `--overwrite`: Optional. Whether to overwrite an existing data directory. True by default.
- `--override_meta`: Optional. Whether to overwrite an existing data directory. True by default.
Example usage:
```bash
aliby-run --expt_id EXPT_PATH --distributed 4 --tps None
```
And to run Omero servers, the basic arguments are shown:
```bash
aliby-run --expt_id XXX --host SERVER.ADDRESS --user USER --password PASSWORD
```
## Script
Use the `aliby.pipeline.Pipeline` object and supply a dictionary, following the example below. The meaning of the parameters are the same as described in the CLI section above.
```python
#!/usr/bin/env python3
from aliby.pipeline import Pipeline, PipelineParameters
# Specify experiment IDs
ids = [101, 102]
for i in ids:
print(i)
try:
params = PipelineParameters.default(
# Create dictionary to define pipeline parameters.
general={
"expt_id": i,
"distributed": 6,
"host": "INSERT ADDRESS HERE",
"username": "INSERT USERNAME HERE",
"password": "INSERT PASSWORD HERE",
# Ensure data will be overwriten
"override_meta": True,
"overwrite": True,
}
)
# Fine-grained control beyond general parameters:
# change specific leaf in the extraction tree.
# This example tells the pipeline to additionally compute the
# nuc_est_conv quantity, which is a measure of the degree of
# localisation of a signal in a cell.
params = params.to_dict()
leaf_to_change = params["extraction"]["tree"]["GFP"]["np_max"]
leaf_to_change.add("nuc_est_conv")
# Regenerate PipelineParameters
p = Pipeline(PipelineParameters.from_dict(params))
# Run pipeline
p.run()
# Error handling
except Exception as e:
print(e)
```
This example code can be the contents of a `run.py` file, and you can run it via
```bash
python run.py
```
in the appropriate virtual environment.
Alternatively, the example code can be the contents of a cell in a jupyter notebook.
...@@ -4,21 +4,15 @@ ...@@ -4,21 +4,15 @@
contain the root `toctree` directive. contain the root `toctree` directive.
.. toctree:: .. toctree::
:hidden:
Home page <self> Home page <self>
Installation <INSTALL.md> Installation <INSTALL.md>
Pipeline options <PIPELINE.md>
Contributing <CONTRIBUTING.md>
.. ..
Examples <examples.rst> Examples <examples.rst>
Reference <api.rst> Reference <api.rst>
.. ..
Contributing <CONTRIBUTING.md>
..
ALIBY reference <_autosummary/aliby>
extraction reference <_autosummary/extraction>
agora reference <_autosummary/agora>
postprocessor reference <_autosummary/postprocessor>
logfile_parser reference <_autosummary/logfile_parser>
.. include:: ../../README.md .. include:: ../../README.md
:parser: myst_parser.sphinx_ :parser: myst_parser.sphinx_
#+title: Input/Output Stage Dependencies
Overview of what fields are required for each consecutive step to run, and
- Registration
- Tiler
- Requires:
- None
# - Optionally:
- Produces:
- /trap_info
- Tiler
- Requires:
- None
- Produces:
- /trap_info
#+title: ALIBY roadmap
Overview of potential improvements, goals, issues and other thoughts worth keeping in the repository. In general, it is things that the original developer would have liked to implement had there been enough time.
* General goals
- Simplify code base
- Reduce dependency on BABY
- Abstract components beyond cell outlines (i.e, vacuole, or other ROIs)
- Enable providing metadata defaults (remove dependency of metadata)
- (Relevant to BABY): Migrate aliby-baby to Pytorch from Keras. Immediately after upgrade h5py to the latest version (we are stuck in 2.10.0 due to Keras).
* Long-term tasks (Soft Eng)
- Support external segmentation/tracking/lineage/processing tools
- Split segmentation, tracking and lineage into independent Steps
- Implement the pipeline as an acyclic graph
- Isolate lineage and tracking into a section of aliby or an independent package
- Abstract cells into "ROIs" or "Outlines"
- Abstract lineage into "Outline relationships" (this may help study cell-to-cell interactions in the future)
- Add support to next generation microscopy formats.
- Make live cell processing great again! (low priority)
* Potential features
- Flat field correction (requires research on what is the best way to do it)
- Support for monotiles (e.g., agarose pads)
- Support the user providing location of tiles (could be a GUI in which the user selects a region)
- Support multiple neural networks (e.g., vacuole/nucleus in adition to cell segmentation)
- Use CellPose as a backup for accuracy-first pipelines
* Potential CLI(+matplotlib) interfaces
The fastest way to get a gui-like interface is by using matplotlib as a panel to update and read keyboard inputs to interact with the data. All of this can be done within matplotlib in a few hundreds of line of code.
- Annotate intracellular contents
- Interface to adjust the parameters for calibration
- Basic selection of region of interest in a per-position basis
* Sections in need of refactoring
** Extraction
Extraction could easily increase its processing speed. Most of the code was not originally written using casting and vectorised operations.
- Reducing the use of python loops to the minimum
- Replacing nested functions with functional mappings (extraction be faster and clearer with a functional programming approach)
- Replacing the tree with a set of tuples and delegating processing order to dask.
Dask can produce its own internal tree and optimise the order of rendering the tree unnecessary
** Postprocessing.
- Clarify the limits of picking and merging classes: These are temporal procedures; in the future segmentation should become more accurate, making picking Picker redundant; better tracking/lineage assignemnt will make merging redundant.
- Formalise how lineage and reshaper processes are handled
- Non-destructive postprocessing.
The way postprocessing is done is destructive at the moment. If we aim to perform more complex data analysis automatically an implementation of complementary and tractable sub-pipelines is essential. (low priority, perhaps within scripts)
- Functionalise parameter-process schema. This schema provides a decent structure, but it requires a lot of boilerplate code. To transition the best option is probably a function that converts Process classes into a function, and another that extracts default values from a Parameters class. This could in theory replace most Process-Parameters pairs. Lineage functions will pose a problem and a common interface to get lineage or outline-to-outline relationships demands to be engineered.
** Compiler/Reporter
- Remove compiler step, and focus on designing an adequate report, then build it straight after postprocessing ends.
** Writers/Readers
- Consider storing signals that are similar (e.g., signals arising from each channel) in a single multidimensional array to save storage space. (mid priority)
- Refactor (Extraction/Postprocessing) Writer to use the DynamicWriter Abstract Base Class.
** Pipeline
Pipeline is in dire need of refactoring, as it coordinates too many things. The best approach would be to modify the structure to delegate more responsibilities to Steps (such as validation) and Writers (such as writing metadata).
* Testing
- I/O interfaces
- Visualisation helpers and other functions
- Running one pipeline from another
- Groupers
* Documentation
- Tutorials and how-to for the usual tasks
- How to deal with different types of data
- How to aggregate data from multiple experiments
- Contribution guidelines (after developing some)
* Tools/alternatives that may be worth considering for the future
- trio/asyncio/anyio for concurrent processing of individual threads
- Pandas -> Polars: Reconsider after pandas 2.0; they will become interoperable
- awkward arrays: Better way to represent data series with different sizes
- h5py -> zarr: OME-ZARR format is out now, it is possible that the field will move in that direction. This would also make us being stuck in h5py 2.10.0 less egregious.
- Use CellACDC's work on producing a common interface to access a multitude of segmentation algorithms.
* Secrets in the code
- As aliby is adapted to future Python versions, keep up with the "FUTURE" statements that enunciate how code can be improved in new python version
- Track FIXMEs and, if we cannot solve them immediately, open an associated issue
* Minor inconveniences to fix
- Update CellTracker models by training with current scikit-learn (currently it warns that the models were trained in an older version of sklearn )
2022-10-10 15:31:27,350 - INFO
Swain Lab microscope experiment log file
GIT commit: e5d5e33 fix: changes to a few issues with focus control on Batman.
Microscope name: Batman
Date: 022-10-10 15:31:27
Log file path: D:\AcquisitionDataBatman\Swain Lab\Ivan\RAW DATA\2022\Oct\10-Oct-2022\pH_med_to_low00\pH_med_to_low.log
Micromanager config file: C:\Users\Public\Microscope control\Micromanager config files\Batman_python_15_4_22.cfg
Omero project: Default project
Omero tags:
Experiment details: Effect on growth and cytoplasmic pH of switch from normal pH (4.25) media to higher pH (5.69). Switching is run using the Oxygen software
-----Acquisition settings-----
2022-10-10 15:31:27,350 - INFO Image Configs:
Image config,Channel,Description,Exposure (ms), Number of Z sections,Z spacing (um),Sectioning method
brightfield1,Brightfield,Default bright field config,30,5,0.6,PIFOC
pHluorin405_0_4,pHluorin405,Phluorin excitation from 405 LED 0.4v and 10ms exposure,5,1,0.6,PIFOC
pHluorin488_0_4,GFPFast,Phluorin excitation from 488 LED 0.4v,10,1,0.6,PIFOC
cy5,cy5,Default cy5,30,1,0.6,PIFOC
Device properties:
Image config,device,property,value
pHluorin405_0_4,DTOL-DAC-1,Volts,0.4
pHluorin488_0_4,DTOL-DAC-2,Volts,0.4
cy5,DTOL-DAC-3,Volts,4
2022-10-10 15:31:27,353 - INFO
group: YST_247 field: position
Name, X, Y, Z, Autofocus offset
YST_247_001,-8968,-3319,2731.125040696934,123.25
YST_247_002,-8953,-3091,2731.3000406995416,123.25
YST_247_003,-8954,-2849,2731.600040704012,122.8
YST_247_004,-8941,-2611,2730.7750406917185,122.8
YST_247_005,-8697,-2541,2731.4500407017767,118.6
group: YST_247 field: time
start: 0
interval: 300
frames: 180
group: YST_247 field: config
brightfield1: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin405_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin488_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
cy5: 0xfffffffffffffffffffffffffffffffffffffffffffff
2022-10-10 15:31:27,356 - INFO
group: YST_1510 field: position
Name,X,Y,Z,Autofocus offset
YST_1510_001,-6450,-230,2343.300034917891,112.55
YST_1510_002,-6450,-436,2343.350034918636,112.55
YST_1510_003,-6450,-639,2344.000034928322,116.8
YST_1510_004,-6450,-831,2344.250034932047,116.8
YST_1510_005,-6848,-536,2343.3250349182636,110
group: YST_1510 field: time
start: 0
interval: 300
frames: 180
group: YST_1510 field: config
brightfield1: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin405_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin488_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
cy5: 0xfffffffffffffffffffffffffffffffffffffffffffff
2022-10-10 15:31:27,359 - INFO
group: YST_1511 field: position
Name, X, Y, Z, Autofocus offset
YST_1511_001,-10618,-1675,2716.900040484965,118.7
YST_1511_002,-10618,-1914,2717.2250404898077,122.45
YST_1511_003,-10367,-1695,2718.2500405050814,120.95
YST_1511_004,-10367,-1937,2718.8250405136496,120.95
YST_1511_005,-10092,-1757,2719.975040530786,119.45
group: YST_1511 field: time
start: 0
interval: 300
frames: 180
group: YST_1511 field: config
brightfield1: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin405_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin488_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
cy5: 0xfffffffffffffffffffffffffffffffffffffffffffff
2022-10-10 15:31:27,362 - INFO
group: YST_1512 field: position
Name,X,Y,Z,Autofocus offset
YST_1512_001,-8173,-2510,2339.0750348549336,115.65
YST_1512_002,-8173,-2718,2338.0250348392874,110.8
YST_1512_003,-8173,-2963,2336.625034818426,110.8
YST_1512_004,-8457,-2963,2336.350034814328,110.9
YST_1512_005,-8481,-2706,2337.575034832582,113.3
group: YST_1512 field: time
start: 0
interval: 300
frames: 180
group: YST_1512 field: config
brightfield1: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin405_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin488_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
cy5: 0xfffffffffffffffffffffffffffffffffffffffffffff
2022-10-10 15:31:27,365 - INFO
group: YST_1513 field: position
Name,X,Y,Z,Autofocus offset
YST_1513_001,-6978,-2596,2339.8750348668545,113.3
YST_1513_002,-6978,-2380,2340.500034876168,113.3
YST_1513_003,-6971,-2163,2340.8750348817557,113.3
YST_1513_004,-6971,-1892,2341.2500348873436,113.3
YST_1513_005,-6692,-1892,2341.550034891814,113.3
group: YST_1513 field: time
start: 0
interval: 300
frames: 180
group: YST_1513 field: config
brightfield1: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin405_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
pHluorin488_0_4: 0xfffffffffffffffffffffffffffffffffffffffffffff
cy5: 0xfffffffffffffffffffffffffffffffffffffffffffff
2022-10-10 15:31:27,365 - INFO
2022-10-10 15:31:27,365 - INFO
-----Experiment started-----
This diff is collapsed.
[tool.poetry] [tool.poetry]
name = "aliby" name = "aliby"
version = "0.1.55" version = "0.1.64"
description = "Process and analyse live-cell imaging data" description = "Process and analyse live-cell imaging data"
authors = ["Alan Munoz <alan.munoz@ed.ac.uk>"] authors = ["Alan Munoz <alan.munoz@ed.ac.uk>"]
packages = [ packages = [
...@@ -14,6 +14,8 @@ readme = "README.md" ...@@ -14,6 +14,8 @@ readme = "README.md"
[tool.poetry.scripts] [tool.poetry.scripts]
aliby-run = "aliby.bin.run:run" aliby-run = "aliby.bin.run:run"
aliby-annotate = "aliby.bin.annotate:annotate"
aliby-visualise = "aliby.bin.visualise:napari_overlay"
[build-system] [build-system]
requires = ["setuptools", "poetry-core>=1.0.0"] requires = ["setuptools", "poetry-core>=1.0.0"]
...@@ -24,9 +26,10 @@ python = ">=3.8, <3.11" ...@@ -24,9 +26,10 @@ python = ">=3.8, <3.11"
PyYAML = "^6.0" PyYAML = "^6.0"
flatten-dict = "^0.4.2" flatten-dict = "^0.4.2"
gaussianprocessderivatives = "^0.1.5" gaussianprocessderivatives = "^0.1.5"
h5py = "2.10" # File I/O
numpy = ">=1.21.6" numpy = ">=1.21.6"
opencv-python = "4.1.2.30" Bottleneck = "^1.3.5"
opencv-python = "^4.7.0.72"
pathos = "^0.2.8" # Lambda-friendly multithreading
p-tqdm = "^1.3.3" p-tqdm = "^1.3.3"
pandas = ">=1.3.3" pandas = ">=1.3.3"
py-find-1st = "^1.1.5" # Fast indexing py-find-1st = "^1.1.5" # Fast indexing
...@@ -34,30 +37,32 @@ scikit-learn = ">=1.0.2" # Used for an extraction metric ...@@ -34,30 +37,32 @@ scikit-learn = ">=1.0.2" # Used for an extraction metric
scipy = ">=1.7.3" scipy = ">=1.7.3"
# Pipeline + I/O # Pipeline + I/O
aliby-baby = "^0.1.15"
dask = "^2021.12.0" dask = "^2021.12.0"
imageio = "2.8.0" # For image-visualisation utilities imageio = "2.8.0" # For image-visualisation utilities
requests-toolbelt = "^0.9.1" requests-toolbelt = "^0.9.1"
scikit-image = ">=0.18.1" scikit-image = ">=0.18.1"
tqdm = "^4.62.3" # progress bars tqdm = "^4.62.3" # progress bars
xmltodict = "^0.13.0" # read ome-tiff metadata xmltodict = "^0.13.0" # read ome-tiff metadata
zarr = "^2.14.0"
GitPython = "^3.1.27"
h5py = "2.10" # File I/O
# Networking
omero-py = { version = ">=5.6.2", optional = true } # contact omero server
# Baby segmentation
aliby-baby = {version = "^0.1.17", optional=true}
# Postprocessing # Postprocessing
[tool.poetry.group.pp.dependencies]
leidenalg = "^0.8.8" leidenalg = "^0.8.8"
more-itertools = "^8.12.0" more-itertools = "^8.12.0"
pathos = "^0.2.8" # Lambda-friendly multithreading
pycatch22 = "^0.4.2" pycatch22 = "^0.4.2"
# Networking [tool.poetry.group.pp]
omero-py = { version = ">=5.6.2", optional = true } # contact omero server optional = true
zeroc-ice = { version="3.6.5", optional = true } # networking interface, slow to build
GitPython = "^3.1.27"
Bottleneck = "^1.3.5"
zarr = "^2.14.0"
[tool.poetry.extras]
omero = [ "omero-py" ]
network = [ "omero-py", "zeroc-ice" ]
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true
...@@ -74,10 +79,10 @@ pre-commit = "^2.20.0" ...@@ -74,10 +79,10 @@ pre-commit = "^2.20.0"
seaborn = "^0.11.2" seaborn = "^0.11.2"
debugpy = "^1.6.3" debugpy = "^1.6.3"
coverage = "^7.0.4" coverage = "^7.0.4"
# [tool.poetry.group.gui.dependencies]
# napari = ">=0.4.16"
jupytext = "^1.14.4" jupytext = "^1.14.4"
grid-strategy = "^0.0.1"
readchar = "^4.0.3"
ipdb = "^0.13.11"
[tool.poetry.group.docs] [tool.poetry.group.docs]
optional = true optional = true
...@@ -94,6 +99,21 @@ optional = true ...@@ -94,6 +99,21 @@ optional = true
[tool.poetry.group.test.dependencies] [tool.poetry.group.test.dependencies]
pytest = "^6.2.5" pytest = "^6.2.5"
[tool.poetry.group.utils]
optional = true
# Dependency groups can only be used by a poetry installation, not pip
[tool.poetry.group.utils.dependencies]
napari = {version = ">=0.4.16", optional=true}
Torch = {version = "^1.13.1", optional=true}
pytorch-lightning = {version = "^1.9.3", optional=true}
torchvision = {version = "^0.14.1", optional=true}
trio = {version = "^0.22.0", optional=true}
grid-strategy = {version = "^0.0.1", optional=true}
[tool.poetry.extras]
omero = ["omero-py"]
baby = ["aliby-baby"]
[tool.black] [tool.black]
line-length = 79 line-length = 79
......
...@@ -3,7 +3,7 @@ import typing as t ...@@ -3,7 +3,7 @@ import typing as t
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections.abc import Iterable from collections.abc import Iterable
from copy import copy from copy import copy
from pathlib import Path, PosixPath from pathlib import Path
from time import perf_counter from time import perf_counter
from typing import Union from typing import Union
...@@ -60,14 +60,14 @@ class ParametersABC(ABC): ...@@ -60,14 +60,14 @@ class ParametersABC(ABC):
else: else:
return iterable return iterable
def to_yaml(self, path: Union[PosixPath, str] = None): def to_yaml(self, path: Union[Path, str] = None):
""" """
Returns a yaml stream of the attributes of the class instance. Returns a yaml stream of the attributes of the class instance.
If path is provided, the yaml stream is saved there. If path is provided, the yaml stream is saved there.
Parameters Parameters
---------- ----------
path : Union[PosixPath, str] path : Union[Path, str]
Output path. Output path.
""" """
if path: if path:
...@@ -80,7 +80,7 @@ class ParametersABC(ABC): ...@@ -80,7 +80,7 @@ class ParametersABC(ABC):
return cls(**d) return cls(**d)
@classmethod @classmethod
def from_yaml(cls, source: Union[PosixPath, str]): def from_yaml(cls, source: Union[Path, str]):
""" """
Returns instance from a yaml filename or stdin Returns instance from a yaml filename or stdin
""" """
...@@ -202,7 +202,7 @@ class ProcessABC(ABC): ...@@ -202,7 +202,7 @@ class ProcessABC(ABC):
def run(self): def run(self):
pass pass
def _log(self, message: str, level: str = "warn"): def _log(self, message: str, level: str = "warning"):
# Log messages in the corresponding level # Log messages in the corresponding level
logger = logging.getLogger("aliby") logger = logging.getLogger("aliby")
getattr(logger, level)(f"{self.__class__.__name__}: {message}") getattr(logger, level)(f"{self.__class__.__name__}: {message}")
...@@ -211,7 +211,7 @@ class ProcessABC(ABC): ...@@ -211,7 +211,7 @@ class ProcessABC(ABC):
def check_type_recursive(val1, val2): def check_type_recursive(val1, val2):
same_types = True same_types = True
if not isinstance(val1, type(val2)) and not all( if not isinstance(val1, type(val2)) and not all(
type(x) in (PosixPath, str) for x in (val1, val2) # Ignore str->path type(x) in (Path, str) for x in (val1, val2) # Ignore str->path
): ):
return False return False
if not isinstance(val1, t.Iterable) and not isinstance(val2, t.Iterable): if not isinstance(val1, t.Iterable) and not isinstance(val2, t.Iterable):
...@@ -249,5 +249,5 @@ class StepABC(ProcessABC): ...@@ -249,5 +249,5 @@ class StepABC(ProcessABC):
return self._run_tp(tp, **kwargs) return self._run_tp(tp, **kwargs)
def run(self): def run(self):
# Replace run withn run_tp # Replace run with run_tp
raise Warning("Steps use run_tp instead of run") raise Warning("Steps use run_tp instead of run")
...@@ -162,5 +162,8 @@ def image_creds_from_h5(fpath: str): ...@@ -162,5 +162,8 @@ def image_creds_from_h5(fpath: str):
attrs = attrs_from_h5(fpath) attrs = attrs_from_h5(fpath)
return ( return (
attrs["image_id"], attrs["image_id"],
yaml.safe_load(attrs["parameters"])["general"]["server_info"], {
k: yaml.safe_load(attrs["parameters"])["general"][k]
for k in ("username", "password", "host")
},
) )
This diff is collapsed.
""" """
Anthology of interfaces for different parsers and lack of them. Anthology of interfaces fordispatch_metadata_parse different parsers and lack of them.
ALIBY decides on using different metadata parsers based on two elements: ALIBY decides on using different metadata parsers based on two elements:
...@@ -12,10 +12,11 @@ If there are no metadata files, ALIBY requires indicating indices for tiler, seg ...@@ -12,10 +12,11 @@ If there are no metadata files, ALIBY requires indicating indices for tiler, seg
""" """
import glob import glob
import logging
import os import os
import typing as t import typing as t
from datetime import datetime from datetime import datetime
from pathlib import Path, PosixPath from pathlib import Path
import pandas as pd import pandas as pd
from pytz import timezone from pytz import timezone
...@@ -97,7 +98,9 @@ def find_file(root_dir, regex): ...@@ -97,7 +98,9 @@ def find_file(root_dir, regex):
) )
file = [sorted(file)[0]] file = [sorted(file)[0]]
if len(file) == 0: if len(file) == 0:
print("Warning:Metadata: No valid swainlab .log found.") logging.getLogger("aliby").log(
logging.WARNING, "Metadata: No valid swainlab .log found."
)
else: else:
return file[0] return file[0]
return None return None
...@@ -173,7 +176,7 @@ def get_meta_from_legacy(parsed_metadata: dict): ...@@ -173,7 +176,7 @@ def get_meta_from_legacy(parsed_metadata: dict):
return result return result
def parse_swainlab_metadata(filedir: t.Union[str, PosixPath]): def parse_swainlab_metadata(filedir: t.Union[str, Path]):
""" """
Dispatcher function that determines which parser to use based on the file ending. Dispatcher function that determines which parser to use based on the file ending.
...@@ -202,7 +205,7 @@ def parse_swainlab_metadata(filedir: t.Union[str, PosixPath]): ...@@ -202,7 +205,7 @@ def parse_swainlab_metadata(filedir: t.Union[str, PosixPath]):
return minimal_meta return minimal_meta
def dispatch_metadata_parser(filepath: t.Union[str, PosixPath]): def dispatch_metadata_parser(filepath: t.Union[str, Path]):
""" """
Function to dispatch different metadata parsers that convert logfiles into a Function to dispatch different metadata parsers that convert logfiles into a
basic metadata dictionary. Currently only contains the swainlab log parsers. basic metadata dictionary. Currently only contains the swainlab log parsers.
...@@ -219,7 +222,7 @@ def dispatch_metadata_parser(filepath: t.Union[str, PosixPath]): ...@@ -219,7 +222,7 @@ def dispatch_metadata_parser(filepath: t.Union[str, PosixPath]):
return parsed_meta return parsed_meta
def dir_to_meta(path: PosixPath, suffix="tiff"): def dir_to_meta(path: Path, suffix="tiff"):
filenames = list(path.glob(f"*.{suffix}")) filenames = list(path.glob(f"*.{suffix}"))
try: try:
......
...@@ -2,7 +2,7 @@ import logging ...@@ -2,7 +2,7 @@ import logging
import typing as t import typing as t
from copy import copy from copy import copy
from functools import cached_property, lru_cache from functools import cached_property, lru_cache
from pathlib import PosixPath from pathlib import Path
import bottleneck as bn import bottleneck as bn
import h5py import h5py
...@@ -11,7 +11,7 @@ import pandas as pd ...@@ -11,7 +11,7 @@ import pandas as pd
from agora.io.bridge import BridgeH5 from agora.io.bridge import BridgeH5
from agora.io.decorators import _first_arg_str_to_df from agora.io.decorators import _first_arg_str_to_df
from agora.utils.association import validate_association from agora.utils.indexing import validate_association
from agora.utils.kymograph import add_index_levels from agora.utils.kymograph import add_index_levels
from agora.utils.merge import apply_merges from agora.utils.merge import apply_merges
...@@ -23,7 +23,7 @@ class Signal(BridgeH5): ...@@ -23,7 +23,7 @@ class Signal(BridgeH5):
Signal assumes that the metadata and data are accessible to perform time-adjustments and apply previously recorded post-processes. Signal assumes that the metadata and data are accessible to perform time-adjustments and apply previously recorded post-processes.
""" """
def __init__(self, file: t.Union[str, PosixPath]): def __init__(self, file: t.Union[str, Path]):
"""Define index_names for dataframes, candidate fluorescence channels, and composite statistics.""" """Define index_names for dataframes, candidate fluorescence channels, and composite statistics."""
super().__init__(file, flag=None) super().__init__(file, flag=None)
self.index_names = ( self.index_names = (
...@@ -47,20 +47,25 @@ class Signal(BridgeH5): ...@@ -47,20 +47,25 @@ class Signal(BridgeH5):
def __getitem__(self, dsets: t.Union[str, t.Collection]): def __getitem__(self, dsets: t.Union[str, t.Collection]):
"""Get and potentially pre-process data from h5 file and return as a dataframe.""" """Get and potentially pre-process data from h5 file and return as a dataframe."""
if isinstance(dsets, str): # no pre-processing if isinstance(dsets, str): # no pre-processing
df = self.get_raw(dsets) return self.get(dsets)
return self.add_name(df, dsets)
elif isinstance(dsets, list): # pre-processing elif isinstance(dsets, list): # pre-processing
is_bgd = [dset.endswith("imBackground") for dset in dsets] is_bgd = [dset.endswith("imBackground") for dset in dsets]
# Check we are not comaring tile-indexed and cell-indexed data # Check we are not comparing tile-indexed and cell-indexed data
assert sum(is_bgd) == 0 or sum(is_bgd) == len( assert sum(is_bgd) == 0 or sum(is_bgd) == len(
dsets dsets
), "Tile data and cell data can't be mixed" ), "Tile data and cell data can't be mixed"
return [ return [self.get(dset) for dset in dsets]
self.add_name(self.apply_prepost(dset), dset) for dset in dsets
]
else: else:
raise Exception(f"Invalid type {type(dsets)} to get datasets") raise Exception(f"Invalid type {type(dsets)} to get datasets")
def get(self, dsets: t.Union[str, t.Collection], **kwargs):
"""Get and potentially pre-process data from h5 file and return as a dataframe."""
if isinstance(dsets, str): # no pre-processing
df = self.get_raw(dsets, **kwargs)
prepost_applied = self.apply_prepost(dsets, **kwargs)
return self.add_name(prepost_applied, dsets)
@staticmethod @staticmethod
def add_name(df, name): def add_name(df, name):
"""Add column of identical strings to a dataframe.""" """Add column of identical strings to a dataframe."""
...@@ -129,18 +134,24 @@ class Signal(BridgeH5): ...@@ -129,18 +134,24 @@ class Signal(BridgeH5):
Returns an array with three columns: the tile id, the mother label, and the daughter label. Returns an array with three columns: the tile id, the mother label, and the daughter label.
""" """
if lineage_location is None: if lineage_location is None:
lineage_location = "postprocessing/lineage" lineage_location = "modifiers/lineage_merged"
if merged:
lineage_location += "_merged"
with h5py.File(self.filename, "r") as f: with h5py.File(self.filename, "r") as f:
# if lineage_location not in f:
# lineage_location = lineage_location.split("_")[0]
if lineage_location not in f:
lineage_location = "postprocessing/lineage"
tile_mo_da = f[lineage_location] tile_mo_da = f[lineage_location]
lineage = np.array(
( if isinstance(tile_mo_da, h5py.Dataset):
tile_mo_da["trap"], lineage = tile_mo_da[()]
tile_mo_da["mother_label"], else:
tile_mo_da["daughter_label"], lineage = np.array(
) (
).T tile_mo_da["trap"],
tile_mo_da["mother_label"],
tile_mo_da["daughter_label"],
)
).T
return lineage return lineage
@_first_arg_str_to_df @_first_arg_str_to_df
...@@ -171,7 +182,7 @@ class Signal(BridgeH5): ...@@ -171,7 +182,7 @@ class Signal(BridgeH5):
""" """
if isinstance(merges, bool): if isinstance(merges, bool):
merges: np.ndarray = self.get_merges() if merges else np.array([]) merges: np.ndarray = self.load_merges() if merges else np.array([])
if merges.any(): if merges.any():
merged = apply_merges(data, merges) merged = apply_merges(data, merges)
else: else:
...@@ -203,9 +214,8 @@ class Signal(BridgeH5): ...@@ -203,9 +214,8 @@ class Signal(BridgeH5):
merged = pd.DataFrame([], index=index) merged = pd.DataFrame([], index=index)
return merged return merged
# Alan: do we need two similar properties - see below? @cached_property
@property def p_available(self):
def datasets(self):
"""Print data sets available in h5 file.""" """Print data sets available in h5 file."""
if not hasattr(self, "_available"): if not hasattr(self, "_available"):
self._available = [] self._available = []
...@@ -214,11 +224,6 @@ class Signal(BridgeH5): ...@@ -214,11 +224,6 @@ class Signal(BridgeH5):
for sig in self._available: for sig in self._available:
print(sig) print(sig)
@cached_property
def p_available(self):
"""Print data sets available in h5 file."""
self.datasets
@cached_property @cached_property
def available(self): def available(self):
"""Get data sets available in h5 file.""" """Get data sets available in h5 file."""
...@@ -298,7 +303,7 @@ class Signal(BridgeH5): ...@@ -298,7 +303,7 @@ class Signal(BridgeH5):
self._log(f"Could not fetch dataset {dataset}: {e}", "error") self._log(f"Could not fetch dataset {dataset}: {e}", "error")
raise e raise e
def get_merges(self): def load_merges(self):
"""Get merge events going up to the first level.""" """Get merge events going up to the first level."""
with h5py.File(self.filename, "r") as f: with h5py.File(self.filename, "r") as f:
merges = f.get("modifiers/merges", np.array([])) merges = f.get("modifiers/merges", np.array([]))
...@@ -315,7 +320,9 @@ class Signal(BridgeH5): ...@@ -315,7 +320,9 @@ class Signal(BridgeH5):
with h5py.File(self.filename, "r") as f: with h5py.File(self.filename, "r") as f:
picks = set() picks = set()
if path in f: if path in f:
picks = set(zip(*[f[path + name] for name in names])) picks = set(
zip(*[f[path + name] for name in names if name in f[path]])
)
return picks return picks
def dataset_to_df(self, f: h5py.File, path: str) -> pd.DataFrame: def dataset_to_df(self, f: h5py.File, path: str) -> pd.DataFrame:
......
...@@ -5,14 +5,12 @@ import itertools ...@@ -5,14 +5,12 @@ import itertools
import logging import logging
import operator import operator
import typing as t import typing as t
from functools import partial, wraps from functools import wraps
from pathlib import Path from pathlib import Path
from time import perf_counter from time import perf_counter
from typing import Callable from typing import Callable
import cv2 import cv2
import h5py
import numpy as np
def repr_obj(obj, indent=0): def repr_obj(obj, indent=0):
......
...@@ -11,7 +11,6 @@ import yaml ...@@ -11,7 +11,6 @@ import yaml
from utils_find_1st import cmp_equal, find_1st from utils_find_1st import cmp_equal, find_1st
from agora.io.bridge import BridgeH5 from agora.io.bridge import BridgeH5
from agora.io.utils import timed
#################### Dynamic version ################################## #################### Dynamic version ##################################
...@@ -173,7 +172,6 @@ class DynamicWriter: ...@@ -173,7 +172,6 @@ class DynamicWriter:
# append or create new dataset # append or create new dataset
self._append(value, key, hgroup) self._append(value, key, hgroup)
except Exception as e: except Exception as e:
print(key, value)
self._log( self._log(
f"{key}:{value} could not be written: {e}", "error" f"{key}:{value} could not be written: {e}", "error"
) )
...@@ -231,7 +229,6 @@ class LinearBabyWriter(DynamicWriter): ...@@ -231,7 +229,6 @@ class LinearBabyWriter(DynamicWriter):
Assumes the edgemasks are of form ((None, tile_size, tile_size), bool). Assumes the edgemasks are of form ((None, tile_size, tile_size), bool).
""" """
# TODO make this YAML: Alan: why?
compression = "gzip" compression = "gzip"
_default_tile_size = 117 _default_tile_size = 117
datatypes = { datatypes = {
...@@ -320,11 +317,7 @@ class StateWriter(DynamicWriter): ...@@ -320,11 +317,7 @@ class StateWriter(DynamicWriter):
@staticmethod @staticmethod
def format_values_tpback(states: list, val_name: str): def format_values_tpback(states: list, val_name: str):
"""Unpacks a dict of state data into tp_back, trap, value.""" """Unpacks a dict of state data into tp_back, trap, value."""
# initialise as empty lists
# Alan: is this initialisation necessary?
tp_back, trap, value = [
[[] for _ in states[0][val_name]] for _ in range(3)
]
# store results as a list of tuples # store results as a list of tuples
lbl_tuples = [ lbl_tuples = [
(tp_back, trap, cell_label) (tp_back, trap, cell_label)
...@@ -335,6 +328,11 @@ class StateWriter(DynamicWriter): ...@@ -335,6 +328,11 @@ class StateWriter(DynamicWriter):
# unpack list of tuples to define variables # unpack list of tuples to define variables
if len(lbl_tuples): if len(lbl_tuples):
tp_back, trap, value = zip(*lbl_tuples) tp_back, trap, value = zip(*lbl_tuples)
else:
# set as empty lists
tp_back, trap, value = [
[[] for _ in states[0][val_name]] for _ in range(3)
]
return tp_back, trap, value return tp_back, trap, value
@staticmethod @staticmethod
...@@ -410,9 +408,9 @@ class StateWriter(DynamicWriter): ...@@ -410,9 +408,9 @@ class StateWriter(DynamicWriter):
#################### Extraction version ############################### #################### Extraction version ###############################
class Writer(BridgeH5): class Writer(BridgeH5):
"""Class to transform data into compatible structures.""" """
Class to transform data into compatible structures.
# Alan: when is this used? Used by Extractor and Postprocessor within the pipeline."""
def __init__(self, filename, flag=None, compression="gzip"): def __init__(self, filename, flag=None, compression="gzip"):
""" """
...@@ -474,7 +472,7 @@ class Writer(BridgeH5): ...@@ -474,7 +472,7 @@ class Writer(BridgeH5):
self.write_pd(f, path, data, compression=self.compression) self.write_pd(f, path, data, compression=self.compression)
# data is a multi-index dataframe # data is a multi-index dataframe
elif isinstance(data, pd.MultiIndex): elif isinstance(data, pd.MultiIndex):
# Alan: should we still not compress here? # TODO: benchmark I/O speed when using compression
self.write_index(f, path, data) # , compression=self.compression) self.write_index(f, path, data) # , compression=self.compression)
# data is a dictionary of dataframes # data is a dictionary of dataframes
elif isinstance(data, Dict) and np.all( elif isinstance(data, Dict) and np.all(
...@@ -551,7 +549,7 @@ class Writer(BridgeH5): ...@@ -551,7 +549,7 @@ class Writer(BridgeH5):
compression=kwargs.get("compression", None), compression=kwargs.get("compression", None),
) )
dset = f[values_path] dset = f[values_path]
dset[()] = df.values dset[()] = df.values.astype("float16")
# create dateset and write indices # create dateset and write indices
if not len(df): # Only write more if not empty if not len(df): # Only write more if not empty
...@@ -568,21 +566,18 @@ class Writer(BridgeH5): ...@@ -568,21 +566,18 @@ class Writer(BridgeH5):
) )
dset = f[indices_path] dset = f[indices_path]
dset[()] = df.index.get_level_values(level=name).tolist() dset[()] = df.index.get_level_values(level=name).tolist()
# create dataset and write columns
if ( # create dataset and write time points as columns
df.columns.dtype == int
or df.columns.dtype == np.dtype("uint")
or df.columns.name == "timepoint"
):
tp_path = path + "/timepoint" tp_path = path + "/timepoint"
f.create_dataset( if tp_path not in f:
name=tp_path, f.create_dataset(
shape=(df.shape[1],), name=tp_path,
maxshape=(max_tps,), shape=(df.shape[1],),
dtype="uint16", maxshape=(max_tps,),
) dtype="uint16",
tps = list(range(df.shape[1])) )
f[tp_path][tps] = tps tps = list(range(df.shape[1]))
f[tp_path][tps] = tps
else: else:
f[path].attrs["columns"] = df.columns.tolist() f[path].attrs["columns"] = df.columns.tolist()
else: else:
......
#!/usr/bin/env jupyter #!/usr/bin/env jupyter
"""
Utilities based on association are used to efficiently acquire indices of tracklets with some kind of relationship.
This can be:
- Cells that are to be merged
- Cells that have a linear relationship
"""
import numpy as np
import typing as t
def validate_association(
association: np.ndarray,
indices: np.ndarray,
match_column: t.Optional[int] = None,
) -> t.Tuple[np.ndarray, np.ndarray]:
"""Select rows from the first array that are present in both.
We use casting for fast multiindexing, generalising for lineage dynamics
Parameters
----------
association : np.ndarray
2-D array where columns are (trap, mother, daughter) or 3-D array where
dimensions are (X, (trap,mother), (trap,daughter))
indices : np.ndarray
2-D array where each column is a different level.
match_column: int
int indicating a specific column is required to match (i.e.
0-1 for target-source when trying to merge tracklets or mother-bud for lineage)
must be present in indices. If it is false one match suffices for the resultant indices
vector to be True.
Returns
-------
np.ndarray
1-D boolean array indicating valid merge events.
np.ndarray
1-D boolean array indicating indices with an association relationship.
Examples
--------
>>> import numpy as np
>>> from agora.utils.association import validate_association
>>> merges = np.array(range(12)).reshape(3,2,2)
>>> indices = np.array(range(6)).reshape(3,2)
>>> print(merges, indices)
>>> print(merges); print(indices)
[[[ 0 1]
[ 2 3]]
[[ 4 5]
[ 6 7]]
[[ 8 9]
[10 11]]]
[[0 1]
[2 3]
[4 5]]
>>> valid_associations, valid_indices = validate_association(merges, indices)
>>> print(valid_associations, valid_indices)
[ True False False] [ True True False]
"""
if association.ndim < 3:
# Reshape into 3-D array for broadcasting if neded
association = np.stack(
(association[:, [0, 1]], association[:, [0, 2]]), axis=1
)
# Compare existing association with available indices
# Swap trap and label axes for the association array to correctly cast
valid_ndassociation = association[..., None] == indices.T[None, ...]
# Broadcasting is confusing (but efficient):
# First we check the dimension across trap and cell id, to ensure both match
valid_cell_ids = valid_ndassociation.all(axis=2)
if match_column is None:
# Then we check the merge tuples to check which cases have both target and source
valid_association = valid_cell_ids.any(axis=2).all(axis=1)
# Finally we check the dimension that crosses all indices, to ensure the pair
# is present in a valid merge event.
valid_indices = (
valid_ndassociation[valid_association].all(axis=2).any(axis=(0, 1))
)
else: # We fetch specific indices if we aim for the ones with one present
valid_indices = valid_cell_ids[:, match_column].any(axis=0)
# Valid association then becomes a boolean array, true means that there is a
# match (match_column) between that cell and the index
valid_association = (
valid_cell_ids[:, match_column] & valid_indices
).any(axis=1)
return valid_association, valid_indices
#!/usr/bin/env jupyter
"""
Convert some types to others
"""
def _str_to_int(x: str or None):
"""
Cast string as int if possible. If Nonetype return None.
"""
if x is not None:
try:
return int(x)
except:
return x