PyOPIA 2.5.9__tar.gz → 2.5.11__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.5.9 → pyopia-2.5.11}/PKG-INFO +1 -1
  2. pyopia-2.5.11/pyopia/__init__.py +1 -0
  3. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/cli.py +28 -5
  4. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/io.py +133 -16
  5. pyopia-2.5.9/pyopia/__init__.py +0 -1
  6. {pyopia-2.5.9 → pyopia-2.5.11}/LICENSE +0 -0
  7. {pyopia-2.5.9 → pyopia-2.5.11}/README.md +0 -0
  8. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/background.py +0 -0
  9. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/classify.py +0 -0
  10. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/exampledata.py +0 -0
  11. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/instrument/__init__.py +0 -0
  12. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/instrument/common.py +0 -0
  13. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/instrument/holo.py +0 -0
  14. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/instrument/silcam.py +0 -0
  15. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/instrument/uvp.py +0 -0
  16. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/pipeline.py +0 -0
  17. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/plotting.py +0 -0
  18. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/process.py +0 -0
  19. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/simulator/__init__.py +0 -0
  20. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/simulator/silcam.py +0 -0
  21. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/statistics.py +0 -0
  22. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/tests/__init__.py +0 -0
  23. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/tests/test_classify.py +0 -0
  24. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/tests/test_notebooks.py +0 -0
  25. {pyopia-2.5.9 → pyopia-2.5.11}/pyopia/tests/test_pipeline.py +0 -0
  26. {pyopia-2.5.9 → pyopia-2.5.11}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyOPIA
3
- Version: 2.5.9
3
+ Version: 2.5.11
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.5.11'
@@ -37,7 +37,7 @@ def docs():
37
37
 
38
38
  @app.command()
39
39
  def modify_config(existing_filename: str, modified_filename: str,
40
- raw_files=None,
40
+ raw_files=None, pixel_size=None,
41
41
  step_name=None, modify_arg=None, modify_value=None):
42
42
  '''Modify a existing config.toml file and write a new one to disc
43
43
 
@@ -49,6 +49,8 @@ def modify_config(existing_filename: str, modified_filename: str,
49
49
  e.g. config_new.toml
50
50
  raw_files : str, optional
51
51
  modify the raw file input in the `[general]` settings, by default None
52
+ pixel_size : str, optional
53
+ modify the pixel size in the `[general]` settings, by default None
52
54
  step_name : str, optional
53
55
  the name of the step to modify e.g. `segmentation`, by default None
54
56
  modify_arg : str, optional
@@ -61,11 +63,18 @@ def modify_config(existing_filename: str, modified_filename: str,
61
63
  toml_settings = pyopia.io.load_toml(existing_filename)
62
64
 
63
65
  if raw_files is not None:
64
- toml_settings['general']['raw_files'] = raw_files
66
+ toml_settings['general']['raw_files'] = f'{raw_files}'
67
+ if pixel_size is not None:
68
+ toml_settings['general']['pixel_size'] = float(pixel_size)
65
69
 
66
70
  if step_name is not None:
67
71
  try:
68
- modify_value = float(modify_value)
72
+ if modify_arg == 'average_window':
73
+ modify_value = int(modify_value)
74
+ elif modify_arg == 'threshold':
75
+ modify_value = float(modify_value)
76
+ else:
77
+ modify_value = str(modify_value)
69
78
  except ValueError:
70
79
  pass
71
80
 
@@ -177,7 +186,8 @@ def process(config_filename: str, num_chunks: int = 1):
177
186
 
178
187
 
179
188
  @app.command()
180
- def merge_mfdata(path_to_data: str, prefix='*'):
189
+ def merge_mfdata(path_to_data: str, prefix='*', overwrite_existing_partials: bool = True,
190
+ chunk_size: int = None):
181
191
  '''Combine a multi-file directory of STATS.nc files into a single '-STATS.nc' file
182
192
  that can then be loaded with {func}`pyopia.io.load_stats`
183
193
 
@@ -189,8 +199,21 @@ def merge_mfdata(path_to_data: str, prefix='*'):
189
199
  prefix : str
190
200
  Prefix to multi-file dataset (for replacing the wildcard in '*Image-D*-STATS.nc').
191
201
  Defaults to '*'
202
+
203
+ overwrite_existing_partials : bool
204
+ Do not reprocess existing merged netcdf files for each chunk if False.
205
+ Otherwise reprocess (load) and overwrite. This can be used to restart
206
+ or continue a previous merge operation as new files become available.
207
+
208
+ chunk_size : int
209
+ Process this many files together and store as partially merged netcdf files, which
210
+ are then merged at the end. Default: None, process all files together.
192
211
  '''
193
- pyopia.io.merge_and_save_mfdataset(path_to_data, prefix=prefix)
212
+ setup_logging({'general': {}})
213
+
214
+ pyopia.io.merge_and_save_mfdataset(path_to_data, prefix=prefix,
215
+ overwrite_existing_partials=overwrite_existing_partials,
216
+ chunk_size=chunk_size)
194
217
 
195
218
 
196
219
  def setup_logging(pipeline_config):
@@ -3,7 +3,7 @@ Module containing tools for datafile and metadata handling
3
3
  '''
4
4
 
5
5
  from datetime import datetime
6
-
6
+ import numpy as np
7
7
  import h5py
8
8
  import pandas as pd
9
9
  import toml
@@ -11,6 +11,7 @@ import xarray
11
11
  import os
12
12
  from glob import glob
13
13
  import xarray as xr
14
+ from tqdm.auto import tqdm
14
15
 
15
16
  from pyopia import __version__ as pyopia_version
16
17
 
@@ -178,7 +179,7 @@ def load_stats(datafilename):
178
179
  '''
179
180
 
180
181
  if datafilename.endswith('.nc'):
181
- with xarray.open_dataset(datafilename) as xstats:
182
+ with xarray.open_dataset(datafilename, engine=NETCDF_ENGINE) as xstats:
182
183
  xstats.load()
183
184
  elif datafilename.endswith('.h5'):
184
185
  logger.warning('In future, load_stats will only take .nc files')
@@ -194,7 +195,11 @@ def load_stats(datafilename):
194
195
 
195
196
 
196
197
  def combine_stats_netcdf_files(path_to_data, prefix='*'):
197
- '''Combine a multi-file directory of STATS.nc files into a 'stats' xarray dataset created by :func:`pyopia.io.write_stats`
198
+ '''.. deprecated:: 2.4.11
199
+ :class:`pyopia.io.combine_stats_netcdf_files` will be removed in version 3.0.0, it is replaced by
200
+ :class:`pyopia.io.concat_stats_netcdf_files`.
201
+
202
+ Combine a multi-file directory of STATS.nc files into a 'stats' xarray dataset created by :func:`pyopia.io.write_stats`
198
203
  when using 'append = false'
199
204
 
200
205
  Parameters
@@ -231,7 +236,63 @@ def combine_stats_netcdf_files(path_to_data, prefix='*'):
231
236
  return xstats, image_stats
232
237
 
233
238
 
234
- def merge_and_save_mfdataset(path_to_data, prefix='*'):
239
+ def concat_stats_netcdf_files(sorted_filelist):
240
+ '''Concatenate specified list of STATS.nc files into one 'xstats' xarray dataset
241
+ created by :func:`pyopia.io.write_stats when using 'append = false'.
242
+
243
+ Existing files are first loaded and then combined, so memory usage will go up with longer file lists.
244
+
245
+ Parameters
246
+ ----------
247
+ sorted_filelist : str
248
+ List of files to be combined into single dataset
249
+
250
+ Returns
251
+ -------
252
+ xstats : xarray.Dataset or None
253
+ Particle statistics and metatdata from processing steps
254
+ image_stats : xarray.Dataset or None
255
+ Summary statistics of each raw image (including those with no particles)
256
+ '''
257
+ if len(sorted_filelist) < 1:
258
+ logger.error('No files found to concatenate, doing nothing.')
259
+ return None, None
260
+
261
+ # We load one dataset at the time into a list for later merge
262
+ datasets = []
263
+ datasets_image_stats = []
264
+
265
+ # Check if we have image statistics in first file, if not, we skip checking the rest
266
+ skip_image_stats = False
267
+ try:
268
+ with xr.open_dataset(sorted_filelist[0], group='image_stats', engine=NETCDF_ENGINE) as ds:
269
+ ds.load()
270
+ except OSError:
271
+ logger.info('Could get image_stats from netcdf files for merging, returning None for this.')
272
+ skip_image_stats = True
273
+
274
+ # Load datasets from each file into the lists
275
+ for f in tqdm(sorted_filelist, desc='Loading datasets'):
276
+ with xr.open_dataset(f, engine=NETCDF_ENGINE) as ds:
277
+ ds.load()
278
+ datasets.append(ds)
279
+
280
+ if not skip_image_stats:
281
+ with xr.open_dataset(f, group='image_stats', engine=NETCDF_ENGINE) as ds:
282
+ ds.load()
283
+ datasets_image_stats.append(ds)
284
+
285
+ # Combine the individual datasets loaded above
286
+ logging.info('Combining datasets')
287
+ xstats = xr.concat(datasets, dim='index')
288
+ image_stats = None
289
+ if not skip_image_stats:
290
+ image_stats = xr.concat(datasets_image_stats, dim='timestamp')
291
+
292
+ return xstats, image_stats
293
+
294
+
295
+ def merge_and_save_mfdataset(path_to_data, prefix='*', overwrite_existing_partials=False, chunk_size=None):
235
296
  '''Combine a multi-file directory of STATS.nc files into a single '-STATS.nc' file
236
297
  that can then be loaded with :func:`pyopia.io.load_stats`
237
298
 
@@ -243,24 +304,80 @@ def merge_and_save_mfdataset(path_to_data, prefix='*'):
243
304
  prefix : str
244
305
  Prefix to multi-file dataset (for replacing the wildcard in '*Image-D*-STATS.nc').
245
306
  Defaults to '*'
307
+
308
+ overwrite_existing_partials : bool
309
+ Do not reprocess existing merged netcdf files for each chunk if False.
310
+ Otherwise reprocess (load) and overwrite. This can be used to restart
311
+ or continue a previous merge operation as new files become available.
312
+
313
+ chunk_size : int
314
+ Number of files to be loaded and merged in each step. Produces a number
315
+ of intermediate/partially merged netcdf files equal to the total number
316
+ of input files divided by chunk_size. The last chunk may contain less
317
+ files than specified, depending on the total number of files.
318
+ Default: None, which processes all files together.
246
319
  '''
320
+ logging.info(f'Combine stats netcdf files from {path_to_data}')
321
+ if (chunk_size is not None) and (chunk_size < 1):
322
+ raise ValueError(f'Invalid chunk size, must be greater than 0, was {chunk_size}')
247
323
 
248
- logging.info(f'combine stats netcdf files from {path_to_data}')
249
- xstats, image_stats = combine_stats_netcdf_files(path_to_data, prefix=prefix)
324
+ # Get sorted list of per-image stats netcdf files
325
+ sorted_filelist = sorted(glob(os.path.join(path_to_data, prefix + 'Image-D*-STATS.nc')))
250
326
 
327
+ # Chunk the file list into smaller parts if specified
328
+ num_files = len(sorted_filelist)
329
+ chunk_size_used = num_files if chunk_size is None else min(chunk_size, num_files)
330
+ num_chunks = int(np.ceil(num_files / chunk_size_used))
331
+ filelist_chunks = [sorted_filelist[i*chunk_size_used:min(num_files, (i+1)*chunk_size_used)] for i in range(num_chunks)]
332
+ infostr = f'Processing {num_chunks} partial file lists of {chunk_size_used} files each'
333
+ infostr += ', based on a total of {num_files} files.'
334
+ logging.info(infostr)
335
+
336
+ # Get config from first file in list
337
+ xstats = load_stats(sorted_filelist[0])
251
338
  settings = steps_from_xstats(xstats)
252
-
253
339
  prefix_out = os.path.basename(settings['steps']['output']['output_datafile'])
254
- output_name = os.path.join(path_to_data, prefix_out)
255
-
256
- logging.info(f'writing {output_name}')
257
- # save the particle statistics (xstats) to NetCDF
258
340
  encoding = setup_xstats_encoding(xstats)
259
- xstats.to_netcdf(output_name + '-STATS.nc', encoding=encoding, engine=NETCDF_ENGINE, format='NETCDF4')
260
- # if summary data for each raw image are available (image_stats), save this into the image_stats group
261
- if image_stats is not None:
262
- image_stats.to_netcdf(output_name + '-STATS.nc', group='image_stats', mode='a', engine=NETCDF_ENGINE)
263
- logging.info(f'writing {output_name} done.')
341
+
342
+ def process_store(i, filelist_):
343
+ output_name = os.path.join(path_to_data, f'part-{i:04d}-{prefix_out}-STATS.nc')
344
+
345
+ # Skip this chunk if the merged output file exists and overwrite is set to False
346
+ if os.path.exists(output_name) and not overwrite_existing_partials:
347
+ logging.info(f'File exists ({output_name}), skipping')
348
+ return output_name
349
+
350
+ # Load the individual datasets
351
+ xstats, image_stats = concat_stats_netcdf_files(filelist_)
352
+
353
+ # Save the particle statistics (xstats) to NetCDF
354
+ logging.info(f'Writing {output_name}')
355
+ if xstats is not None:
356
+ xstats.to_netcdf(output_name, mode='w', encoding=encoding, engine=NETCDF_ENGINE, format='NETCDF4')
357
+
358
+ # If summary data for each raw image are available (image_stats), save this into the image_stats group
359
+ if image_stats is not None:
360
+ image_stats.to_netcdf(output_name, group='image_stats', mode='a', engine=NETCDF_ENGINE)
361
+ logging.info(f'Writing {output_name} done.')
362
+
363
+ return output_name
364
+
365
+ # Loop over filelist chunkst and created merged netcdf files for each
366
+ merged_files = []
367
+ for i, filelist_ in enumerate(filelist_chunks):
368
+ output_name = process_store(i, filelist_)
369
+ merged_files.append(output_name)
370
+
371
+ # Finally, merge the partially merged files
372
+ logging.info('Doing final merge of partially merged files')
373
+ output_name = os.path.join(path_to_data, prefix_out + '-STATS.nc')
374
+ with xr.open_mfdataset(merged_files, concat_dim='index', combine='nested') as ds:
375
+ ds.to_netcdf(output_name, mode='w', encoding=encoding, engine=NETCDF_ENGINE, format='NETCDF4')
376
+
377
+ with xr.open_mfdataset(merged_files, group='image_stats', concat_dim='timestamp', combine='nested') as ds:
378
+ ds.to_netcdf(output_name, mode='a', group='image_stats', engine=NETCDF_ENGINE)
379
+
380
+ logging.info(f'Writing {output_name} done.')
264
381
 
265
382
 
266
383
  def steps_from_xstats(xstats):
@@ -1 +0,0 @@
1
- __version__ = '2.5.9'
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