PyOPIA 2.12.1__tar.gz → 2.13.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.
- {pyopia-2.12.1 → pyopia-2.13.0}/PKG-INFO +20 -2
- {pyopia-2.12.1 → pyopia-2.13.0}/README.md +19 -1
- pyopia-2.13.0/pyopia/__init__.py +1 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/cli.py +44 -0
- pyopia-2.13.0/pyopia/dataexport/__init__.py +0 -0
- pyopia-2.13.0/pyopia/dataexport/ecotaxa.py +239 -0
- pyopia-2.12.1/pyopia/__init__.py +0 -1
- {pyopia-2.12.1 → pyopia-2.13.0}/.gitignore +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/LICENSE +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/auxillarydata.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/background.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/cf_metadata.json +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/classify.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/exampledata.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/instrument/__init__.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/instrument/common.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/instrument/holo.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/instrument/silcam.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/instrument/uvp.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/io.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/metadata.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/pipeline.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/plotting.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/process.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/simulator/__init__.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/simulator/silcam.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/statistics.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/tests/__init__.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/tests/test_auxillarydata.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/tests/test_classify.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/tests/test_io.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/tests/test_notebooks.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyopia/tests/test_pipeline.py +0 -0
- {pyopia-2.12.1 → pyopia-2.13.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyOPIA
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.13.0
|
|
4
4
|
Summary: A Python Ocean Particle Image Analysis toolbox.
|
|
5
5
|
Project-URL: Repository, https://github.com/sintef/pyopia
|
|
6
6
|
Project-URL: Documentation, https://pyopia.readthedocs.io
|
|
@@ -57,11 +57,29 @@ A Python Ocean Particle Image Analysis toolbox
|
|
|
57
57
|
1) Install [uv](https://docs.astral.sh/uv/getting-started/installation)
|
|
58
58
|
2) Initialize PyOPIA project with a small example image dataset and run processing
|
|
59
59
|
```bash
|
|
60
|
-
uvx --python 3.12 --from pyopia[classification] pyopia
|
|
60
|
+
uvx --python 3.12 --from pyopia[classification] pyopia init-project pyopiatest --example-data
|
|
61
|
+
```
|
|
62
|
+
```bash
|
|
61
63
|
cd pyopiatest
|
|
64
|
+
```
|
|
65
|
+
```bash
|
|
62
66
|
uvx --python 3.12 --from pyopia[classification] pyopia process config.toml
|
|
63
67
|
```
|
|
64
68
|
3) Inspect the processed particle statistics in the processed/ folder
|
|
69
|
+
4) Merge the individual processed image STATS files into a single STATS.nc file, then create a montage of the processed STATS.nc
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uvx --python 3.12 --from pyopia[classification] pyopia merge-mfdata processed
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uvx --python 3.12 --from pyopia[classification] pyopia make-montage processed\pyopiatest-STATS.nc
|
|
77
|
+
```
|
|
78
|
+
5) Visualise the monatge of all processed singular particle images in one
|
|
79
|
+
```bash
|
|
80
|
+
montage.png
|
|
81
|
+
```
|
|
82
|
+
Will show you a montage of all the processed particle images in one.
|
|
65
83
|
|
|
66
84
|
See the documentation for more information on how to install and use PyOPIA.
|
|
67
85
|
|
|
@@ -8,11 +8,29 @@ A Python Ocean Particle Image Analysis toolbox
|
|
|
8
8
|
1) Install [uv](https://docs.astral.sh/uv/getting-started/installation)
|
|
9
9
|
2) Initialize PyOPIA project with a small example image dataset and run processing
|
|
10
10
|
```bash
|
|
11
|
-
uvx --python 3.12 --from pyopia[classification] pyopia
|
|
11
|
+
uvx --python 3.12 --from pyopia[classification] pyopia init-project pyopiatest --example-data
|
|
12
|
+
```
|
|
13
|
+
```bash
|
|
12
14
|
cd pyopiatest
|
|
15
|
+
```
|
|
16
|
+
```bash
|
|
13
17
|
uvx --python 3.12 --from pyopia[classification] pyopia process config.toml
|
|
14
18
|
```
|
|
15
19
|
3) Inspect the processed particle statistics in the processed/ folder
|
|
20
|
+
4) Merge the individual processed image STATS files into a single STATS.nc file, then create a montage of the processed STATS.nc
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uvx --python 3.12 --from pyopia[classification] pyopia merge-mfdata processed
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx --python 3.12 --from pyopia[classification] pyopia make-montage processed\pyopiatest-STATS.nc
|
|
28
|
+
```
|
|
29
|
+
5) Visualise the monatge of all processed singular particle images in one
|
|
30
|
+
```bash
|
|
31
|
+
montage.png
|
|
32
|
+
```
|
|
33
|
+
Will show you a montage of all the processed particle images in one.
|
|
16
34
|
|
|
17
35
|
See the documentation for more information on how to install and use PyOPIA.
|
|
18
36
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.13.0"
|
|
@@ -41,6 +41,7 @@ import pyopia.statistics
|
|
|
41
41
|
import pyopia.auxillarydata
|
|
42
42
|
import pyopia.exampledata
|
|
43
43
|
import pyopia.metadata
|
|
44
|
+
import pyopia.dataexport.ecotaxa
|
|
44
45
|
|
|
45
46
|
app = typer.Typer()
|
|
46
47
|
|
|
@@ -473,6 +474,49 @@ def make_montage(
|
|
|
473
474
|
plt.savefig(output_filename, dpi=300, bbox_inches="tight")
|
|
474
475
|
|
|
475
476
|
|
|
477
|
+
@app.command()
|
|
478
|
+
def export_to_ecotaxa(
|
|
479
|
+
stats_filename: pathlib.Path,
|
|
480
|
+
export_filename: pathlib.Path,
|
|
481
|
+
make_label_folders: bool = False,
|
|
482
|
+
filter_variable: Annotated[Tuple[str, float, float], typer.Option()] = [
|
|
483
|
+
None,
|
|
484
|
+
None,
|
|
485
|
+
None,
|
|
486
|
+
],
|
|
487
|
+
):
|
|
488
|
+
"""EcoTaxa export: Create a zip file with particle images and statistics csv
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
config_filename : pathlib.Path
|
|
493
|
+
Config filename
|
|
494
|
+
output_filename: pathlib.Path
|
|
495
|
+
Name of zip file to contain exported particle images and statistics
|
|
496
|
+
make_label_folders : bool, optional, default False
|
|
497
|
+
If True, store particle images in sub-folders with label names.
|
|
498
|
+
NB: This must be False to create an EcoTaxa compatible zip file.
|
|
499
|
+
filter_variable: list
|
|
500
|
+
Variables to filter on (name, min, max), e.g. ['depth', 5, None]
|
|
501
|
+
"""
|
|
502
|
+
print("[blue]LOAD STATS")
|
|
503
|
+
xstats = pyopia.io.load_stats(str(stats_filename))
|
|
504
|
+
|
|
505
|
+
# Filter the stats
|
|
506
|
+
if filter_variable[0] is not None:
|
|
507
|
+
print(filter_variable)
|
|
508
|
+
xstats = xstats.where(
|
|
509
|
+
(xstats[filter_variable[0]] <= filter_variable[2])
|
|
510
|
+
& (xstats[filter_variable[0]] >= filter_variable[1])
|
|
511
|
+
).dropna(dim="index")
|
|
512
|
+
|
|
513
|
+
print("[blue]CREATING ECOTAXA BUNDLE")
|
|
514
|
+
ecotaxa_export = pyopia.dataexport.ecotaxa.EcotaxaExporter()
|
|
515
|
+
ecotaxa_export.create_bundle(
|
|
516
|
+
xstats, export_filename, make_label_folders=make_label_folders
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
476
520
|
def process_file_list(file_list, pipeline_config, c):
|
|
477
521
|
"""Run a PyOPIA processing pipeline for a chuncked list of files based on a given config.toml
|
|
478
522
|
|
|
File without changes
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Create data bundle from PyOPIA stats that can be imported into EcoTaxa.
|
|
3
|
+
|
|
4
|
+
EcoTaxa: https://ecotaxa.obs-vlfr.fr/
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pyopia
|
|
9
|
+
import pyopia.io
|
|
10
|
+
import pyopia.statistics
|
|
11
|
+
import zipfile
|
|
12
|
+
import io
|
|
13
|
+
import xarray as xr
|
|
14
|
+
import pandas as pd
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from tqdm.rich import tqdm
|
|
19
|
+
|
|
20
|
+
# Define the mapping from EcoTaxa variable names to PyOPIA stats column names.
|
|
21
|
+
# The type expected by EcoTaxa is also defined.
|
|
22
|
+
# Direct mapping from PyOPIA stats row values (statsrows) is possible in many cases.
|
|
23
|
+
# Where no direct mapping from a single PyOPIA stats variable exists, a combination
|
|
24
|
+
# or retrieval of other relevant metadata is used to construct the EcoTaxa value.
|
|
25
|
+
_ecotaxa_dict = {
|
|
26
|
+
"img_file_name": ("export_name", "str"), # source: statsrow
|
|
27
|
+
"img_rank": ("img_rank", "float"), # source: we will make it exist
|
|
28
|
+
"object_id": ("export_name", "str"), # source: statsrow
|
|
29
|
+
"object_lat": (
|
|
30
|
+
"latitude",
|
|
31
|
+
"float",
|
|
32
|
+
), # needs to be in decimal degrees, source: auxillary data
|
|
33
|
+
"object_lon": (
|
|
34
|
+
"longitude",
|
|
35
|
+
"float",
|
|
36
|
+
), # needs to be in decimal degrees, source: auxillary data
|
|
37
|
+
"object_date": (
|
|
38
|
+
"timestamp",
|
|
39
|
+
"str",
|
|
40
|
+
), # UTC, format: YYYYMMDD, source: statsrow timestamp.dt.strftime(%Y%m%d)
|
|
41
|
+
"object_time": (
|
|
42
|
+
"timestamp",
|
|
43
|
+
"str",
|
|
44
|
+
), # UTC, format: HHMMSS, source: statsrow timestamp.dt.strftime(%H%M%S)
|
|
45
|
+
"object_depth_min": ("depth", "float"), # source: statsrow
|
|
46
|
+
"object_depth_max": ("depth", "float"), # source: statsrow
|
|
47
|
+
"object_major": ("major_axis_length", "float"), # source: statsrow
|
|
48
|
+
"object_minor": ("minor_axis_length", "float"), # source: statsrow
|
|
49
|
+
"object_circ.": ("equivalent_diameter", "float"), # source: statsrow
|
|
50
|
+
"process_id": (
|
|
51
|
+
"process_id",
|
|
52
|
+
"float",
|
|
53
|
+
), # If missing will be added by EcoTaxa
|
|
54
|
+
"process_img_software_version": ("pyopia.__version__", "str"),
|
|
55
|
+
"process_img_resolution": ("process_img_resolution", "float"),
|
|
56
|
+
"process_particle_pixel_size_?m": (
|
|
57
|
+
"pixel_size",
|
|
58
|
+
"float",
|
|
59
|
+
), # Needs to be converted from pixels to micrometers, the ? is a choice of EcoTaxa
|
|
60
|
+
"process_date": (
|
|
61
|
+
"process_date",
|
|
62
|
+
"str",
|
|
63
|
+
), # Added by custom step in create_bundle, from Modified time in PyOPIA netcdf (UTC)
|
|
64
|
+
"process_time": (
|
|
65
|
+
"process_time",
|
|
66
|
+
"str",
|
|
67
|
+
), # Same as process_date. datetime.now(timezone.utc).strftime("%H%M%S").
|
|
68
|
+
"acq_id": ("acq_id", "float"), # If missing will be added by EcoTaxa
|
|
69
|
+
"sample_id": ("sample_id", "float"), # If missing will be added by EcoTaxa
|
|
70
|
+
"sample_stationid": (
|
|
71
|
+
"sample_stationid",
|
|
72
|
+
"str",
|
|
73
|
+
), # Will be constructed from project name and station (PyOPIA netcdf)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Some columns requires formatting or transformations
|
|
77
|
+
_ecotaxa_formatters = {
|
|
78
|
+
"object_date": lambda x: x.strftime("%Y%m%d"),
|
|
79
|
+
"object_time": lambda x: x.strftime("%H%M%S"),
|
|
80
|
+
"img_file_name": lambda x: x + ".png",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_ecotaxa_types = {
|
|
84
|
+
"float": "[f]",
|
|
85
|
+
"str": "[t]",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class EcotaxaExporter:
|
|
90
|
+
"""Export particle statistics (xstats) and images (ROIs) to a zip file for EcoTaxa import"""
|
|
91
|
+
|
|
92
|
+
def statsrow_to_ecotaxarow(self, statsrow, xstats_attrs):
|
|
93
|
+
"""Create a row in the EcoTaxa import file from PyOPIA stats row"""
|
|
94
|
+
ecotaxarow = dict()
|
|
95
|
+
for k, (statsname, statstype) in _ecotaxa_dict.items():
|
|
96
|
+
formatter = _ecotaxa_formatters.get(k, lambda x: x)
|
|
97
|
+
if statsname in statsrow.index:
|
|
98
|
+
ecotaxarow[k] = formatter(statsrow.get(statsname))
|
|
99
|
+
elif statsname in xstats_attrs:
|
|
100
|
+
ecotaxarow[k] = formatter(xstats_attrs.get(statsname))
|
|
101
|
+
else:
|
|
102
|
+
ecotaxarow[k] = np.nan
|
|
103
|
+
# type casting
|
|
104
|
+
return ecotaxarow
|
|
105
|
+
|
|
106
|
+
def create_bundle(
|
|
107
|
+
self,
|
|
108
|
+
xstats: xr.Dataset,
|
|
109
|
+
export_filename: Path,
|
|
110
|
+
roi_dir: Optional[Path] = None,
|
|
111
|
+
make_label_folders=False,
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
Create an EcoTaxa import bundle file containing particle images and stats csv.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
xstats : xr.Dataset
|
|
119
|
+
A dataset containing particle statistics and metadata.
|
|
120
|
+
export_filename : pathlib.Path
|
|
121
|
+
Path to the output `.zip` file that will contain the bundle.
|
|
122
|
+
roi_dir : pathlib.Path, optional
|
|
123
|
+
Directory containing particle image files to include in the bundle.
|
|
124
|
+
If None, the roi_dir in the xstats steps metadata will be used.
|
|
125
|
+
make_label_folders : bool, optional, default False
|
|
126
|
+
If True, store particle images in sub-folders with label names.
|
|
127
|
+
NB: This must be False to create an EcoTaxa compatible zip file.
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
None
|
|
131
|
+
This method does not return a value. It writes a zip file.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
# Reconstruct PyOPIA config from xstats
|
|
135
|
+
self.config = pyopia.io.steps_from_xstats(xstats)
|
|
136
|
+
|
|
137
|
+
self.export_filename = export_filename
|
|
138
|
+
|
|
139
|
+
# Name of particle roi directory from config, unless overridden by input argument
|
|
140
|
+
self.roi_dir = (
|
|
141
|
+
Path(self.config["steps"]["statextract"]["export_outputpath"])
|
|
142
|
+
if roi_dir is None
|
|
143
|
+
else roi_dir
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Create statistics DataFrame, add classification best guess info for all particles
|
|
147
|
+
self.df_stats_export = pyopia.statistics.add_best_guesses_to_stats(
|
|
148
|
+
xstats.to_pandas()
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
buffer = io.BytesIO()
|
|
152
|
+
with zipfile.ZipFile(self.export_filename, mode="w") as zip:
|
|
153
|
+
# Export particle statistics dataframe to buffer as csv
|
|
154
|
+
buffer.seek(0)
|
|
155
|
+
buffer.truncate(0)
|
|
156
|
+
self.df_stats_export.to_csv(buffer)
|
|
157
|
+
|
|
158
|
+
ecotaxarows = []
|
|
159
|
+
|
|
160
|
+
# Write particle statistics csv to zip file, only if labelled folders are enabled
|
|
161
|
+
# This file is not used by EcoTaxa import
|
|
162
|
+
if make_label_folders:
|
|
163
|
+
buffer.seek(0)
|
|
164
|
+
zip.writestr("particle_statistics.csv", buffer.read())
|
|
165
|
+
|
|
166
|
+
# create it outside here
|
|
167
|
+
for idx, (_, row) in enumerate(
|
|
168
|
+
tqdm(
|
|
169
|
+
self.df_stats_export.iterrows(), total=self.df_stats_export.shape[0]
|
|
170
|
+
)
|
|
171
|
+
):
|
|
172
|
+
# Get particle image
|
|
173
|
+
export_name = row["export_name"]
|
|
174
|
+
if export_name == "not_exported":
|
|
175
|
+
continue
|
|
176
|
+
particle_image = pyopia.statistics.roi_from_export_name(
|
|
177
|
+
export_name, self.roi_dir
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Convert PyOPIA stats row to EcoTaxa import row
|
|
181
|
+
ecotaxarows.append(self.statsrow_to_ecotaxarow(row, xstats.attrs))
|
|
182
|
+
|
|
183
|
+
# Save the particle image to the buffer
|
|
184
|
+
buffer.seek(0)
|
|
185
|
+
buffer.truncate(0)
|
|
186
|
+
plt.imsave(buffer, particle_image, format="png")
|
|
187
|
+
|
|
188
|
+
# Write the buffer to the zip file with a filename
|
|
189
|
+
label_folder = ""
|
|
190
|
+
if make_label_folders:
|
|
191
|
+
label_folder = row["best guess"].replace("probability_", "") + "/"
|
|
192
|
+
buffer.seek(0)
|
|
193
|
+
zip.writestr(f"{label_folder}{export_name}.png", buffer.read())
|
|
194
|
+
|
|
195
|
+
# Convert export table to Pandas DataFrame
|
|
196
|
+
df_ecotaxa_export_table = pd.DataFrame(ecotaxarows)
|
|
197
|
+
df_ecotaxa_types = pd.DataFrame(
|
|
198
|
+
[
|
|
199
|
+
{
|
|
200
|
+
k: _ecotaxa_types[ettype]
|
|
201
|
+
for k, (_, ettype) in _ecotaxa_dict.items()
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Add some EcoTaxa required information manually here.
|
|
207
|
+
# Construct from multiple PyOPIA data values or metadata as needed.
|
|
208
|
+
pixel_size = self.config["general"]["pixel_size"]
|
|
209
|
+
df_ecotaxa_export_table["process_img_software_version"] = (
|
|
210
|
+
f"PyOPIA {xstats.attrs['PyOPIA_version']}"
|
|
211
|
+
)
|
|
212
|
+
df_ecotaxa_export_table["img_rank"] = 0
|
|
213
|
+
df_ecotaxa_export_table["process_particle_pixel_size_?m"] = pixel_size
|
|
214
|
+
df_ecotaxa_export_table["sample_stationid"] = "-".join(
|
|
215
|
+
[xstats.attrs.get(el, "") for el in ["project_name", "station"]]
|
|
216
|
+
)
|
|
217
|
+
processing_datetime = pd.to_datetime(xstats.attrs["Modified"])
|
|
218
|
+
df_ecotaxa_export_table["process_time"] = processing_datetime.strftime(
|
|
219
|
+
"%H%M%S"
|
|
220
|
+
)
|
|
221
|
+
df_ecotaxa_export_table["process_date"] = processing_datetime.strftime(
|
|
222
|
+
"%Y%m%d"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
for col in ["object_major", "object_minor", "object_circ."]:
|
|
226
|
+
df_ecotaxa_export_table[col] *= pixel_size
|
|
227
|
+
|
|
228
|
+
# Insert type defintion as first row in exported table
|
|
229
|
+
df_ecotaxa_export_table = pd.concat(
|
|
230
|
+
[df_ecotaxa_types, df_ecotaxa_export_table],
|
|
231
|
+
axis=0,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Write to zip archive
|
|
235
|
+
buffer.seek(0)
|
|
236
|
+
buffer.truncate(0)
|
|
237
|
+
df_ecotaxa_export_table.to_csv(buffer, index=False, sep="\t")
|
|
238
|
+
buffer.seek(0)
|
|
239
|
+
zip.writestr("ecotaxa_particle_statistics.tsv", buffer.read())
|
pyopia-2.12.1/pyopia/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.12.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
|
|
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
|