PyOPIA 2.16.13__tar.gz → 2.16.15__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.
- {pyopia-2.16.13 → pyopia-2.16.15}/.gitignore +1 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/PKG-INFO +1 -1
- pyopia-2.16.15/pyopia/__init__.py +1 -0
- pyopia-2.16.15/pyopia/tests/conftest.py +139 -0
- pyopia-2.16.15/pyopia/tests/test_cli.py +284 -0
- pyopia-2.16.13/pyopia/__init__.py +0 -1
- {pyopia-2.16.13 → pyopia-2.16.15}/LICENSE +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/README.md +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/auxillarydata.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/background.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/cf_metadata.json +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/classify.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/classify_torch.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/cli.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/dataexport/__init__.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/dataexport/ecotaxa.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/exampledata.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/instrument/__init__.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/instrument/common.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/instrument/holo.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/instrument/silcam.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/instrument/uvp.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/io.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/metadata.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/pipeline.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/plotting.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/process.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/realtime.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/simulator/__init__.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/simulator/silcam.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/statistics.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/__init__.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_auxillarydata.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_classify.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_io.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_notebooks.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_pipeline.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_process.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyopia/tests/test_realtime.py +0 -0
- {pyopia-2.16.13 → pyopia-2.16.15}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.16.15"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Shared pytest fixtures for pyopia.cli integration tests.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import toml
|
|
10
|
+
from typer.testing import CliRunner
|
|
11
|
+
|
|
12
|
+
import pyopia.exampledata
|
|
13
|
+
from pyopia.cli import app
|
|
14
|
+
|
|
15
|
+
runner = CliRunner()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def invoke_in(directory, args):
|
|
19
|
+
'''Invoke the CLI app with the working directory set to `directory`,
|
|
20
|
+
restoring the original working directory afterwards.
|
|
21
|
+
'''
|
|
22
|
+
original_cwd = os.getcwd()
|
|
23
|
+
os.chdir(directory)
|
|
24
|
+
try:
|
|
25
|
+
return runner.invoke(app, args)
|
|
26
|
+
finally:
|
|
27
|
+
os.chdir(original_cwd)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(scope='session')
|
|
31
|
+
def silcam_example_files(tmp_path_factory):
|
|
32
|
+
'''Download the real SilCam example image and classifier model once per test session.
|
|
33
|
+
|
|
34
|
+
pyopia.pipeline.FilesToProcess.prepare_chunking() refuses to process a raw file
|
|
35
|
+
list with fewer than 2 entries (`num_chunks > len(files) // 2`), even for the
|
|
36
|
+
default single-chunk case. Only one distinct real raw frame is available via
|
|
37
|
+
pyopia.exampledata, so the downloaded image is duplicated under a second,
|
|
38
|
+
differently-timestamped filename to satisfy that minimum. The image content
|
|
39
|
+
processed by the pipeline is still the real downloaded photo, just used twice;
|
|
40
|
+
no pipeline/classification logic is faked.
|
|
41
|
+
'''
|
|
42
|
+
download_dir = tmp_path_factory.mktemp('silcam_example_data')
|
|
43
|
+
image_filename = pyopia.exampledata.get_example_silc_image(str(download_dir))
|
|
44
|
+
model_path = pyopia.exampledata.get_example_model(str(download_dir))
|
|
45
|
+
|
|
46
|
+
original_path = download_dir / image_filename
|
|
47
|
+
duplicate_path = download_dir / 'D20181101T142732.838206.silc'
|
|
48
|
+
shutil.copy(original_path, duplicate_path)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
'download_dir': download_dir,
|
|
52
|
+
'model_path': model_path,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture(scope='session')
|
|
57
|
+
def silcam_cli_project(tmp_path_factory, silcam_example_files):
|
|
58
|
+
'''Run the real `pyopia process` CLI command against the real downloaded SilCam
|
|
59
|
+
example image, producing genuine per-image STATS.nc output and exported ROI images.
|
|
60
|
+
|
|
61
|
+
Background correction is deliberately left out of this config: with only one real
|
|
62
|
+
raw frame available, CorrectBackgroundAccurate would build its background from
|
|
63
|
+
that same frame, which self-cancels the image and yields zero particles. This
|
|
64
|
+
mirrors the working config used in test_pipeline.py::test_silcam_pipeline.
|
|
65
|
+
|
|
66
|
+
Session-scoped so the real download and model inference only happens once, and is
|
|
67
|
+
shared by every CLI test that needs real particle stats/ROIs to operate on
|
|
68
|
+
(process, merge-mfdata, convert-raw-images, make-montage, export-to-ecotaxa).
|
|
69
|
+
'''
|
|
70
|
+
project_dir = tmp_path_factory.mktemp('silcam_cli_project')
|
|
71
|
+
outfolder = project_dir / 'proc'
|
|
72
|
+
roi_folder = outfolder / 'roi'
|
|
73
|
+
|
|
74
|
+
config_filename = project_dir / 'silcam-config.toml'
|
|
75
|
+
pipeline_config = {
|
|
76
|
+
'general': {
|
|
77
|
+
'raw_files': str(silcam_example_files['download_dir'] / '*.silc'),
|
|
78
|
+
'pixel_size': 28,
|
|
79
|
+
},
|
|
80
|
+
'steps': {
|
|
81
|
+
'classifier': {
|
|
82
|
+
'pipeline_class': 'pyopia.classify.Classify',
|
|
83
|
+
'model_path': str(silcam_example_files['model_path']),
|
|
84
|
+
},
|
|
85
|
+
'load': {
|
|
86
|
+
'pipeline_class': 'pyopia.instrument.silcam.SilCamLoad',
|
|
87
|
+
},
|
|
88
|
+
'imageprep': {
|
|
89
|
+
'pipeline_class': 'pyopia.instrument.silcam.ImagePrep',
|
|
90
|
+
'image_level': 'imraw',
|
|
91
|
+
},
|
|
92
|
+
'segmentation': {
|
|
93
|
+
'pipeline_class': 'pyopia.process.Segment',
|
|
94
|
+
'threshold': 0.85,
|
|
95
|
+
'segment_source': 'im_minimum',
|
|
96
|
+
},
|
|
97
|
+
'statextract': {
|
|
98
|
+
'pipeline_class': 'pyopia.process.CalculateStats',
|
|
99
|
+
'export_outputpath': str(roi_folder),
|
|
100
|
+
'roi_source': 'im_minimum',
|
|
101
|
+
},
|
|
102
|
+
'output': {
|
|
103
|
+
'pipeline_class': 'pyopia.io.StatsToDisc',
|
|
104
|
+
'output_datafile': str(outfolder / 'test'),
|
|
105
|
+
'append': False,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
with open(config_filename, 'w') as fh:
|
|
110
|
+
toml.dump(pipeline_config, fh)
|
|
111
|
+
|
|
112
|
+
result = invoke_in(project_dir, ['process', str(config_filename)])
|
|
113
|
+
assert result.exit_code == 0, result.output
|
|
114
|
+
|
|
115
|
+
stats_files = sorted(outfolder.glob('*Image-D*-STATS.nc'))
|
|
116
|
+
assert len(stats_files) == 2, f'Expected two per-image STATS files, found: {stats_files}'
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
'project_dir': project_dir,
|
|
120
|
+
'config_filename': config_filename,
|
|
121
|
+
'outfolder': outfolder,
|
|
122
|
+
'roi_folder': roi_folder,
|
|
123
|
+
'stats_files': stats_files,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.fixture(scope='session')
|
|
128
|
+
def silcam_cli_merged_stats(silcam_cli_project):
|
|
129
|
+
'''Merge the per-image STATS.nc file(s) from `silcam_cli_project` via the real
|
|
130
|
+
`merge-mfdata` CLI command, producing a combined STATS.nc shared by the
|
|
131
|
+
make-montage and export-to-ecotaxa tests.
|
|
132
|
+
'''
|
|
133
|
+
outfolder = silcam_cli_project['outfolder']
|
|
134
|
+
result = invoke_in(silcam_cli_project['project_dir'], ['merge-mfdata', str(outfolder)])
|
|
135
|
+
assert result.exit_code == 0, result.output
|
|
136
|
+
|
|
137
|
+
merged_path = outfolder / 'test-STATS.nc'
|
|
138
|
+
assert merged_path.is_file()
|
|
139
|
+
return merged_path
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Tests for the pyopia.cli command line interface, using typer's CliRunner.
|
|
3
|
+
|
|
4
|
+
Commands that need real particle statistics/ROI images to operate on (process,
|
|
5
|
+
merge-mfdata, convert-raw-images, make-montage, export-to-ecotaxa) share a single
|
|
6
|
+
real end-to-end pipeline run against the downloaded SilCam example image, via the
|
|
7
|
+
session-scoped `silcam_cli_project` / `silcam_cli_merged_stats` fixtures defined in
|
|
8
|
+
conftest.py. This mirrors the real-download, real-processing approach already used
|
|
9
|
+
in test_pipeline.py and test_classify.py, rather than mocking the pipeline itself.
|
|
10
|
+
'''
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import pathlib
|
|
14
|
+
import zipfile
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
import toml
|
|
18
|
+
import xarray
|
|
19
|
+
|
|
20
|
+
import pyopia
|
|
21
|
+
import pyopia.cli
|
|
22
|
+
from pyopia.cli import app
|
|
23
|
+
|
|
24
|
+
from pyopia.tests.conftest import invoke_in, runner
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_generate_config_silcam(tmp_path):
|
|
28
|
+
result = invoke_in(tmp_path, [
|
|
29
|
+
'generate-config', 'silcam', 'images/*.silc', 'model.keras', 'proc', 'test'
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
assert result.exit_code == 0
|
|
33
|
+
config_path = tmp_path / 'silcam-config.toml'
|
|
34
|
+
assert config_path.is_file()
|
|
35
|
+
|
|
36
|
+
toml_settings = toml.load(config_path)
|
|
37
|
+
assert toml_settings['general']['raw_files'] == 'images/*.silc'
|
|
38
|
+
assert toml_settings['steps']['classifier']['model_path'] == 'model.keras'
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_generate_config_holo(tmp_path):
|
|
42
|
+
result = invoke_in(tmp_path, [
|
|
43
|
+
'generate-config', 'holo', 'images/*.pgm', 'model.keras', 'proc', 'test'
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
assert result.exit_code == 0
|
|
47
|
+
config_path = tmp_path / 'holo-config.toml'
|
|
48
|
+
assert config_path.is_file()
|
|
49
|
+
|
|
50
|
+
toml_settings = toml.load(config_path)
|
|
51
|
+
assert toml_settings['general']['raw_files'] == 'images/*.pgm'
|
|
52
|
+
assert toml_settings['steps']['initial']['pipeline_class'] == 'pyopia.instrument.holo.Initial'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_generate_config_uvp(tmp_path):
|
|
56
|
+
result = invoke_in(tmp_path, [
|
|
57
|
+
'generate-config', 'uvp', 'images/*.png', 'model.keras', 'proc', 'test'
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
assert result.exit_code == 0
|
|
61
|
+
config_path = tmp_path / 'uvp-config.toml'
|
|
62
|
+
assert config_path.is_file()
|
|
63
|
+
|
|
64
|
+
toml_settings = toml.load(config_path)
|
|
65
|
+
assert toml_settings['general']['raw_files'] == 'images/*.png'
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_modify_config(tmp_path):
|
|
69
|
+
generate_result = invoke_in(tmp_path, [
|
|
70
|
+
'generate-config', 'silcam', 'images/*.silc', 'model.keras', 'proc', 'test'
|
|
71
|
+
])
|
|
72
|
+
assert generate_result.exit_code == 0
|
|
73
|
+
|
|
74
|
+
modify_result = invoke_in(tmp_path, [
|
|
75
|
+
'modify-config', 'silcam-config.toml', 'modified-config.toml',
|
|
76
|
+
'--raw-files', 'other_images/*.silc',
|
|
77
|
+
'--pixel-size', '12.5',
|
|
78
|
+
'--step-name', 'segmentation',
|
|
79
|
+
'--modify-arg', 'threshold',
|
|
80
|
+
'--modify-value', '0.85',
|
|
81
|
+
])
|
|
82
|
+
assert modify_result.exit_code == 0
|
|
83
|
+
|
|
84
|
+
modified_path = tmp_path / 'modified-config.toml'
|
|
85
|
+
assert modified_path.is_file()
|
|
86
|
+
|
|
87
|
+
toml_settings = toml.load(modified_path)
|
|
88
|
+
assert toml_settings['general']['raw_files'] == 'other_images/*.silc'
|
|
89
|
+
assert toml_settings['general']['pixel_size'] == 12.5
|
|
90
|
+
assert toml_settings['steps']['segmentation']['threshold'] == 0.85
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_docs_launches_readthedocs_url(monkeypatch):
|
|
94
|
+
launched = {}
|
|
95
|
+
monkeypatch.setattr(pyopia.cli.typer, 'launch', lambda url: launched.setdefault('url', url))
|
|
96
|
+
|
|
97
|
+
result = runner.invoke(app, ['docs'])
|
|
98
|
+
|
|
99
|
+
assert result.exit_code == 0
|
|
100
|
+
assert launched['url'] == 'https://pyopia.readthedocs.io'
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_version_flag_prints_package_version():
|
|
104
|
+
result = runner.invoke(app, ['--version'])
|
|
105
|
+
|
|
106
|
+
assert result.exit_code == 0
|
|
107
|
+
assert f'PyOPIA version: {pyopia.__version__}' in result.output
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_init_project_creates_expected_structure(tmp_path):
|
|
111
|
+
result = invoke_in(tmp_path, ['init-project', 'myproj'])
|
|
112
|
+
|
|
113
|
+
assert result.exit_code == 0, result.output
|
|
114
|
+
|
|
115
|
+
proj_folder = tmp_path / 'myproj'
|
|
116
|
+
assert (proj_folder / 'images').is_dir()
|
|
117
|
+
assert (proj_folder / 'auxillarydata' / 'auxillary_data.csv').is_file()
|
|
118
|
+
assert (proj_folder / 'README').is_file()
|
|
119
|
+
assert (proj_folder / 'metadata.json').is_file()
|
|
120
|
+
assert (proj_folder / 'config.toml').is_file()
|
|
121
|
+
|
|
122
|
+
config = toml.load(proj_folder / 'config.toml')
|
|
123
|
+
assert config['general']['raw_files'] == 'images/*.silc'
|
|
124
|
+
assert config['steps']['output']['project_metadata_file'] == 'metadata.json'
|
|
125
|
+
assert config['steps']['output']['auxillary_data_file'] == 'auxillarydata/auxillary_data.csv'
|
|
126
|
+
|
|
127
|
+
output_datafile = config['steps']['output']['output_datafile']
|
|
128
|
+
assert (proj_folder / pathlib.Path(output_datafile).parent).is_dir()
|
|
129
|
+
|
|
130
|
+
metadata = json.loads((proj_folder / 'metadata.json').read_text())
|
|
131
|
+
assert metadata['project_name'] == 'myproj'
|
|
132
|
+
assert metadata['instrument'] == 'silcam'
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_init_project_refuses_to_overwrite_existing_folder(tmp_path):
|
|
136
|
+
existing_project = tmp_path / 'myproj'
|
|
137
|
+
existing_project.mkdir()
|
|
138
|
+
|
|
139
|
+
result = invoke_in(tmp_path, ['init-project', 'myproj'])
|
|
140
|
+
|
|
141
|
+
assert result.exit_code == 0
|
|
142
|
+
assert 'ERROR' in result.output
|
|
143
|
+
assert not (existing_project / 'config.toml').exists()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_check_chunks_rejects_less_than_one_chunk():
|
|
147
|
+
with pytest.raises(RuntimeError, match='at least 1 chunk'):
|
|
148
|
+
pyopia.cli.check_chunks(0, {'steps': {'output': {}}})
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_check_chunks_rejects_multiple_chunks_when_append_enabled():
|
|
152
|
+
with pytest.raises(RuntimeError, match='append = false'):
|
|
153
|
+
pyopia.cli.check_chunks(2, {'steps': {'output': {'append': True}}})
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_check_chunks_allows_multiple_chunks_when_append_disabled():
|
|
157
|
+
pyopia.cli.check_chunks(2, {'steps': {'output': {'append': False}}})
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_process_requires_an_output_step(tmp_path):
|
|
161
|
+
config_filename = tmp_path / 'config.toml'
|
|
162
|
+
with open(config_filename, 'w') as fh:
|
|
163
|
+
toml.dump({'general': {'raw_files': str(tmp_path / '*.silc')}, 'steps': {}}, fh)
|
|
164
|
+
|
|
165
|
+
result = invoke_in(tmp_path, ['process', str(config_filename)])
|
|
166
|
+
|
|
167
|
+
assert result.exit_code != 0
|
|
168
|
+
assert 'output' in str(result.exception)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_process_produces_real_particle_stats_and_roi_export(silcam_cli_project):
|
|
172
|
+
for stats_file in silcam_cli_project['stats_files']:
|
|
173
|
+
with xarray.open_dataset(stats_file) as stats:
|
|
174
|
+
stats.load()
|
|
175
|
+
|
|
176
|
+
assert len(stats.major_axis_length) == 870
|
|
177
|
+
assert any(name.startswith('probability_') for name in stats.data_vars)
|
|
178
|
+
|
|
179
|
+
assert (silcam_cli_project['project_dir'] / 'filelist.txt').is_file()
|
|
180
|
+
|
|
181
|
+
roi_files = list(silcam_cli_project['roi_folder'].glob('*.h5'))
|
|
182
|
+
assert len(roi_files) == len(silcam_cli_project['stats_files']) == 2
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_process_realtime_requires_an_output_step(tmp_path):
|
|
186
|
+
config_filename = tmp_path / 'config.toml'
|
|
187
|
+
with open(config_filename, 'w') as fh:
|
|
188
|
+
toml.dump({'general': {}, 'steps': {}}, fh)
|
|
189
|
+
|
|
190
|
+
result = invoke_in(tmp_path, ['process-realtime', str(config_filename)])
|
|
191
|
+
|
|
192
|
+
assert result.exit_code != 0
|
|
193
|
+
assert 'output' in str(result.exception)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_process_realtime_requires_output_datafile_setting(tmp_path):
|
|
197
|
+
config_filename = tmp_path / 'config.toml'
|
|
198
|
+
with open(config_filename, 'w') as fh:
|
|
199
|
+
toml.dump({
|
|
200
|
+
'general': {},
|
|
201
|
+
'steps': {'output': {'pipeline_class': 'pyopia.io.StatsToDisc'}},
|
|
202
|
+
}, fh)
|
|
203
|
+
|
|
204
|
+
result = invoke_in(tmp_path, ['process-realtime', str(config_filename)])
|
|
205
|
+
|
|
206
|
+
assert result.exit_code != 0
|
|
207
|
+
assert 'output_datafile' in str(result.exception)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_process_realtime_prepares_output_folder_and_calls_run_realtime(tmp_path, monkeypatch):
|
|
211
|
+
recorded = {}
|
|
212
|
+
monkeypatch.setattr(
|
|
213
|
+
pyopia.cli.pyopia.realtime, 'run_realtime',
|
|
214
|
+
lambda pipeline_config, watch_folder=None: recorded.update(
|
|
215
|
+
pipeline_config=pipeline_config, watch_folder=watch_folder
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
output_datafile = str(tmp_path / 'proc' / 'test')
|
|
220
|
+
config_filename = tmp_path / 'config.toml'
|
|
221
|
+
with open(config_filename, 'w') as fh:
|
|
222
|
+
toml.dump({
|
|
223
|
+
'general': {'raw_files': str(tmp_path / 'images' / '*.silc')},
|
|
224
|
+
'steps': {'output': {'pipeline_class': 'pyopia.io.StatsToDisc', 'output_datafile': output_datafile}},
|
|
225
|
+
}, fh)
|
|
226
|
+
|
|
227
|
+
result = invoke_in(tmp_path, [
|
|
228
|
+
'process-realtime', str(config_filename), '--watch-folder', str(tmp_path / 'images')
|
|
229
|
+
])
|
|
230
|
+
|
|
231
|
+
assert result.exit_code == 0, result.output
|
|
232
|
+
assert (tmp_path / 'proc').is_dir()
|
|
233
|
+
assert recorded['watch_folder'] == str(tmp_path / 'images')
|
|
234
|
+
assert recorded['pipeline_config']['steps']['output']['output_datafile'] == output_datafile
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_merge_mfdata_combines_per_image_stats(silcam_cli_project, silcam_cli_merged_stats):
|
|
238
|
+
with xarray.open_dataset(silcam_cli_merged_stats) as merged:
|
|
239
|
+
merged.load()
|
|
240
|
+
|
|
241
|
+
per_image_particle_counts = []
|
|
242
|
+
for stats_file in silcam_cli_project['stats_files']:
|
|
243
|
+
with xarray.open_dataset(stats_file) as original:
|
|
244
|
+
original.load()
|
|
245
|
+
per_image_particle_counts.append(len(original.major_axis_length))
|
|
246
|
+
|
|
247
|
+
assert len(merged.major_axis_length) == sum(per_image_particle_counts) == 1740
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_convert_raw_images_creates_png(silcam_cli_project, tmp_path):
|
|
251
|
+
result = invoke_in(tmp_path, ['convert-raw-images', str(silcam_cli_project['config_filename'])])
|
|
252
|
+
|
|
253
|
+
assert result.exit_code == 0, result.output
|
|
254
|
+
|
|
255
|
+
converted = list((tmp_path / 'images_converted').glob('*.png'))
|
|
256
|
+
assert len(converted) == 2
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_make_montage_creates_real_montage_image(silcam_cli_merged_stats, tmp_path):
|
|
260
|
+
montage_path = tmp_path / 'montage.png'
|
|
261
|
+
|
|
262
|
+
result = invoke_in(tmp_path, [
|
|
263
|
+
'make-montage', str(silcam_cli_merged_stats), '--output-filename', str(montage_path)
|
|
264
|
+
])
|
|
265
|
+
|
|
266
|
+
assert result.exit_code == 0, result.output
|
|
267
|
+
assert montage_path.is_file()
|
|
268
|
+
assert montage_path.stat().st_size > 0
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_export_to_ecotaxa_creates_bundle_zip(silcam_cli_merged_stats, tmp_path):
|
|
272
|
+
export_path = tmp_path / 'ecotaxa_export.zip'
|
|
273
|
+
|
|
274
|
+
result = invoke_in(tmp_path, [
|
|
275
|
+
'export-to-ecotaxa', str(silcam_cli_merged_stats), str(export_path)
|
|
276
|
+
])
|
|
277
|
+
|
|
278
|
+
assert result.exit_code == 0, result.output
|
|
279
|
+
assert export_path.is_file()
|
|
280
|
+
|
|
281
|
+
with zipfile.ZipFile(export_path) as bundle:
|
|
282
|
+
names = bundle.namelist()
|
|
283
|
+
assert 'ecotaxa_particle_statistics.tsv' in names
|
|
284
|
+
assert sum(name.endswith('.png') for name in names) == 1740
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.16.13"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|