Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Publish to PyPI

on:
release:
types: [published]

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: astral-sh/setup-uv@v6

- name: Build wheel and sdist
run: uv build

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/pyclm
permissions:
id-token: write # required for Trusted Publishing
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ notebooks/gol_snaps/
log.txt
/other/*
*.pdf
figures/**/*.tif
figures/**/*.png
figures/**/*.avi
figures/**/*.mp4
/example_scripts/
# Documentation
documentation/_build/
documentation/_static/
documentation/_generated/
documentation/_zoo_gallery.md
documentation/zoo_sources/*.tif

58 changes: 16 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# PyCLM
A Modular Closed-Loop Microscopy Software in Python

**Documentation available at [readthedocs](https://pyclm.readthedocs.io/en/latest/)**

![](documentation\imgs\Figure%201.png "PyCLM Overview")

## Overview
Expand All @@ -20,55 +22,27 @@ Ideally, the number of possible simultaneous experiments should be limited only
3. **Extensibility.** It should be possible to implement a wide range of new segmentation and stimulation methods on top of the existing software.
- Segmentation and stimulation methods inherit from a base class, and are required to implement three straightforward functions.


## Usage
For first time usage, see [_Installation_](#installation) (below). For a more detailed overview, see [Usage](documentation/usage.md). Running an experiment with PyCLM is split into three parts:

### 1. Designing a multi-experiment:
Multiple experiments can be run simultaneously using PyCLM, without any programming experience.

1. Choose an empty directory to contain the multi-experiment configuration files. This is also where PyCLM will generate the output (data and log files).
2. Copy in .toml files corresponding to the experiment(s) that will be run.
3. Write (or reuse) a schedule.toml file.
4. Choose imaging positions (using the micromanager interface), and assign an experiment to each position.

### 2. Running a multi-experiment:

Once a directory for the multi-experiment has been set up, close out of all existing microscope control software, activate your venv or conda environment, and run main.py with command-line arguments.

- Data is saved continuously during the multi-experiment, and the code execution can be aborted at any time without data loss.
- Experiment progress can be monitored through the experiment log, as well as through continuous .tif export.

### 3. Analyzing a completed multi-experiment:
Data is exported into separate .hdf5 files for each experiment, which alongside acquired images contain important metadata about timing and imaging configurations. These files also track the light pattern applied at each timepoint and the segmentation generated, if applicable.
- These .hdf5 files can be converted to .tif stacks, with or without pattern/segmentation overlay by running the `convert_to_tifs.py` script.
- For convenience, a script for simple tracking (or segmentation and tracking) is also available.

### (Bonus: Reusing experiments)
Ease of reusability is a major advantage of PyCLM. {experiment}.toml files as well as schedule.toml files can be quickly copied over from old experiments to new ones. Running old experiments on a new day is as simple as copying over old files and picking out imaging positions. The details of past experiments are contained in the structure of the experiment setup.

## Installation
_Documentation is currently incomplete. Insterested users are recommended to check back in over the next week as the documentation is continually updated._

Installation can be technically demanding, and ease of installation will depend on your microscope setup. PyCLM is tested on a Nikon Ti2-Eclipse confocal microscope with a Mightex Polygon 1000 DMD, running on a Windows OS.

### 1. Install and configure MicroManager
PyCLM does not communicate with your microscope hardware directly. Instead, it operates through pymmcore-plus, which is built on top of [MicroManager](https://micro-manager.org/), a well-documented microscope control platform with support for a wide range of devices. MicroManager software will be used to determine imaging configurations, and define config groups. It is worth familiarizing yourself with MicroManager before using PyCLM.

### 2. Create a Python virtual environment
Clone this GitHub repository to a directory where you can run code:
PyCLM is installed with [uv](https://docs.astral.sh/uv/).

``git clone https://github.com/Harrison-Oatman/PyCLM.git``
```bash
uv add closed-loop-microscopy
```

### 3. Install and configure pymmcore-plus
Optional extras:

```bash
uv add "closed-loop-microscopy[cellpose]" # CellposeSAM segmentation (requires CUDA)
uv add "closed-loop-microscopy[calibration]" # DMD calibration tools
```

### 4. Install CellposeSAM
### Hardware requirement: Micro-Manager

### 5. Calibrate your DMD
For PyCLM to deliver spatially modulated stimuli, the relationship between DMD pixel coordinates and camera pixel coordinates needs to be determined. MicroManager provides a tool for calibrating the DMD using the "[Projector](https://micro-manager.org/Projector_Plugin)" plugin. This plugin will generate a set of DMD patterns to approximate the affine transform to/from the DMD.
PyCLM controls hardware through [pymmcore-plus](https://github.com/pymmcore-plus/pymmcore-plus), which wraps the [Micro-Manager](https://micro-manager.org) C++ core. The Python package installs without issue, but running a real experiment requires:

### 6. Update pyclm_config.toml
``pyclm_config.toml`` is a configuration file used during every run of PyCLM to get important information about the DMD, including the DMD shape, and the a
1. **Install Micro-Manager** — download the nightly build for your platform from [micro-manager.org](https://micro-manager.org/wiki/Download_Micro-Manager_Latest_Release).
2. **Point PyCLM at your MM installation** — set the `config_path` field in `pyclm_config.toml` to your Micro-Manager `.cfg` hardware configuration file.

Importing the package and using the dry-run (`--dry`) mode work without a Micro-Manager installation.

22 changes: 22 additions & 0 deletions documentation/api/bar_patterns.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Bar Patterns
============

.. autoclass:: pyclm.core.patterns.bar_patterns.StationaryBarPattern
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.bar_patterns.BarPattern
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.bar_patterns.BouncingBarPattern
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.bar_patterns.SawToothMethod
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.bar_patterns.RotatingBarPattern
:members: generate
:show-inheritance:
31 changes: 31 additions & 0 deletions documentation/api/feedback_control_patterns.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Feedback Control Patterns
=========================

These patterns use per-cell segmentation data to direct light to specific
sub-cellular regions based on each cell's position within the field.
All require a ``channel`` argument naming a segmented imaging channel.

.. autoclass:: pyclm.core.patterns.feedback_control_patterns.PerCellPatternMethod
:members: generate, process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.feedback_control_patterns.RotateCcwModel
:members: process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.feedback_control_patterns.MoveOutModel
:members: process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.feedback_control_patterns.MoveInModel
:members: process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.feedback_control_patterns.MoveDownModel
:members: process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.feedback_control_patterns.BounceModel
:members: generate, process_prop
:show-inheritance:

16 changes: 16 additions & 0 deletions documentation/api/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Pattern Method API Reference
============================

Each pattern method is a subclass of :class:`~pyclm.core.patterns.pattern.PatternMethod`.
Implement :meth:`~pyclm.core.patterns.pattern.PatternMethod.generate` to return a
float32 array in ``[0, 1]`` with shape equal to the camera ROI.

.. toctree::
:maxdepth: 1

pattern_base
bar_patterns
wave_patterns
static_patterns
feedback_control_patterns
ktr_patterns
22 changes: 22 additions & 0 deletions documentation/api/ktr_patterns.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
KTR / Nucleus Control Patterns
================================

Patterns that use per-cell intensity from a reporter channel (e.g. a
kinase translocation reporter) to make binary or graded illumination
decisions per cell.

.. autoclass:: pyclm.core.patterns.ktr_patterns.NucleusControlMethod
:members: generate, process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.ktr_patterns.BinaryNucleusClampModel
:members: process_prop
:show-inheritance:

.. autoclass:: pyclm.core.patterns.ktr_patterns.GlobalCycleModel
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.ktr_patterns.CenteredImageModel
:members: generate
:show-inheritance:
15 changes: 15 additions & 0 deletions documentation/api/pattern_base.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Base Classes
============

.. autoclass:: pyclm.core.patterns.pattern.PatternMethod
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: pyclm.core.patterns.pattern.PatternContext
:members:
:show-inheritance:

.. autoclass:: pyclm.core.patterns.zoo.ZooMeta
:members:
:show-inheritance:
10 changes: 10 additions & 0 deletions documentation/api/static_patterns.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Static Patterns
===============

.. autoclass:: pyclm.core.patterns.static_patterns.CirclePattern
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.static_patterns.FullOnPattern
:members: generate
:show-inheritance:
10 changes: 10 additions & 0 deletions documentation/api/wave_patterns.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Wave Patterns
=============

.. autoclass:: pyclm.core.patterns.wave_patterns.StationaryWavePattern
:members: generate
:show-inheritance:

.. autoclass:: pyclm.core.patterns.wave_patterns.WavePattern
:members: generate
:show-inheritance:
10 changes: 9 additions & 1 deletion documentation/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import os
import sys

# Repo root (for finding pyclm as an installed package or editable install)
sys.path.insert(0, os.path.abspath(".."))
# src layout — lets the zoo extension import pyclm directly without a venv install
sys.path.insert(0, os.path.abspath("../src"))
# Extension directory
sys.path.insert(0, os.path.abspath("ext"))

project = "PyCLM"
copyright = "2026, Harrison-Oatman"
Expand All @@ -13,10 +18,13 @@
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"zoo_gallery",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# Exclude the gallery fragment so it is not processed as a standalone page;
# it is only included by method_zoo.md.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "_zoo_gallery.md"]

html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
Loading
Loading