Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
/data/*
notebooks/gol_snaps/
.idea/
Expand Down
24 changes: 23 additions & 1 deletion documentation/first_time_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,34 @@ Pass `--config` if `pyclm_config.toml` is not in the experiment directory or rep
uv run pyclm path/to/experiment_dir --config path/to/pyclm_config.toml
```

Use `--dry` to do a full rehearsal without connecting to the microscope (images are read from a `tif-source/` folder in the working directory):
Use `--dry` to run a full rehearsal without connecting to the microscope:

```bash
uv run pyclm path/to/experiment_dir --dry
```

In dry mode, PyCLM reads simulated images from TIF files placed inside the experiment directory. Positions are loaded from `PositionList.pos` or `multipoints.xml` if present, and each TIF is matched to a position by label (e.g. `on.00.tif` matches position `on.00`) or stem (e.g. `on.tif` matches any position with stem `on`). For explicit control, add a `dry_run.yml` to the experiment directory mapping position names to TIF files:

```yaml
positions:
- name: on.00
x: 0.0
y: 0.0
source: on.tif
- name: off.00
x: 500.0
y: 0.0
source: off.tif
```

Add `--gui` to open a live Napari viewer that updates as data is written:

```bash
uv run pyclm path/to/experiment_dir --dry --gui
```

`--gui` can also be used during a real experiment to monitor output in real time.

**Programmatically** (required when using a custom `PositionMover` or custom pattern/segmentation methods):

```python
Expand Down
2 changes: 1 addition & 1 deletion documentation/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Multiple experiments can be run simultaneously using PyCLM, without any programm
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.
- Experiment progress can be monitored through the experiment log, or in real time using the `--gui` flag, which opens a live Napari viewer that updates as images are acquired.

## 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.
Expand Down
11 changes: 4 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ authors = [
requires-python = ">=3.11"
dependencies = [
"h5py>=3.14.0",
"napari>=0.6.0",
"natsort>=8.4.0",
"notebook>=7.5.1",
"opencv-python>=4.12.0.88",
"pandas-stubs==2.3.2.250827",
"pymmcore-plus==0.13.7",
"pymmcore==11.2.1.71.0",
"pyside6>=6.10.2",
"pyyaml>=6.0.3",
"scikit-image>=0.25.2",
"tifffile>=2025.9.20",
"toml>=0.10.2",
]

[optional-dependencies]
[project.optional-dependencies]
cellpose = [
"cellpose>=4.0.6",
"torch",
Expand Down Expand Up @@ -85,12 +88,6 @@ select = [
pyclm = "pyclm.__main__:main"
convert_hdf5s = "pyclm.convert_hdf5s:main"

[project.optional-dependencies]
analysis = [
"colorcet>=3.1.0",
"colorstamps>=0.1.3",
"seaborn>=0.13.2",
]

[build-system]
requires = ["uv_build>=0.8.14,<0.9.0"]
Expand Down
7 changes: 6 additions & 1 deletion src/pyclm/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def main():
args = process_args()
base_path = Path(args.directory)
run_pyclm(base_path, args.config, dry=args.dry)
run_pyclm(base_path, args.config, dry=args.dry, gui=args.gui)


def process_args():
Expand All @@ -19,6 +19,11 @@ def process_args():
parser.add_argument(
"--dry", action="store_true", help="run without executing the experiment"
)
parser.add_argument(
"--gui",
action="store_true",
help="run with an updating gui to track experiment progress",
)

return parser.parse_args()

Expand Down
15 changes: 6 additions & 9 deletions src/pyclm/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
from .core.position_mover import PositionMover
from .core.real_core import RealMicroscopeCore
from .core.virtual_microscope.simulated_core import SimulatedMicroscopeCore
from .core.virtual_microscope.simulated_source import TimeSeriesImageSource

logger = logging.getLogger(__name__)

Expand All @@ -40,19 +39,15 @@ def __init__(
config="MMConfig_demo.cfg",
dry=False,
position_mover: PositionMover | None = None,
dry_image_source: TimeSeriesImageSource | None = None,
dry_image_source: Path | None = None,
):
if not dry:
# Applies if config specifies that a real microscope is in use
self.core = RealMicroscopeCore()
else:
if dry_image_source is None:
image_source = TimeSeriesImageSource.from_folder(
Path("tif-source"), pattern="*.tif", loop=True
)
else:
image_source = dry_image_source
self.core = SimulatedMicroscopeCore(image_source, slm_device=None)
raise ValueError("dry_image_source must be provided when dry=True.")
self.core = SimulatedMicroscopeCore(dry_image_source, slm_device=None)
self.core.loadSystemConfiguration(config)
self.all_queues = AllQueues()

Expand Down Expand Up @@ -141,7 +136,9 @@ def initialize(
)
self.microscope.declare_slm()
self.outbox.base_path = out_path
self.outbox.initialize(schedule)
all_layers = self.outbox.initialize(schedule, self.core)

return all_layers

def run(self):
with ThreadPoolExecutor() as executor:
Expand Down
9 changes: 8 additions & 1 deletion src/pyclm/convert_hdf5s.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

import cv2
import h5py
import numpy as np
import tifffile
from h5py import File
Expand Down Expand Up @@ -117,6 +118,10 @@ def make_tif(fp, at, chan="channel_638", binning_override=None):
if t_val not in f:
continue
data_group = f[t_val]

if not isinstance(data_group, h5py.Group):
continue

if channel_key not in data_group:
continue

Expand All @@ -135,6 +140,9 @@ def make_tif(fp, at, chan="channel_638", binning_override=None):
data_dset.refresh()
data = np.array(data_dset)

if data.shape == (0, 0):
continue

except Exception as e:
# might happen if writing is in progress for this specific frame
continue
Expand Down Expand Up @@ -207,7 +215,6 @@ def process_args():
help="binning during experiment (autodetected if not sepcified)",
default=None,
)
# Removed binning, overlay_pattern, just_patterns args

return parser.parse_args()

Expand Down
Loading
Loading