PyOPIA 2.6.0__tar.gz → 2.6.2__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.6.0 → pyopia-2.6.2}/PKG-INFO +1 -1
- pyopia-2.6.2/pyopia/__init__.py +1 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/cli.py +57 -21
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/pipeline.py +36 -4
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/process.py +1 -1
- pyopia-2.6.0/pyopia/__init__.py +0 -1
- {pyopia-2.6.0 → pyopia-2.6.2}/LICENSE +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/README.md +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/background.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/classify.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/exampledata.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/__init__.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/common.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/holo.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/silcam.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/uvp.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/io.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/plotting.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/simulator/__init__.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/simulator/silcam.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/statistics.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/__init__.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/test_classify.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/test_notebooks.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/test_pipeline.py +0 -0
- {pyopia-2.6.0 → pyopia-2.6.2}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.6.2'
|
|
@@ -5,14 +5,17 @@ PyOPIA top-level code primarily for managing cmd line entry points
|
|
|
5
5
|
import typer
|
|
6
6
|
import toml
|
|
7
7
|
import os
|
|
8
|
+
import time
|
|
8
9
|
import datetime
|
|
9
10
|
import traceback
|
|
10
11
|
import logging
|
|
11
|
-
from rich.progress import
|
|
12
|
+
from rich.progress import Progress
|
|
12
13
|
from rich.logging import RichHandler
|
|
14
|
+
import rich.progress
|
|
13
15
|
import pandas as pd
|
|
14
16
|
import threading
|
|
15
17
|
|
|
18
|
+
import pyopia
|
|
16
19
|
import pyopia.background
|
|
17
20
|
import pyopia.instrument.silcam
|
|
18
21
|
import pyopia.instrument.holo
|
|
@@ -115,23 +118,27 @@ def generate_config(instrument: str, raw_files: str, model_path: str, outfolder:
|
|
|
115
118
|
|
|
116
119
|
|
|
117
120
|
@app.command()
|
|
118
|
-
def process(config_filename: str, num_chunks: int = 1):
|
|
121
|
+
def process(config_filename: str, num_chunks: int = 1, strategy: str = 'block'):
|
|
119
122
|
'''Run a PyOPIA processing pipeline based on given a config.toml
|
|
120
123
|
|
|
121
124
|
Parameters
|
|
122
125
|
----------
|
|
123
126
|
config_filename : str
|
|
124
|
-
|
|
127
|
+
Config filename
|
|
128
|
+
|
|
125
129
|
numchunks : int, optional
|
|
126
|
-
|
|
130
|
+
Split the dataset into chucks, and process in parallell, by default 1
|
|
127
131
|
|
|
132
|
+
strategy : str, optional
|
|
133
|
+
Strategy to use for chunking dataset, either `block` or `interleave`. Defult: `block`
|
|
128
134
|
'''
|
|
129
|
-
|
|
130
|
-
from pyopia.pipeline import Pipeline
|
|
135
|
+
t1 = time.time()
|
|
131
136
|
|
|
132
137
|
with Progress(transient=True) as progress:
|
|
138
|
+
progress.console.print(f"[blue]PYOPIA VERSION {pyopia.__version__}")
|
|
139
|
+
|
|
133
140
|
progress.console.print("[blue]LOAD CONFIG")
|
|
134
|
-
pipeline_config = load_toml(config_filename)
|
|
141
|
+
pipeline_config = pyopia.io.load_toml(config_filename)
|
|
135
142
|
|
|
136
143
|
setup_logging(pipeline_config)
|
|
137
144
|
logger = logging.getLogger('rich')
|
|
@@ -140,11 +147,14 @@ def process(config_filename: str, num_chunks: int = 1):
|
|
|
140
147
|
check_chunks(num_chunks, pipeline_config)
|
|
141
148
|
|
|
142
149
|
progress.console.print("[blue]OBTAIN IMAGE LIST")
|
|
143
|
-
raw_files = pyopia.pipeline.FilesToProcess(pipeline_config['general']['raw_files'])
|
|
144
150
|
conf_corrbg = pipeline_config['steps'].get('correctbackground', dict())
|
|
145
151
|
average_window = conf_corrbg.get('average_window', 0)
|
|
146
152
|
bgshift_function = conf_corrbg.get('bgshift_function', 'pass')
|
|
147
|
-
raw_files.
|
|
153
|
+
raw_files = pyopia.pipeline.FilesToProcess(pipeline_config['general']['raw_files'])
|
|
154
|
+
raw_files.prepare_chunking(num_chunks, average_window, bgshift_function, strategy=strategy)
|
|
155
|
+
|
|
156
|
+
# Write the dataset list of images to a text file
|
|
157
|
+
raw_files.to_filelist_file('filelist.txt')
|
|
148
158
|
|
|
149
159
|
progress.console.print('[blue]PREPARE FOLDERS')
|
|
150
160
|
if 'output' not in pipeline_config['steps']:
|
|
@@ -163,26 +173,36 @@ def process(config_filename: str, num_chunks: int = 1):
|
|
|
163
173
|
progress.console.print("[blue]INITIALISE PIPELINE")
|
|
164
174
|
|
|
165
175
|
def process_file_list(file_list, c):
|
|
166
|
-
processing_pipeline = Pipeline(pipeline_config)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
processing_pipeline = pyopia.pipeline.Pipeline(pipeline_config)
|
|
177
|
+
|
|
178
|
+
with get_custom_progress_bar(f'[blue]Processing progress (chunk {c})', disable=c != 0) as pbar:
|
|
179
|
+
for filename in pbar.track(file_list, description=f'[blue]Processing progress (chunk {c})'):
|
|
180
|
+
try:
|
|
181
|
+
logger.debug(f'Chunk {c} starting to process {filename}')
|
|
182
|
+
processing_pipeline.run(filename)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.warning('[red]An error occured in processing, ' +
|
|
185
|
+
'skipping rest of pipeline and moving to next image.' +
|
|
186
|
+
f'(chunk {c})')
|
|
187
|
+
logger.error(e)
|
|
188
|
+
logger.debug(''.join(traceback.format_tb(e.__traceback__)))
|
|
178
189
|
|
|
179
190
|
# With one chunk we keep the non-threaded functionality to ensure backwards compatibility
|
|
191
|
+
job_list = []
|
|
180
192
|
if num_chunks == 1:
|
|
181
193
|
process_file_list(raw_files, 0)
|
|
182
194
|
else:
|
|
183
195
|
for c, chunk in enumerate(raw_files.chunked_files):
|
|
184
196
|
job = threading.Thread(target=process_file_list, args=(chunk, c, ))
|
|
185
197
|
job.start()
|
|
198
|
+
job_list.append(job)
|
|
199
|
+
|
|
200
|
+
# Calculate and print total processing time
|
|
201
|
+
# If we are using threads, make sure all jobs have finished
|
|
202
|
+
[job.join() for job in job_list]
|
|
203
|
+
time_total = pd.to_timedelta(time.time() - t1, 'seconds')
|
|
204
|
+
with Progress(transient=True) as progress:
|
|
205
|
+
progress.console.print(f"[blue]PROCESSING COMPLETED IN {time_total}")
|
|
186
206
|
|
|
187
207
|
|
|
188
208
|
@app.command()
|
|
@@ -249,5 +269,21 @@ def check_chunks(chunks, pipeline_config):
|
|
|
249
269
|
raise RuntimeError('Output mode must be set to "append = false" in "output" step when using more than one chunk')
|
|
250
270
|
|
|
251
271
|
|
|
272
|
+
def get_custom_progress_bar(description, disable):
|
|
273
|
+
''' Create a custom rich.progress.Progress object for displaying progress bars'''
|
|
274
|
+
progress = Progress(
|
|
275
|
+
rich.progress.TextColumn(description),
|
|
276
|
+
rich.progress.BarColumn(),
|
|
277
|
+
rich.progress.TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
278
|
+
rich.progress.MofNCompleteColumn(),
|
|
279
|
+
rich.progress.TextColumn("•"),
|
|
280
|
+
rich.progress.TimeElapsedColumn(),
|
|
281
|
+
rich.progress.TextColumn("•"),
|
|
282
|
+
rich.progress.TimeRemainingColumn(),
|
|
283
|
+
disable=disable
|
|
284
|
+
)
|
|
285
|
+
return progress
|
|
286
|
+
|
|
287
|
+
|
|
252
288
|
if __name__ == "__main__":
|
|
253
289
|
app()
|
|
@@ -4,6 +4,7 @@ Module for managing the PyOpia processing pipeline
|
|
|
4
4
|
Refer to the :class:`Pipeline` class documentation for examples of how to process datasets and images
|
|
5
5
|
'''
|
|
6
6
|
from typing import TypedDict
|
|
7
|
+
import time
|
|
7
8
|
import datetime
|
|
8
9
|
import pandas as pd
|
|
9
10
|
from operator import methodcaller
|
|
@@ -114,7 +115,10 @@ class Pipeline():
|
|
|
114
115
|
continue
|
|
115
116
|
|
|
116
117
|
logger.info(f'Running pipeline step: {stepname}')
|
|
118
|
+
t1 = time.time()
|
|
117
119
|
self.run_step(stepname)
|
|
120
|
+
t2 = time.time()
|
|
121
|
+
logger.debug(f'Running pipeline step {stepname} took {t2-t1:.3f} seconds')
|
|
118
122
|
|
|
119
123
|
# Check for signal from this step that we should skip remaining pipeline for this image
|
|
120
124
|
if self.data['skip_next_steps']:
|
|
@@ -369,25 +373,53 @@ class FilesToProcess:
|
|
|
369
373
|
with open(path_to_filelist, 'w') as fh:
|
|
370
374
|
[fh.writelines(L + '\n') for L in self.files]
|
|
371
375
|
|
|
372
|
-
|
|
376
|
+
with open(path_to_filelist+'.chunks', 'w') as fh:
|
|
377
|
+
for i, chunk in enumerate(self.chunked_files):
|
|
378
|
+
fh.write(f'--- Chunk {i}, N = {len(chunk)} ---\n')
|
|
379
|
+
[fh.writelines(L + '\n') for L in chunk]
|
|
380
|
+
|
|
381
|
+
def prepare_chunking(self, num_chunks, average_window, bgshift_function, strategy='block'):
|
|
382
|
+
'''Chunk the file list and add initial background files to each chunk
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
num_chunks : int
|
|
387
|
+
Number of chunks to produce (must be at least 1)
|
|
388
|
+
average_window : int
|
|
389
|
+
Number of images to use for background correction
|
|
390
|
+
bgshift_function : str
|
|
391
|
+
Background update strategy, either `pass` (static background) or `accurate`
|
|
392
|
+
strategy : str, optional
|
|
393
|
+
Strategy to use for chunking dataset, either `block` or `interleave`. Defult: `block`
|
|
394
|
+
'''
|
|
373
395
|
if num_chunks > len(self.files) // 2:
|
|
374
396
|
raise RuntimeError('Number of chunks exceeds more than half the number of files to process. Use less chunks.')
|
|
375
|
-
self.chunk_files(num_chunks)
|
|
397
|
+
self.chunk_files(num_chunks, strategy)
|
|
376
398
|
self.build_initial_background_files(average_window=average_window)
|
|
377
399
|
self.insert_bg_files_into_chunks(bgshift_function=bgshift_function)
|
|
378
400
|
|
|
379
|
-
def chunk_files(self, num_chunks: int):
|
|
401
|
+
def chunk_files(self, num_chunks: int, strategy: str = 'block'):
|
|
380
402
|
'''Chunk the file list and create FilesToProcess.chunked_files
|
|
381
403
|
|
|
382
404
|
Parameters
|
|
383
405
|
----------
|
|
384
406
|
num_chunks : int
|
|
385
407
|
number of chunks to produce (must be at least 1)
|
|
408
|
+
strategy : str, optional
|
|
409
|
+
Strategy to use for chunking dataset, either `block` or `interleave`. Defult: `block`
|
|
386
410
|
'''
|
|
387
411
|
if num_chunks < 1:
|
|
388
412
|
raise RuntimeError('You must have at least one chunk')
|
|
389
413
|
chunk_length = int(np.ceil(len(self.files) / num_chunks))
|
|
390
|
-
|
|
414
|
+
if strategy == 'block':
|
|
415
|
+
logging.debug('Chunking file list with strategy: block')
|
|
416
|
+
self.chunked_files = [self.files[i:i + chunk_length] for i in range(0, len(self.files), chunk_length)]
|
|
417
|
+
elif strategy == 'interleave':
|
|
418
|
+
logging.debug('Chunking file list with strategy: interleave')
|
|
419
|
+
self.chunked_files = [self.files[i::num_chunks] for i in range(0, num_chunks)]
|
|
420
|
+
else:
|
|
421
|
+
logging.debug(f'Invalid chunking strategy: {strategy}')
|
|
422
|
+
raise RuntimeError(f'Unknown strategy: {strategy}. Should be either block or interleave')
|
|
391
423
|
|
|
392
424
|
def insert_bg_files_into_chunks(self, bgshift_function='pass'):
|
|
393
425
|
average_window = len(self.background_files)
|
|
@@ -623,7 +623,7 @@ class CalculateImageStats():
|
|
|
623
623
|
|
|
624
624
|
# Add image "global" statistics, separate from the particle stats above (stats)
|
|
625
625
|
image_saturation = np.nan if stats.empty else stats['saturation'].values[0]
|
|
626
|
-
data['image_stats'].loc[data['timestamp'], 'filename'] =
|
|
626
|
+
data['image_stats'].loc[data['timestamp'], 'filename'] = data.get('filename', '')
|
|
627
627
|
data['image_stats'].loc[data['timestamp'], 'particle_count'] = int(stats.shape[0])
|
|
628
628
|
data['image_stats'].loc[data['timestamp'], 'saturation'] = image_saturation
|
|
629
629
|
|
pyopia-2.6.0/pyopia/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.6.0'
|
|
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
|