PyOPIA 2.6.1__tar.gz → 2.7.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.
Files changed (26) hide show
  1. {pyopia-2.6.1 → pyopia-2.7.0}/PKG-INFO +1 -1
  2. pyopia-2.7.0/pyopia/__init__.py +1 -0
  3. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/background.py +25 -9
  4. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/cli.py +16 -10
  5. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/pipeline.py +32 -4
  6. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/process.py +1 -1
  7. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/statistics.py +1 -1
  8. pyopia-2.6.1/pyopia/__init__.py +0 -1
  9. {pyopia-2.6.1 → pyopia-2.7.0}/LICENSE +0 -0
  10. {pyopia-2.6.1 → pyopia-2.7.0}/README.md +0 -0
  11. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/classify.py +0 -0
  12. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/exampledata.py +0 -0
  13. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/instrument/__init__.py +0 -0
  14. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/instrument/common.py +0 -0
  15. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/instrument/holo.py +0 -0
  16. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/instrument/silcam.py +0 -0
  17. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/instrument/uvp.py +0 -0
  18. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/io.py +0 -0
  19. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/plotting.py +0 -0
  20. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/simulator/__init__.py +0 -0
  21. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/simulator/silcam.py +0 -0
  22. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/tests/__init__.py +0 -0
  23. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/tests/test_classify.py +0 -0
  24. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/tests/test_notebooks.py +0 -0
  25. {pyopia-2.6.1 → pyopia-2.7.0}/pyopia/tests/test_pipeline.py +0 -0
  26. {pyopia-2.6.1 → pyopia-2.7.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyOPIA
3
- Version: 2.6.1
3
+ Version: 2.7.0
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.7.0'
@@ -95,9 +95,11 @@ def shift_bgstack_fast(bgstack, imbg, imnew):
95
95
  return bgstack, imbg
96
96
 
97
97
 
98
- def correct_im_accurate(imbg, imraw):
98
+ def correct_im_accurate(imbg, imraw, divide_bg=False):
99
99
  '''
100
- Corrects raw image by subtracting the background and scaling the output
100
+ Corrects raw image by subtracting or dividing the background and scaling the output
101
+
102
+ For dividing method see: https://doi.org/10.1016/j.marpolbul.2016.11.063)
101
103
 
102
104
  There is a small chance of clipping of imc in both crushed blacks and blown
103
105
  highlights if the background or raw images are very poorly obtained
@@ -108,6 +110,9 @@ def correct_im_accurate(imbg, imraw):
108
110
  background averaged image
109
111
  imraw : float64
110
112
  raw image
113
+ divide_bg : (bool, optional)
114
+ If True, the correction will be performed by dividing the raw image by the background
115
+ Default to False
111
116
 
112
117
  Returns
113
118
  -------
@@ -115,10 +120,16 @@ def correct_im_accurate(imbg, imraw):
115
120
  corrected image, same type as input
116
121
  '''
117
122
 
118
- im_corrected = imraw - imbg
119
- im_corrected += (1 / 2 - np.percentile(im_corrected, 50))
120
-
121
- im_corrected += 1 - im_corrected.max()
123
+ if divide_bg:
124
+ imbg = np.clip(imbg, a_min=1/255, a_max=None) # Clipping the zero_value pixels
125
+ im_corrected = imraw / imbg
126
+ im_corrected += (1 / 2 - np.percentile(im_corrected, 50))
127
+ im_corrected -= im_corrected.min() # Shift the negative values to zero
128
+ im_corrected = np.clip(im_corrected, a_min=0, a_max=1)
129
+ else:
130
+ im_corrected = imraw - imbg
131
+ im_corrected += (1 / 2 - np.percentile(im_corrected, 50))
132
+ im_corrected += 1 - im_corrected.max() # Shift the positive values exceeding unity to one
122
133
 
123
134
  return im_corrected
124
135
 
@@ -207,7 +218,7 @@ class CorrectBackgroundAccurate():
207
218
  ----------
208
219
  bgshift_function : (string, optional)
209
220
  Function used to shift the background. Defaults to passing (i.e. static background)
210
- Available options are 'accurate', 'fast', or 'pass' to apply a statick background correction:
221
+ Available options are 'accurate', 'fast', or 'pass' to apply a static background correction:
211
222
 
212
223
  :func:`pyopia.background.shift_bgstack_accurate`
213
224
 
@@ -220,6 +231,10 @@ class CorrectBackgroundAccurate():
220
231
  The key in Pipeline.data of the image to be background corrected.
221
232
  Defaults to 'imraw'
222
233
 
234
+ divide_bg : (bool)
235
+ If True, it performs background correction by dividing the raw image by the background.
236
+ Default to False.
237
+
223
238
  Returns
224
239
  -------
225
240
  data : :class:`pyopia.pipeline.Data`
@@ -256,10 +271,11 @@ class CorrectBackgroundAccurate():
256
271
  Then you could use :class:`pyopia.pipeline.CorrectBackgroundNone` if you need to instead.
257
272
  '''
258
273
 
259
- def __init__(self, bgshift_function='pass', average_window=1, image_source='imraw'):
274
+ def __init__(self, bgshift_function='pass', average_window=1, image_source='imraw', divide_bg=False):
260
275
  self.bgshift_function = bgshift_function
261
276
  self.average_window = average_window
262
277
  self.image_source = image_source
278
+ self.divide_bg = divide_bg
263
279
 
264
280
  def _build_background_step(self, data):
265
281
  '''Add one layer to the background stack from the raw image in data pipeline, and update the background image.'''
@@ -284,7 +300,7 @@ class CorrectBackgroundAccurate():
284
300
  data['skip_next_steps'] = True
285
301
  return data
286
302
 
287
- data['im_corrected'] = correct_im_accurate(data['imbg'], data[self.image_source])
303
+ data['im_corrected'] = correct_im_accurate(data['imbg'], data[self.image_source], divide_bg=self.divide_bg)
288
304
 
289
305
  match self.bgshift_function:
290
306
  case 'pass':
@@ -15,6 +15,7 @@ import rich.progress
15
15
  import pandas as pd
16
16
  import threading
17
17
 
18
+ import pyopia
18
19
  import pyopia.background
19
20
  import pyopia.instrument.silcam
20
21
  import pyopia.instrument.holo
@@ -117,25 +118,27 @@ def generate_config(instrument: str, raw_files: str, model_path: str, outfolder:
117
118
 
118
119
 
119
120
  @app.command()
120
- def process(config_filename: str, num_chunks: int = 1):
121
+ def process(config_filename: str, num_chunks: int = 1, strategy: str = 'block'):
121
122
  '''Run a PyOPIA processing pipeline based on given a config.toml
122
123
 
123
124
  Parameters
124
125
  ----------
125
126
  config_filename : str
126
- config filename
127
+ Config filename
128
+
127
129
  numchunks : int, optional
128
- 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
129
131
 
132
+ strategy : str, optional
133
+ Strategy to use for chunking dataset, either `block` or `interleave`. Defult: `block`
130
134
  '''
131
- from pyopia.io import load_toml
132
- from pyopia.pipeline import Pipeline
133
-
134
135
  t1 = time.time()
135
136
 
136
137
  with Progress(transient=True) as progress:
138
+ progress.console.print(f"[blue]PYOPIA VERSION {pyopia.__version__}")
139
+
137
140
  progress.console.print("[blue]LOAD CONFIG")
138
- pipeline_config = load_toml(config_filename)
141
+ pipeline_config = pyopia.io.load_toml(config_filename)
139
142
 
140
143
  setup_logging(pipeline_config)
141
144
  logger = logging.getLogger('rich')
@@ -144,11 +147,14 @@ def process(config_filename: str, num_chunks: int = 1):
144
147
  check_chunks(num_chunks, pipeline_config)
145
148
 
146
149
  progress.console.print("[blue]OBTAIN IMAGE LIST")
147
- raw_files = pyopia.pipeline.FilesToProcess(pipeline_config['general']['raw_files'])
148
150
  conf_corrbg = pipeline_config['steps'].get('correctbackground', dict())
149
151
  average_window = conf_corrbg.get('average_window', 0)
150
152
  bgshift_function = conf_corrbg.get('bgshift_function', 'pass')
151
- 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')
152
158
 
153
159
  progress.console.print('[blue]PREPARE FOLDERS')
154
160
  if 'output' not in pipeline_config['steps']:
@@ -167,7 +173,7 @@ def process(config_filename: str, num_chunks: int = 1):
167
173
  progress.console.print("[blue]INITIALISE PIPELINE")
168
174
 
169
175
  def process_file_list(file_list, c):
170
- processing_pipeline = Pipeline(pipeline_config)
176
+ processing_pipeline = pyopia.pipeline.Pipeline(pipeline_config)
171
177
 
172
178
  with get_custom_progress_bar(f'[blue]Processing progress (chunk {c})', disable=c != 0) as pbar:
173
179
  for filename in pbar.track(file_list, description=f'[blue]Processing progress (chunk {c})'):
@@ -373,25 +373,53 @@ class FilesToProcess:
373
373
  with open(path_to_filelist, 'w') as fh:
374
374
  [fh.writelines(L + '\n') for L in self.files]
375
375
 
376
- 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
+ '''
377
395
  if num_chunks > len(self.files) // 2:
378
396
  raise RuntimeError('Number of chunks exceeds more than half the number of files to process. Use less chunks.')
379
- self.chunk_files(num_chunks)
397
+ self.chunk_files(num_chunks, strategy)
380
398
  self.build_initial_background_files(average_window=average_window)
381
399
  self.insert_bg_files_into_chunks(bgshift_function=bgshift_function)
382
400
 
383
- def chunk_files(self, num_chunks: int):
401
+ def chunk_files(self, num_chunks: int, strategy: str = 'block'):
384
402
  '''Chunk the file list and create FilesToProcess.chunked_files
385
403
 
386
404
  Parameters
387
405
  ----------
388
406
  num_chunks : int
389
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`
390
410
  '''
391
411
  if num_chunks < 1:
392
412
  raise RuntimeError('You must have at least one chunk')
393
413
  chunk_length = int(np.ceil(len(self.files) / num_chunks))
394
- 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')
395
423
 
396
424
  def insert_bg_files_into_chunks(self, bgshift_function='pass'):
397
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
 
@@ -534,7 +534,7 @@ def get_j(dias, number_distribution):
534
534
 
535
535
  # use polyfit to obtain the slope of the ditriubtion in log-space (which is
536
536
  # assumed near-linear in most parts of the ocean)
537
- p = np.polyfit(np.log(dias[ind]), np.log(number_distribution[ind]), 1)
537
+ p = np.polyfit(np.log(dias[ind]), np.log(number_distribution[ind], where=number_distribution[ind] > 0), 1)
538
538
  junge_slope = p[0]
539
539
  return junge_slope
540
540
 
@@ -1 +0,0 @@
1
- __version__ = '2.6.1'
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