PyIncucyte 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyincucyte-0.3.0/LICENSE +28 -0
- pyincucyte-0.3.0/PKG-INFO +355 -0
- pyincucyte-0.3.0/PyIncucyte.egg-info/PKG-INFO +355 -0
- pyincucyte-0.3.0/PyIncucyte.egg-info/SOURCES.txt +41 -0
- pyincucyte-0.3.0/PyIncucyte.egg-info/dependency_links.txt +1 -0
- pyincucyte-0.3.0/PyIncucyte.egg-info/entry_points.txt +3 -0
- pyincucyte-0.3.0/PyIncucyte.egg-info/requires.txt +13 -0
- pyincucyte-0.3.0/PyIncucyte.egg-info/top_level.txt +1 -0
- pyincucyte-0.3.0/README.md +321 -0
- pyincucyte-0.3.0/pyincucyte/__init__.py +113 -0
- pyincucyte-0.3.0/pyincucyte/__main__.py +8 -0
- pyincucyte-0.3.0/pyincucyte/cache.py +169 -0
- pyincucyte-0.3.0/pyincucyte/channels.py +89 -0
- pyincucyte-0.3.0/pyincucyte/cli.py +665 -0
- pyincucyte-0.3.0/pyincucyte/client.py +611 -0
- pyincucyte-0.3.0/pyincucyte/compat.py +68 -0
- pyincucyte-0.3.0/pyincucyte/config.py +130 -0
- pyincucyte-0.3.0/pyincucyte/engine.py +1667 -0
- pyincucyte-0.3.0/pyincucyte/errors.py +68 -0
- pyincucyte-0.3.0/pyincucyte/gui/__init__.py +5 -0
- pyincucyte-0.3.0/pyincucyte/gui/__main__.py +8 -0
- pyincucyte-0.3.0/pyincucyte/gui/app.py +1447 -0
- pyincucyte-0.3.0/pyincucyte/gui/dialogs.py +285 -0
- pyincucyte-0.3.0/pyincucyte/gui/theme.py +398 -0
- pyincucyte-0.3.0/pyincucyte/gui/widgets.py +620 -0
- pyincucyte-0.3.0/pyincucyte/manifest.py +162 -0
- pyincucyte-0.3.0/pyincucyte/models.py +446 -0
- pyincucyte-0.3.0/pyincucyte/options.py +491 -0
- pyincucyte-0.3.0/pyincucyte/state.py +190 -0
- pyincucyte-0.3.0/pyincucyte/watch.py +177 -0
- pyincucyte-0.3.0/pyincucyte/wells.py +114 -0
- pyincucyte-0.3.0/pyproject.toml +67 -0
- pyincucyte-0.3.0/setup.cfg +4 -0
- pyincucyte-0.3.0/tests/test_cache.py +153 -0
- pyincucyte-0.3.0/tests/test_cli.py +157 -0
- pyincucyte-0.3.0/tests/test_client.py +265 -0
- pyincucyte-0.3.0/tests/test_frame_counts.py +213 -0
- pyincucyte-0.3.0/tests/test_gui_threading.py +63 -0
- pyincucyte-0.3.0/tests/test_options.py +145 -0
- pyincucyte-0.3.0/tests/test_packaging.py +140 -0
- pyincucyte-0.3.0/tests/test_stack_downloads.py +236 -0
- pyincucyte-0.3.0/tests/test_time_window.py +196 -0
- pyincucyte-0.3.0/tests/test_wells_and_state.py +143 -0
pyincucyte-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Jamie Malcolm
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
may be used to endorse or promote products derived from this software
|
|
17
|
+
without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyIncucyte
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Download Incucyte live-cell imaging TIFFs from Python, a CLI, or a desktop app.
|
|
5
|
+
Author: Jamie Malcolm
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/Jay2owe/PyIncucyteGUI
|
|
8
|
+
Project-URL: Source, https://github.com/Jay2owe/PyIncucyteGUI
|
|
9
|
+
Project-URL: Issues, https://github.com/Jay2owe/PyIncucyteGUI/issues
|
|
10
|
+
Keywords: incucyte,pyincucyte,microscopy,live-cell imaging,imagej,tiff,automation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests>=2.31
|
|
24
|
+
Requires-Dist: urllib3>=2.0
|
|
25
|
+
Requires-Dist: Pillow>=10.0
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Requires-Dist: tifffile>=2023.7.10
|
|
28
|
+
Requires-Dist: pythonnet>=3.0; sys_platform == "win32"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# PyIncucyte
|
|
36
|
+
|
|
37
|
+
Download images off an Incucyte live-cell imaging system — from a desktop app, a
|
|
38
|
+
command line, or a Python script. It replaces click-by-click downloading with a
|
|
39
|
+
tool that knows what it already has and fetches only what is new.
|
|
40
|
+
|
|
41
|
+
Three front ends, one engine:
|
|
42
|
+
|
|
43
|
+
| You want to | Use |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Pick a plate and grab the images | the desktop app — `pyincucyte gui` |
|
|
46
|
+
| Script it, or run it on a schedule | the CLI — `pyincucyte download …` |
|
|
47
|
+
| Call it from an analysis pipeline | the API — `from pyincucyte import IncucyteClient` |
|
|
48
|
+
|
|
49
|
+
## Requirements
|
|
50
|
+
|
|
51
|
+
- Python 3.10+
|
|
52
|
+
- A network route to the instrument (at Imperial the Incucyte is on the internal
|
|
53
|
+
network only — it is not reachable from outside)
|
|
54
|
+
- Windows with the Incucyte desktop client installed, **for the login step only**.
|
|
55
|
+
The password is hashed by the vendor's own .NET assembly before it leaves the
|
|
56
|
+
machine, and only that hash is stored. Once a machine has logged in, the saved
|
|
57
|
+
credentials work without it.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install PyIncucyte # from PyPI
|
|
63
|
+
pip install -e . # from this checkout
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
One name throughout: the distribution, the import name and the command are all
|
|
67
|
+
`pyincucyte`. The desktop app is a module inside it, so `pyincucyte gui` and
|
|
68
|
+
`pyincucyte download ...` are the same program.
|
|
69
|
+
|
|
70
|
+
Installing does not replace `py-incucyte-gui`, the name this was published
|
|
71
|
+
under before 0.3. Uninstall that one if it is still on the machine:
|
|
72
|
+
`pip uninstall py-incucyte-gui`.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## The desktop app
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pyincucyte gui # or: pyincucyte-gui, python -m pyincucyte.gui
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Sign in, pick a vessel, paint the wells you want, choose a layout, press
|
|
83
|
+
**Download**. Everything long-running happens on a worker thread, so the window
|
|
84
|
+
stays usable and **Cancel** in the status bar always works.
|
|
85
|
+
|
|
86
|
+
Worth knowing:
|
|
87
|
+
|
|
88
|
+
- **Preview** (Ctrl+P) counts exactly what would be downloaded and lists the
|
|
89
|
+
filenames, without fetching a single image.
|
|
90
|
+
- **Scanned only** keeps just the wells the instrument actually imaged in the
|
|
91
|
+
most recent scan — wells with no data are shown dimmed on the plate.
|
|
92
|
+
- **Copy CLI command** (Tools menu, and in the confirm dialog) turns whatever is
|
|
93
|
+
on screen into the equivalent `pyincucyte` command, ready to paste into a
|
|
94
|
+
pipeline script or a scheduled task.
|
|
95
|
+
- **Presets** (File menu) save the whole recipe as JSON. The same file works as
|
|
96
|
+
`pyincucyte download --preset my-run.json` and as `ExportOptions.load(...)`.
|
|
97
|
+
- The plate picker takes click, drag-to-paint, shift-click for a block, and a
|
|
98
|
+
click on a row letter or column number to flip a whole line.
|
|
99
|
+
- Light and dark themes follow Windows, and **View → Toggle dark mode** overrides.
|
|
100
|
+
- Settings, window size and per-vessel well selections are remembered. Settings
|
|
101
|
+
saved by earlier versions are migrated on first run.
|
|
102
|
+
|
|
103
|
+
## The command line
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pyincucyte probe # is the instrument reachable?
|
|
107
|
+
pyincucyte login # saves credentials
|
|
108
|
+
pyincucyte vessels # what is on the device
|
|
109
|
+
pyincucyte plan -v 38 -o ./images --start-from first # dry run
|
|
110
|
+
|
|
111
|
+
pyincucyte download -v 38 -o ./images --start-from first
|
|
112
|
+
pyincucyte download -v 38 -o ./images --wells D2-D5 --channels phase,green \
|
|
113
|
+
--layout time_stack
|
|
114
|
+
pyincucyte watch -v 38 -o ./images -i 10 --start-from first
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Useful anywhere: `--json` for machine-readable output, `--preset FILE` to load a
|
|
118
|
+
saved recipe, `--save-preset FILE` to write one, `--dry-run`, `--quiet`.
|
|
119
|
+
|
|
120
|
+
`pyincucyte manifest ./images` summarises what a folder already contains.
|
|
121
|
+
|
|
122
|
+
### Time windows
|
|
123
|
+
|
|
124
|
+
`--start-from` and `--end-at` both take a date, a date *and time*, or a relative
|
|
125
|
+
offset. Both ends are inclusive.
|
|
126
|
+
|
|
127
|
+
| Written as | Means |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `first` | the experiment's own first scan, at its real time of day |
|
|
130
|
+
| `today` / `now` | midnight this morning / this instant |
|
|
131
|
+
| `2026-03-05` | start: 00:00 that day. End: the whole of that day |
|
|
132
|
+
| `2026-03-05 14:30` | exactly that minute (`T` instead of a space also works) |
|
|
133
|
+
| `-48h` (start only) | a rolling window: 48 hours before now |
|
|
134
|
+
| `+72h` (end only) | 72 hours after the *start* |
|
|
135
|
+
| `-50f` (start only) | the last 50 frames |
|
|
136
|
+
| `+100f` (end only) | the first 100 frames from the start |
|
|
137
|
+
|
|
138
|
+
So the first three days of an experiment that began at 14:30 is
|
|
139
|
+
`--start-from first --end-at +72h`, and it correctly excludes that morning's
|
|
140
|
+
scans. Time units are `s`, `m`, `h`, `d`, `w`, and the sign is required so an
|
|
141
|
+
offset can never be mistaken for a date.
|
|
142
|
+
|
|
143
|
+
**Frame counts** measure the same window in scan times rather than clock time —
|
|
144
|
+
`-50f`, `+100 frames`, `-24 scans` all work. Use them when the *length of the
|
|
145
|
+
stack* is what matters, which is usually the case for a video: `--start-from
|
|
146
|
+
-100f` gives a 100-frame stack whatever the scan interval was, where `-48h`
|
|
147
|
+
gives however many frames happened to fall in two days.
|
|
148
|
+
|
|
149
|
+
A frame is one scan time, so the count is unaffected by how many wells or
|
|
150
|
+
channels you select — five frames of two channels is five timepoints and ten
|
|
151
|
+
files. Count from one end or the other, not both.
|
|
152
|
+
|
|
153
|
+
PyIncucyte walks the days backwards for `-50f` and stops as soon as it has
|
|
154
|
+
enough, so asking for the last 50 frames of a three-month run costs a couple of
|
|
155
|
+
metadata calls rather than ninety.
|
|
156
|
+
|
|
157
|
+
The instrument only lists scans one calendar day at a time, so PyIncucyte sweeps
|
|
158
|
+
whole days and then trims to your exact window.
|
|
159
|
+
|
|
160
|
+
### Layouts
|
|
161
|
+
|
|
162
|
+
`--layout` replaces the old `--hyperstack` / `--time-stack` flag pair, which
|
|
163
|
+
still works.
|
|
164
|
+
|
|
165
|
+
| Layout | Axes | One file per |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `separate` (default) | `YX` | well, channel and scan time |
|
|
168
|
+
| `channel_stack` | `CYX` | well and scan time |
|
|
169
|
+
| `time_stack` | `TYX` | well and channel |
|
|
170
|
+
| `time_channel_stack` | `TCYX` | well |
|
|
171
|
+
|
|
172
|
+
### Options
|
|
173
|
+
|
|
174
|
+
| Flag | Description |
|
|
175
|
+
|------|-------------|
|
|
176
|
+
| `-v`, `--vessel` | Vessel ID — repeat for several |
|
|
177
|
+
| `-o`, `--output` | Output folder |
|
|
178
|
+
| `-w`, `--wells` | Well filter (`A1`, `A1,B3`, `A1-D4`, `all`) |
|
|
179
|
+
| `--vessel-wells ID:WELLS` | Per-vessel well filter (`-f` is the old spelling) |
|
|
180
|
+
| `-c`, `--channels` | `phase`, `green`/`color1`, `red`/`color2`, `all` |
|
|
181
|
+
| `--layout` | See the table above |
|
|
182
|
+
| `-s`, `--start-from` | `first`, `today`, `now`, a date/time, `-48h`, or `-50f` |
|
|
183
|
+
| `--end-at` | `now` (default), a date/time, `+72h`, or `+100f` |
|
|
184
|
+
| `-d`, `--date` | Shorthand for a single day |
|
|
185
|
+
| `-t`, `--scan-time` | Only scan times containing this text |
|
|
186
|
+
| `--workers` | Parallel fetches (default 4) |
|
|
187
|
+
| `--green-lut` / `--no-green-lut` | Recolour Phase as green RGB (display only) |
|
|
188
|
+
| `--state-scope` | `auto` (default), `folder`, `global`, `none` |
|
|
189
|
+
| `--cache` | `auto` (default), `always`, `never` — cache source payloads |
|
|
190
|
+
| `--no-manifest` | Skip writing the manifest and CSV index |
|
|
191
|
+
| `-i`, `--interval` | `watch` only: poll interval in minutes |
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## The Python API
|
|
196
|
+
|
|
197
|
+
This is the part an automated pipeline should use.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from pyincucyte import IncucyteClient
|
|
201
|
+
|
|
202
|
+
with IncucyteClient.from_saved() as incucyte:
|
|
203
|
+
plan = incucyte.plan(
|
|
204
|
+
vessel=38, output="./run-01",
|
|
205
|
+
wells="A1-D6", channels="phase,green",
|
|
206
|
+
layout="time_channel_stack",
|
|
207
|
+
start_from="first", end_at="+72h") # the first three days
|
|
208
|
+
|
|
209
|
+
print(plan.summary())
|
|
210
|
+
# 1 vessel - 24 wells - 118 scan times - One ImageJ TCYX stack per well
|
|
211
|
+
# 24 output files - 5,664 source images - ~15.8 GB
|
|
212
|
+
|
|
213
|
+
result = incucyte.download(plan, progress=print)
|
|
214
|
+
|
|
215
|
+
for image in result.files:
|
|
216
|
+
segment(image.path,
|
|
217
|
+
well=image.well, # "A1"
|
|
218
|
+
channels=image.channels, # ["Phase", "GFP"] in stack order
|
|
219
|
+
axes=image.axes, # "TCYX"
|
|
220
|
+
timepoints=image.scan_times)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`incucyte.fetch(...)` plans and downloads in one call. `start_from` and `end_at`
|
|
224
|
+
also accept `date` and `datetime` objects directly, and `plan.window` reports the
|
|
225
|
+
`(start, end)` the plan actually used — it is recorded in the manifest too.
|
|
226
|
+
|
|
227
|
+
### What you get back
|
|
228
|
+
|
|
229
|
+
`DownloadResult` carries `.files` (typed `OutputFile` records), `.paths`,
|
|
230
|
+
`.errors`, `.cancelled`, `.bytes_total`, `.duration_seconds` and `.summary()`.
|
|
231
|
+
Failures are collected, not raised — one unreadable well never aborts a plate.
|
|
232
|
+
|
|
233
|
+
Each `OutputFile` knows its vessel, well, row/column, site, channel display
|
|
234
|
+
names, device channel numbers, scan times, ImageJ axis order, elapsed-time label
|
|
235
|
+
and size.
|
|
236
|
+
|
|
237
|
+
### The manifest
|
|
238
|
+
|
|
239
|
+
Every download writes `pyincucyte-manifest.json` and `pyincucyte-index.csv` into
|
|
240
|
+
the output folder. **Read these instead of globbing the folder and re-parsing
|
|
241
|
+
filenames** — they already say which well, channel and timepoint every file is.
|
|
242
|
+
Watch mode merges into the same manifest, so it stays a complete index of the
|
|
243
|
+
folder however many polls filled it.
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
import json, pandas as pd
|
|
247
|
+
manifest = json.load(open("run-01/pyincucyte-manifest.json"))
|
|
248
|
+
index = pd.read_csv("run-01/pyincucyte-index.csv")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Watching, without blocking
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
watcher = incucyte.watch(options, on_result=lambda r: analyse(r.paths))
|
|
255
|
+
... # your pipeline carries on
|
|
256
|
+
watcher.stop(wait=30)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`Watcher` also works as a context manager, and `run_forever()` blocks if that is
|
|
260
|
+
what you want.
|
|
261
|
+
|
|
262
|
+
### Presets shared with the GUI
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from pyincucyte import ExportOptions
|
|
266
|
+
|
|
267
|
+
options = ExportOptions.load("nightly.json") # saved from the GUI
|
|
268
|
+
result = incucyte.fetch(options.replace(output="./tonight"))
|
|
269
|
+
print(options.cli_command()) # the equivalent CLI line
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Why watch mode stays cheap
|
|
273
|
+
|
|
274
|
+
A time stack has to contain every frame, so one new scan invalidates the whole
|
|
275
|
+
file. Rebuilt naively, every poll would re-download the entire experiment — and
|
|
276
|
+
the cost grows with each hour of the run. PyIncucyte keeps the source payloads
|
|
277
|
+
in `.pyincucyte-cache/` inside the output folder the first time it fetches them,
|
|
278
|
+
so a rebuild is a disk read and only genuinely new frames touch the instrument.
|
|
279
|
+
|
|
280
|
+
It is on automatically for the time layouts, which are the ones that rebuild.
|
|
281
|
+
`--cache always` / `--cache never` (or `cache_payloads=` in `ExportOptions`)
|
|
282
|
+
override that; deleting the folder only costs time. `result.cache.summary()`
|
|
283
|
+
reports the hit rate.
|
|
284
|
+
|
|
285
|
+
### Resume state
|
|
286
|
+
|
|
287
|
+
By default the ledger of what has already been fetched lives in the output
|
|
288
|
+
folder (`.pyincucyte-state.json`), so parallel experiments never collide and
|
|
289
|
+
moving a folder moves its history with it. `state_scope="global"` restores the
|
|
290
|
+
old shared file; `"none"` disables resume entirely.
|
|
291
|
+
|
|
292
|
+
### Errors
|
|
293
|
+
|
|
294
|
+
Everything derives from `IncucyteError`, so a pipeline can wrap a whole run in
|
|
295
|
+
one `except`: `DeviceUnreachableError`, `AuthenticationError`,
|
|
296
|
+
`NotLoggedInError`, `TokenExpiredError`, `ApiError`, `VesselNotFoundError`,
|
|
297
|
+
`EncryptionUnavailableError`, `ExportError`.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Layout of this repository
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
PyIncucyte/
|
|
305
|
+
pyincucyte/
|
|
306
|
+
client.py IncucyteClient - the object a pipeline imports
|
|
307
|
+
options.py ExportOptions - the recipe shared by GUI, CLI and API
|
|
308
|
+
models.py Vessel, ExportPlan, DownloadResult, OutputFile
|
|
309
|
+
manifest.py the JSON manifest and CSV index
|
|
310
|
+
state.py resume ledger, scoped to the output folder
|
|
311
|
+
cache.py source-payload cache, so rebuilt stacks do not re-download
|
|
312
|
+
watch.py Watcher - poll and download in a background thread
|
|
313
|
+
engine.py wire-level REST and ImageJ TIFF writing
|
|
314
|
+
cli.py command line
|
|
315
|
+
compat.py the import names retired in 0.3
|
|
316
|
+
gui/ desktop app: theme.py, widgets.py, dialogs.py, app.py
|
|
317
|
+
tests/ run with: python -m pytest
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Nothing lives outside the package any more. The loose `incucyte_downloader.py`
|
|
321
|
+
and `incucyte_gui.py` modules are gone, and so is the second `py_incucyte_gui`
|
|
322
|
+
package; importing `pyincucyte` registers all three as aliases, so this still
|
|
323
|
+
resolves - to the very same module object, so monkeypatching behaves as before:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
import pyincucyte
|
|
327
|
+
from incucyte_downloader import download_scan_images
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Where settings live
|
|
331
|
+
|
|
332
|
+
- Windows: `%APPDATA%\PyIncucyte`
|
|
333
|
+
- macOS/Linux: `$XDG_CONFIG_HOME/pyincucyte` or `~/.config/pyincucyte`
|
|
334
|
+
- Override with `PYINCUCYTE_HOME`
|
|
335
|
+
|
|
336
|
+
A folder left over from before the rename (`PyIncucyteGUI` / `pyincucytegui`)
|
|
337
|
+
keeps being used if it holds settings, and `PYINCUCYTEGUI_HOME` still works, so
|
|
338
|
+
upgrading does not log anybody out.
|
|
339
|
+
|
|
340
|
+
A source checkout that already has a `.tmp/` folder keeps using it.
|
|
341
|
+
`PYINCUCYTE_CLIENT_DIR` overrides where the Incucyte client install is looked for.
|
|
342
|
+
|
|
343
|
+
## Build and publish
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
python -m pip install --upgrade build twine
|
|
347
|
+
python -m build
|
|
348
|
+
python -m twine check dist/*
|
|
349
|
+
python -m twine upload dist/*
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## License
|
|
353
|
+
|
|
354
|
+
BSD 3-Clause. Copyright (c) 2026, Jamie Malcolm. Ownership is personal rather
|
|
355
|
+
than lab or institute owned.
|