PyOPIA 2.5.10__tar.gz → 2.5.12__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.5.10 → pyopia-2.5.12}/PKG-INFO +1 -1
- pyopia-2.5.12/pyopia/__init__.py +1 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/cli.py +16 -2
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/io.py +141 -17
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/process.py +6 -8
- pyopia-2.5.10/pyopia/__init__.py +0 -1
- {pyopia-2.5.10 → pyopia-2.5.12}/LICENSE +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/README.md +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/background.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/classify.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/exampledata.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/instrument/__init__.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/instrument/common.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/instrument/holo.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/instrument/silcam.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/instrument/uvp.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/pipeline.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/plotting.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/simulator/__init__.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/simulator/silcam.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/statistics.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/tests/__init__.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/tests/test_classify.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/tests/test_notebooks.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyopia/tests/test_pipeline.py +0 -0
- {pyopia-2.5.10 → pyopia-2.5.12}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.5.12'
|
|
@@ -186,7 +186,8 @@ def process(config_filename: str, num_chunks: int = 1):
|
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
@app.command()
|
|
189
|
-
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):
|
|
190
191
|
'''Combine a multi-file directory of STATS.nc files into a single '-STATS.nc' file
|
|
191
192
|
that can then be loaded with {func}`pyopia.io.load_stats`
|
|
192
193
|
|
|
@@ -198,8 +199,21 @@ def merge_mfdata(path_to_data: str, prefix='*'):
|
|
|
198
199
|
prefix : str
|
|
199
200
|
Prefix to multi-file dataset (for replacing the wildcard in '*Image-D*-STATS.nc').
|
|
200
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.
|
|
201
211
|
'''
|
|
202
|
-
|
|
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)
|
|
203
217
|
|
|
204
218
|
|
|
205
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
|
|
|
@@ -83,14 +84,21 @@ def write_stats(stats,
|
|
|
83
84
|
existing_stats = load_stats(datafilename + '-STATS.nc')
|
|
84
85
|
xstats = xarray.concat([existing_stats, xstats], 'index')
|
|
85
86
|
elif not append:
|
|
87
|
+
# When appending, only store the last row in the image_stats DataFrame
|
|
86
88
|
datafilename += ('-Image-D' +
|
|
87
89
|
str(xstats['timestamp'][0].values).replace('-', '').replace(':', '').replace('.', '-'))
|
|
90
|
+
|
|
88
91
|
encoding = setup_xstats_encoding(xstats)
|
|
89
92
|
xstats.to_netcdf(datafilename + '-STATS.nc', encoding=encoding, engine=NETCDF_ENGINE, format='NETCDF4')
|
|
90
93
|
|
|
91
94
|
# If we have image statistics (summary data for each raw image), add the image_stats a group
|
|
92
95
|
if image_stats is not None:
|
|
93
|
-
|
|
96
|
+
if append:
|
|
97
|
+
ximage_stats = image_stats.to_xarray()
|
|
98
|
+
else:
|
|
99
|
+
ximage_stats = image_stats.loc[[image_stats.index[-1]], :].to_xarray()
|
|
100
|
+
|
|
101
|
+
ximage_stats.to_netcdf(datafilename + '-STATS.nc', group='image_stats', mode='a', engine=NETCDF_ENGINE)
|
|
94
102
|
|
|
95
103
|
|
|
96
104
|
def setup_xstats_encoding(xstats, string_vars=['export name', 'holo_filename']):
|
|
@@ -178,7 +186,7 @@ def load_stats(datafilename):
|
|
|
178
186
|
'''
|
|
179
187
|
|
|
180
188
|
if datafilename.endswith('.nc'):
|
|
181
|
-
with xarray.open_dataset(datafilename) as xstats:
|
|
189
|
+
with xarray.open_dataset(datafilename, engine=NETCDF_ENGINE) as xstats:
|
|
182
190
|
xstats.load()
|
|
183
191
|
elif datafilename.endswith('.h5'):
|
|
184
192
|
logger.warning('In future, load_stats will only take .nc files')
|
|
@@ -194,7 +202,11 @@ def load_stats(datafilename):
|
|
|
194
202
|
|
|
195
203
|
|
|
196
204
|
def combine_stats_netcdf_files(path_to_data, prefix='*'):
|
|
197
|
-
'''
|
|
205
|
+
'''.. deprecated:: 2.4.11
|
|
206
|
+
:class:`pyopia.io.combine_stats_netcdf_files` will be removed in version 3.0.0, it is replaced by
|
|
207
|
+
:class:`pyopia.io.concat_stats_netcdf_files`.
|
|
208
|
+
|
|
209
|
+
Combine a multi-file directory of STATS.nc files into a 'stats' xarray dataset created by :func:`pyopia.io.write_stats`
|
|
198
210
|
when using 'append = false'
|
|
199
211
|
|
|
200
212
|
Parameters
|
|
@@ -231,7 +243,63 @@ def combine_stats_netcdf_files(path_to_data, prefix='*'):
|
|
|
231
243
|
return xstats, image_stats
|
|
232
244
|
|
|
233
245
|
|
|
234
|
-
def
|
|
246
|
+
def concat_stats_netcdf_files(sorted_filelist):
|
|
247
|
+
'''Concatenate specified list of STATS.nc files into one 'xstats' xarray dataset
|
|
248
|
+
created by :func:`pyopia.io.write_stats when using 'append = false'.
|
|
249
|
+
|
|
250
|
+
Existing files are first loaded and then combined, so memory usage will go up with longer file lists.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
sorted_filelist : str
|
|
255
|
+
List of files to be combined into single dataset
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
xstats : xarray.Dataset or None
|
|
260
|
+
Particle statistics and metatdata from processing steps
|
|
261
|
+
image_stats : xarray.Dataset or None
|
|
262
|
+
Summary statistics of each raw image (including those with no particles)
|
|
263
|
+
'''
|
|
264
|
+
if len(sorted_filelist) < 1:
|
|
265
|
+
logger.error('No files found to concatenate, doing nothing.')
|
|
266
|
+
return None, None
|
|
267
|
+
|
|
268
|
+
# We load one dataset at the time into a list for later merge
|
|
269
|
+
datasets = []
|
|
270
|
+
datasets_image_stats = []
|
|
271
|
+
|
|
272
|
+
# Check if we have image statistics in first file, if not, we skip checking the rest
|
|
273
|
+
skip_image_stats = False
|
|
274
|
+
try:
|
|
275
|
+
with xr.open_dataset(sorted_filelist[0], group='image_stats', engine=NETCDF_ENGINE) as ds:
|
|
276
|
+
ds.load()
|
|
277
|
+
except OSError:
|
|
278
|
+
logger.info('Could get image_stats from netcdf files for merging, returning None for this.')
|
|
279
|
+
skip_image_stats = True
|
|
280
|
+
|
|
281
|
+
# Load datasets from each file into the lists
|
|
282
|
+
for f in tqdm(sorted_filelist, desc='Loading datasets'):
|
|
283
|
+
with xr.open_dataset(f, engine=NETCDF_ENGINE) as ds:
|
|
284
|
+
ds.load()
|
|
285
|
+
datasets.append(ds)
|
|
286
|
+
|
|
287
|
+
if not skip_image_stats:
|
|
288
|
+
with xr.open_dataset(f, group='image_stats', engine=NETCDF_ENGINE) as ds:
|
|
289
|
+
ds.load()
|
|
290
|
+
datasets_image_stats.append(ds)
|
|
291
|
+
|
|
292
|
+
# Combine the individual datasets loaded above
|
|
293
|
+
logging.info('Combining datasets')
|
|
294
|
+
xstats = xr.concat(datasets, dim='index')
|
|
295
|
+
image_stats = None
|
|
296
|
+
if not skip_image_stats:
|
|
297
|
+
image_stats = xr.concat(datasets_image_stats, dim='timestamp')
|
|
298
|
+
|
|
299
|
+
return xstats, image_stats
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def merge_and_save_mfdataset(path_to_data, prefix='*', overwrite_existing_partials=False, chunk_size=None):
|
|
235
303
|
'''Combine a multi-file directory of STATS.nc files into a single '-STATS.nc' file
|
|
236
304
|
that can then be loaded with :func:`pyopia.io.load_stats`
|
|
237
305
|
|
|
@@ -243,24 +311,80 @@ def merge_and_save_mfdataset(path_to_data, prefix='*'):
|
|
|
243
311
|
prefix : str
|
|
244
312
|
Prefix to multi-file dataset (for replacing the wildcard in '*Image-D*-STATS.nc').
|
|
245
313
|
Defaults to '*'
|
|
314
|
+
|
|
315
|
+
overwrite_existing_partials : bool
|
|
316
|
+
Do not reprocess existing merged netcdf files for each chunk if False.
|
|
317
|
+
Otherwise reprocess (load) and overwrite. This can be used to restart
|
|
318
|
+
or continue a previous merge operation as new files become available.
|
|
319
|
+
|
|
320
|
+
chunk_size : int
|
|
321
|
+
Number of files to be loaded and merged in each step. Produces a number
|
|
322
|
+
of intermediate/partially merged netcdf files equal to the total number
|
|
323
|
+
of input files divided by chunk_size. The last chunk may contain less
|
|
324
|
+
files than specified, depending on the total number of files.
|
|
325
|
+
Default: None, which processes all files together.
|
|
246
326
|
'''
|
|
327
|
+
logging.info(f'Combine stats netcdf files from {path_to_data}')
|
|
328
|
+
if (chunk_size is not None) and (chunk_size < 1):
|
|
329
|
+
raise ValueError(f'Invalid chunk size, must be greater than 0, was {chunk_size}')
|
|
247
330
|
|
|
248
|
-
|
|
249
|
-
|
|
331
|
+
# Get sorted list of per-image stats netcdf files
|
|
332
|
+
sorted_filelist = sorted(glob(os.path.join(path_to_data, prefix + 'Image-D*-STATS.nc')))
|
|
250
333
|
|
|
334
|
+
# Chunk the file list into smaller parts if specified
|
|
335
|
+
num_files = len(sorted_filelist)
|
|
336
|
+
chunk_size_used = num_files if chunk_size is None else min(chunk_size, num_files)
|
|
337
|
+
num_chunks = int(np.ceil(num_files / chunk_size_used))
|
|
338
|
+
filelist_chunks = [sorted_filelist[i*chunk_size_used:min(num_files, (i+1)*chunk_size_used)] for i in range(num_chunks)]
|
|
339
|
+
infostr = f'Processing {num_chunks} partial file lists of {chunk_size_used} files each'
|
|
340
|
+
infostr += f', based on a total of {num_files} files.'
|
|
341
|
+
logging.info(infostr)
|
|
342
|
+
|
|
343
|
+
# Get config from first file in list
|
|
344
|
+
xstats = load_stats(sorted_filelist[0])
|
|
251
345
|
settings = steps_from_xstats(xstats)
|
|
252
|
-
|
|
253
346
|
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
347
|
encoding = setup_xstats_encoding(xstats)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
348
|
+
|
|
349
|
+
def process_store(i, filelist_):
|
|
350
|
+
output_name = os.path.join(path_to_data, f'part-{i:04d}-{prefix_out}-STATS.nc')
|
|
351
|
+
|
|
352
|
+
# Skip this chunk if the merged output file exists and overwrite is set to False
|
|
353
|
+
if os.path.exists(output_name) and not overwrite_existing_partials:
|
|
354
|
+
logging.info(f'File exists ({output_name}), skipping')
|
|
355
|
+
return output_name
|
|
356
|
+
|
|
357
|
+
# Load the individual datasets
|
|
358
|
+
xstats, image_stats = concat_stats_netcdf_files(filelist_)
|
|
359
|
+
|
|
360
|
+
# Save the particle statistics (xstats) to NetCDF
|
|
361
|
+
logging.info(f'Writing {output_name}')
|
|
362
|
+
if xstats is not None:
|
|
363
|
+
xstats.to_netcdf(output_name, mode='w', encoding=encoding, engine=NETCDF_ENGINE, format='NETCDF4')
|
|
364
|
+
|
|
365
|
+
# If summary data for each raw image are available (image_stats), save this into the image_stats group
|
|
366
|
+
if image_stats is not None:
|
|
367
|
+
image_stats.to_netcdf(output_name, group='image_stats', mode='a', engine=NETCDF_ENGINE)
|
|
368
|
+
logging.info(f'Writing {output_name} done.')
|
|
369
|
+
|
|
370
|
+
return output_name
|
|
371
|
+
|
|
372
|
+
# Loop over filelist chunkst and created merged netcdf files for each
|
|
373
|
+
merged_files = []
|
|
374
|
+
for i, filelist_ in enumerate(filelist_chunks):
|
|
375
|
+
output_name = process_store(i, filelist_)
|
|
376
|
+
merged_files.append(output_name)
|
|
377
|
+
|
|
378
|
+
# Finally, merge the partially merged files
|
|
379
|
+
logging.info('Doing final merge of partially merged files')
|
|
380
|
+
output_name = os.path.join(path_to_data, prefix_out + '-STATS.nc')
|
|
381
|
+
with xr.open_mfdataset(merged_files, concat_dim='index', combine='nested') as ds:
|
|
382
|
+
ds.to_netcdf(output_name, mode='w', encoding=encoding, engine=NETCDF_ENGINE, format='NETCDF4')
|
|
383
|
+
|
|
384
|
+
with xr.open_mfdataset(merged_files, group='image_stats', concat_dim='timestamp', combine='nested') as ds:
|
|
385
|
+
ds.to_netcdf(output_name, mode='a', group='image_stats', engine=NETCDF_ENGINE)
|
|
386
|
+
|
|
387
|
+
logging.info(f'Writing {output_name} done.')
|
|
264
388
|
|
|
265
389
|
|
|
266
390
|
def steps_from_xstats(xstats):
|
|
@@ -259,14 +259,12 @@ def extract_particles(imc, timestamp, Classification, region_properties,
|
|
|
259
259
|
stats : DataFrame
|
|
260
260
|
List of particle statistics for every particle, according to Partstats class
|
|
261
261
|
'''
|
|
262
|
-
|
|
262
|
+
num_rows = max(len(region_properties), 1)
|
|
263
|
+
filenames = ['not_exported'] * num_rows
|
|
263
264
|
|
|
264
265
|
if Classification is not None:
|
|
265
266
|
# pre-allocation
|
|
266
|
-
predictions = np.zeros((len(
|
|
267
|
-
len(Classification.class_labels)),
|
|
268
|
-
dtype='float64')
|
|
269
|
-
predictions *= np.nan
|
|
267
|
+
predictions = np.nan * np.zeros((num_rows, len(Classification.class_labels)), dtype='float64')
|
|
270
268
|
|
|
271
269
|
# obtain the original image filename from the timestamp
|
|
272
270
|
filename = timestamp.strftime('D%Y%m%dT%H%M%S.%f')
|
|
@@ -292,8 +290,8 @@ def extract_particles(imc, timestamp, Classification, region_properties,
|
|
|
292
290
|
HDF5File = None
|
|
293
291
|
|
|
294
292
|
# pre-allocate some things
|
|
295
|
-
data = np.zeros((
|
|
296
|
-
bboxes = np.zeros((
|
|
293
|
+
data = np.nan * np.zeros((num_rows, len(propnames)), dtype=np.float64)
|
|
294
|
+
bboxes = np.nan * np.zeros((num_rows, 4), dtype=np.float64)
|
|
297
295
|
nb_extractable_part = 0
|
|
298
296
|
|
|
299
297
|
for i, el in enumerate(region_properties):
|
|
@@ -630,7 +628,7 @@ class CalculateImageStats():
|
|
|
630
628
|
data['image_stats'].loc[data['timestamp'], 'saturation'] = image_saturation
|
|
631
629
|
|
|
632
630
|
# Skip remaining calculations if no particles where found
|
|
633
|
-
if data['stats'].size == 0:
|
|
631
|
+
if data['stats'].dropna().size == 0:
|
|
634
632
|
return data
|
|
635
633
|
|
|
636
634
|
# Calculate D50, nc and vc stats
|
pyopia-2.5.10/pyopia/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.5.10'
|
|
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
|