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.
Files changed (26) hide show
  1. {pyopia-2.6.0 → pyopia-2.6.2}/PKG-INFO +1 -1
  2. pyopia-2.6.2/pyopia/__init__.py +1 -0
  3. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/cli.py +57 -21
  4. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/pipeline.py +36 -4
  5. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/process.py +1 -1
  6. pyopia-2.6.0/pyopia/__init__.py +0 -1
  7. {pyopia-2.6.0 → pyopia-2.6.2}/LICENSE +0 -0
  8. {pyopia-2.6.0 → pyopia-2.6.2}/README.md +0 -0
  9. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/background.py +0 -0
  10. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/classify.py +0 -0
  11. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/exampledata.py +0 -0
  12. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/__init__.py +0 -0
  13. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/common.py +0 -0
  14. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/holo.py +0 -0
  15. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/silcam.py +0 -0
  16. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/instrument/uvp.py +0 -0
  17. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/io.py +0 -0
  18. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/plotting.py +0 -0
  19. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/simulator/__init__.py +0 -0
  20. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/simulator/silcam.py +0 -0
  21. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/statistics.py +0 -0
  22. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/__init__.py +0 -0
  23. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/test_classify.py +0 -0
  24. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/test_notebooks.py +0 -0
  25. {pyopia-2.6.0 → pyopia-2.6.2}/pyopia/tests/test_pipeline.py +0 -0
  26. {pyopia-2.6.0 → pyopia-2.6.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyOPIA
3
- Version: 2.6.0
3
+ Version: 2.6.2
4
4
  Summary: A Python Ocean Particle Image Analysis toolbox.
5
5
  Home-page: https://github.com/sintef/pyopia
6
6
  Keywords: Ocean,Particles,Imaging,Measurement,Size distribution
@@ -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 track, Progress
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
- config filename
127
+ Config filename
128
+
125
129
  numchunks : int, optional
126
- split the dataset into chucks, and process in parallell, by default 1
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
- from pyopia.io import load_toml
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.prepare_chunking(num_chunks, average_window, bgshift_function)
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
- for filename in track(file_list, description=f'[blue]Processing progress (chunk {c})',
168
- disable=c != 0):
169
- try:
170
- logger.debug(f'Chunk {c} starting to process {filename}')
171
- processing_pipeline.run(filename)
172
- except Exception as e:
173
- logger.warning('[red]An error occured in processing, ' +
174
- 'skipping rest of pipeline and moving to next image.' +
175
- f'(chunk {c})')
176
- logger.error(e)
177
- logger.debug(''.join(traceback.format_tb(e.__traceback__)))
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
- def prepare_chunking(self, num_chunks, average_window, bgshift_function):
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
- self.chunked_files = [self.files[i:i + chunk_length] for i in range(0, len(self.files), chunk_length)]
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'] = getattr(data, '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
 
@@ -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