PyOPIA 2.9.2__tar.gz → 2.10.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.9.2 → pyopia-2.10.0}/PKG-INFO +6 -4
- pyopia-2.10.0/pyopia/__init__.py +1 -0
- pyopia-2.10.0/pyopia/cf_metadata.json +70 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/classify.py +60 -30
- pyopia-2.10.0/pyopia/exampledata.py +150 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/io.py +268 -139
- pyopia-2.10.0/pyopia/plotting.py +221 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/process.py +1 -1
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/statistics.py +4 -4
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/tests/test_classify.py +88 -63
- pyopia-2.10.0/pyopia/tests/test_io.py +64 -0
- pyopia-2.10.0/pyopia/tests/test_notebooks.py +26 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyproject.toml +5 -3
- pyopia-2.9.2/pyopia/__init__.py +0 -1
- pyopia-2.9.2/pyopia/exampledata.py +0 -143
- pyopia-2.9.2/pyopia/plotting.py +0 -51
- pyopia-2.9.2/pyopia/tests/test_notebooks.py +0 -22
- {pyopia-2.9.2 → pyopia-2.10.0}/.gitignore +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/LICENSE +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/README.md +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/background.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/cli.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/instrument/__init__.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/instrument/common.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/instrument/holo.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/instrument/silcam.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/instrument/uvp.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/pipeline.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/simulator/__init__.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/simulator/silcam.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/tests/__init__.py +0 -0
- {pyopia-2.9.2 → pyopia-2.10.0}/pyopia/tests/test_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyOPIA
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.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
|
|
@@ -8,6 +8,7 @@ Author-email: Emlyn Davies <emlyn.davies@sintef.no>, "Alex Nimmo Smith@plymouth.
|
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Keywords: Imaging,Measurement,Ocean,Particles,Size distribution
|
|
10
10
|
Requires-Python: ~=3.12
|
|
11
|
+
Requires-Dist: click<8.2.0
|
|
11
12
|
Requires-Dist: cmocean<4,>=3.0.3
|
|
12
13
|
Requires-Dist: dask>=2024.8.1
|
|
13
14
|
Requires-Dist: flake8<7,>=6.1.0
|
|
@@ -21,7 +22,7 @@ Requires-Dist: matplotlib>=3.7
|
|
|
21
22
|
Requires-Dist: myst-nb<0.18,>=0.17.2
|
|
22
23
|
Requires-Dist: nbclient==0.7
|
|
23
24
|
Requires-Dist: nbconvert<8,>=7.16.4
|
|
24
|
-
Requires-Dist: numpy
|
|
25
|
+
Requires-Dist: numpy>=1.24.0
|
|
25
26
|
Requires-Dist: pandas[computation]<3,>=2.1.1
|
|
26
27
|
Requires-Dist: poetry-version-plugin<0.3,>=0.2.0
|
|
27
28
|
Requires-Dist: pytest-error-for-skips<3,>=2.0.2
|
|
@@ -29,6 +30,7 @@ Requires-Dist: pytest>=7.2.0
|
|
|
29
30
|
Requires-Dist: readthedocs-sphinx-search<0.4,>=0.3.2
|
|
30
31
|
Requires-Dist: scikit-image<0.25,>=0.24.0
|
|
31
32
|
Requires-Dist: scipy<2,>=1.11.2
|
|
33
|
+
Requires-Dist: seaborn>=0.13.2
|
|
32
34
|
Requires-Dist: sphinx-copybutton<0.6,>=0.5.2
|
|
33
35
|
Requires-Dist: sphinx-rtd-theme>=0.5.0
|
|
34
36
|
Requires-Dist: sphinx-togglebutton<0.4,>=0.3.2
|
|
@@ -40,8 +42,8 @@ Requires-Dist: typer[all]<0.10,>=0.9.0
|
|
|
40
42
|
Requires-Dist: urllib3<2.0
|
|
41
43
|
Requires-Dist: xarray<2024,>=2023.12.0
|
|
42
44
|
Provides-Extra: classification
|
|
43
|
-
Requires-Dist: keras==3.
|
|
44
|
-
Requires-Dist: tensorflow
|
|
45
|
+
Requires-Dist: keras==3.9.1; extra == 'classification'
|
|
46
|
+
Requires-Dist: tensorflow>=2.19.0; extra == 'classification'
|
|
45
47
|
Description-Content-Type: text/markdown
|
|
46
48
|
|
|
47
49
|
PyOPIA
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.10.0"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{"major_axis_length": {
|
|
2
|
+
"standard_name": "major_axis_length",
|
|
3
|
+
"long_name": "The length of the major axis of the ellipse that has the same normalized second central moments as the region",
|
|
4
|
+
"units": "micrometer",
|
|
5
|
+
"calculation_method": "Computed using skimage.measure.regionprops (axis_major_length)",
|
|
6
|
+
"pyopia_process_level": 1},
|
|
7
|
+
"minor_axis_length": {
|
|
8
|
+
"standard_name": "minor_axis_length",
|
|
9
|
+
"long_name": "The length of the minor axis of the ellipse that has the same normalized second central moments as the region",
|
|
10
|
+
"units": "micrometer",
|
|
11
|
+
"calculation_method": "Computed using skimage.measure.regionprops (axis_minor_length)",
|
|
12
|
+
"pyopia_process_level": 1},
|
|
13
|
+
"equivalent_diameter": {
|
|
14
|
+
"standard_name": "equivalent_circular_diameter",
|
|
15
|
+
"long_name": "Diameter of a circle with the same area as the particle",
|
|
16
|
+
"units": "micrometer",
|
|
17
|
+
"calculation_method": "Computed using skimage.measure.regionprops (equivalent_diameter)",
|
|
18
|
+
"pyopia_process_level": 1},
|
|
19
|
+
"minr": {
|
|
20
|
+
"standard_name": "minimum_row_index",
|
|
21
|
+
"long_name": "Minimum row index of the particle bounding box",
|
|
22
|
+
"units": "pixels",
|
|
23
|
+
"calculation_method": "Extracted from skimage.measure.regionprops (bbox[0])",
|
|
24
|
+
"pyopia_process_level": 1},
|
|
25
|
+
"maxr": {
|
|
26
|
+
"standard_name": "maximum_row_index",
|
|
27
|
+
"long_name": "Maximum row index of the particle bounding box",
|
|
28
|
+
"units": "pixels",
|
|
29
|
+
"calculation_method": "Extracted from skimage.measure.regionprops (bbox[2])",
|
|
30
|
+
"pyopia_process_level": 1},
|
|
31
|
+
"minc": {
|
|
32
|
+
"standard_name": "minimum_column_index",
|
|
33
|
+
"long_name": "Minimum column index of the particle bounding box",
|
|
34
|
+
"units": "pixels",
|
|
35
|
+
"calculation_method": "Extracted from skimage.measure.regionprops (bbox[1])",
|
|
36
|
+
"pyopia_process_level": 1},
|
|
37
|
+
"maxc": {
|
|
38
|
+
"standard_name": "maximum_column_index",
|
|
39
|
+
"long_name": "Maximum column index of the particle bounding box",
|
|
40
|
+
"units": "pixels",
|
|
41
|
+
"calculation_method": "Extracted from skimage.measure.regionprops (bbox[3])",
|
|
42
|
+
"pyopia_process_level": 1},
|
|
43
|
+
"saturation": {
|
|
44
|
+
"standard_name": "image_saturation",
|
|
45
|
+
"long_name": "Percentage saturation of the image",
|
|
46
|
+
"units": "percent",
|
|
47
|
+
"calculation_method": "Computed as the percentage of the image covered by particles relative to the maximum acceptable coverage",
|
|
48
|
+
"pyopia_process_level": 1},
|
|
49
|
+
"index": {
|
|
50
|
+
"standard_name": "index",
|
|
51
|
+
"long_name": "Index of the particle in the dataset",
|
|
52
|
+
"units": "",
|
|
53
|
+
"calculation_method": "Sequential numbering of particles in the dataset",
|
|
54
|
+
"pyopia_process_level": 1},
|
|
55
|
+
"export_name": {
|
|
56
|
+
"standard_name": "export_name",
|
|
57
|
+
"long_name": "Name of the exported particle ROI file",
|
|
58
|
+
"units": "",
|
|
59
|
+
"calculation_method": "Generated during particle export",
|
|
60
|
+
"pyopia_process_level": 1},
|
|
61
|
+
"time": {
|
|
62
|
+
"standard_name": "time",
|
|
63
|
+
"long_name": "Time of particle observation",
|
|
64
|
+
"calculation_method": "Extracted from the timestamp of the observation",
|
|
65
|
+
"pyopia_process_level": 0},
|
|
66
|
+
"timestamp": {
|
|
67
|
+
"standard_name": "timestamp",
|
|
68
|
+
"long_name": "Timestamp of particle observation",
|
|
69
|
+
"calculation_method": "Recorded during particle observation",
|
|
70
|
+
"pyopia_process_level": 0}}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
2
|
Module containing tools for classifying particle ROIs
|
|
3
|
-
|
|
3
|
+
"""
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
8
8
|
import logging
|
|
9
|
+
from skimage.exposure import rescale_intensity
|
|
10
|
+
|
|
9
11
|
logger = logging.getLogger()
|
|
10
12
|
|
|
11
13
|
# import tensorflow here. It must be imported on the processor where it will be used!
|
|
@@ -15,16 +17,16 @@ try:
|
|
|
15
17
|
from tensorflow import keras
|
|
16
18
|
import tensorflow as tf
|
|
17
19
|
except ImportError:
|
|
18
|
-
info_str =
|
|
19
|
-
info_str +=
|
|
20
|
-
info_str +=
|
|
21
|
-
info_str +=
|
|
22
|
-
info_str +=
|
|
20
|
+
info_str = "ERROR: Could not import Keras. Classify will not work"
|
|
21
|
+
info_str += " until you install tensorflow.\n"
|
|
22
|
+
info_str += "Use: pip install pyopia[classification]\n"
|
|
23
|
+
info_str += " or: pip install pyopia[classification-arm64]"
|
|
24
|
+
info_str += " for tensorflow-macos (silicon chips)"
|
|
23
25
|
raise ImportError(info_str)
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
class Classify
|
|
27
|
-
|
|
28
|
+
class Classify:
|
|
29
|
+
"""
|
|
28
30
|
A classifier class for PyOPIA workflow.
|
|
29
31
|
This is intended as a parent class that can be used as a template for flexible classification methods
|
|
30
32
|
|
|
@@ -32,6 +34,10 @@ class Classify():
|
|
|
32
34
|
----------
|
|
33
35
|
model_path : str
|
|
34
36
|
path to particle-classifier e.g. '/testdata/model_name/particle_classifier.keras'
|
|
37
|
+
normalize_intensity : bool
|
|
38
|
+
Scale input image intensity to [0-1] range before classification
|
|
39
|
+
correct_whitebalance : bool
|
|
40
|
+
Perform whitebalance correction before classification
|
|
35
41
|
|
|
36
42
|
Example
|
|
37
43
|
-------
|
|
@@ -71,23 +77,36 @@ class Classify():
|
|
|
71
77
|
import pyopia.exampledata
|
|
72
78
|
model_path = exampledata.get_example_model()
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
model_path=None,
|
|
85
|
+
normalize_intensity=True,
|
|
86
|
+
correct_whitebalance=False,
|
|
87
|
+
):
|
|
76
88
|
self.model_path = model_path
|
|
77
89
|
self.load_model()
|
|
78
90
|
|
|
79
91
|
# Get config for image resizing from the model
|
|
80
|
-
_, self.img_height, self.img_width, _ = self.model.get_config()[
|
|
81
|
-
|
|
92
|
+
_, self.img_height, self.img_width, _ = self.model.get_config()["layers"][0][
|
|
93
|
+
"config"
|
|
94
|
+
]["batch_shape"]
|
|
95
|
+
self.pad_to_aspect_ratio = getattr(
|
|
96
|
+
self.model.layers[0], "pad_to_aspect_ratio", False
|
|
97
|
+
)
|
|
82
98
|
|
|
83
99
|
# Enable this to perform whitebalance correction in the preprocessing step
|
|
84
|
-
self.correct_whitebalance =
|
|
100
|
+
self.correct_whitebalance = correct_whitebalance
|
|
101
|
+
|
|
102
|
+
# Enable this to rescale intensity in the preprocessing step
|
|
103
|
+
self.normalize_intensity = normalize_intensity
|
|
85
104
|
|
|
86
105
|
def __call__(self):
|
|
87
106
|
return self
|
|
88
107
|
|
|
89
108
|
def load_model(self):
|
|
90
|
-
|
|
109
|
+
"""
|
|
91
110
|
Load a trained Keras model into the Classify class.
|
|
92
111
|
|
|
93
112
|
Parameters
|
|
@@ -96,10 +115,10 @@ class Classify():
|
|
|
96
115
|
loaded Keras model
|
|
97
116
|
class_names: list
|
|
98
117
|
names for the model output classes
|
|
99
|
-
|
|
118
|
+
"""
|
|
100
119
|
model_path = self.model_path
|
|
101
120
|
|
|
102
|
-
os.environ[
|
|
121
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
|
|
103
122
|
keras.backend.clear_session()
|
|
104
123
|
|
|
105
124
|
# Instantiate Keras model from file
|
|
@@ -109,21 +128,23 @@ class Classify():
|
|
|
109
128
|
# Try to create model output class name list from last model layer name
|
|
110
129
|
class_labels = None
|
|
111
130
|
try:
|
|
112
|
-
class_labels = self.model.layers[-1].name.split(
|
|
131
|
+
class_labels = self.model.layers[-1].name.split(".")
|
|
113
132
|
except: # noqa E722
|
|
114
|
-
logger.info(
|
|
133
|
+
logger.info(
|
|
134
|
+
"Could not get class names from model layer name, reverting to old method with header file."
|
|
135
|
+
)
|
|
115
136
|
|
|
116
137
|
# If we could not create correct class names above, revert to old header file method
|
|
117
138
|
expected_class_number = self.model.layers[-1].output.shape[1]
|
|
118
139
|
if class_labels is None or len(class_labels) != expected_class_number:
|
|
119
|
-
header = pd.read_csv(os.path.join(path,
|
|
140
|
+
header = pd.read_csv(os.path.join(path, "header.tfl.txt"))
|
|
120
141
|
class_labels = header.columns
|
|
121
142
|
|
|
122
143
|
self.class_labels = class_labels
|
|
123
144
|
logger.info(self.class_labels)
|
|
124
145
|
|
|
125
146
|
def preprocessing(self, img_input):
|
|
126
|
-
|
|
147
|
+
"""
|
|
127
148
|
Preprocess ROI ready for prediction. example here based on the pysilcam network setup
|
|
128
149
|
|
|
129
150
|
Parameters
|
|
@@ -135,7 +156,7 @@ class Classify():
|
|
|
135
156
|
-------
|
|
136
157
|
img_preprocessed : float
|
|
137
158
|
A particle ROI with range 0.-255., corrected and preprocessed, ready for prediction
|
|
138
|
-
|
|
159
|
+
"""
|
|
139
160
|
|
|
140
161
|
whitebalanced = img_input.astype(np.float64)
|
|
141
162
|
|
|
@@ -143,10 +164,16 @@ class Classify():
|
|
|
143
164
|
if self.correct_whitebalance:
|
|
144
165
|
p = 99
|
|
145
166
|
for c in range(3):
|
|
146
|
-
whitebalanced[:, :, c] += (p/100) - np.percentile(
|
|
167
|
+
whitebalanced[:, :, c] += (p / 100) - np.percentile(
|
|
168
|
+
whitebalanced[:, :, c], p
|
|
169
|
+
)
|
|
147
170
|
whitebalanced[whitebalanced > 1] = 1
|
|
148
171
|
whitebalanced[whitebalanced < 0] = 0
|
|
149
172
|
|
|
173
|
+
# Rescale intensity to 0-1 range
|
|
174
|
+
if self.normalize_intensity:
|
|
175
|
+
whitebalanced = rescale_intensity(whitebalanced)
|
|
176
|
+
|
|
150
177
|
# convert back to 0-255 scaling (because of this layer in the network:
|
|
151
178
|
# layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)))
|
|
152
179
|
# This is useful because it allows training to use tf.keras.utils.image_dataset_from_directory,
|
|
@@ -154,9 +181,12 @@ class Classify():
|
|
|
154
181
|
img = keras.utils.img_to_array(whitebalanced * 255)
|
|
155
182
|
|
|
156
183
|
# resize to match the dimentions expected by the network
|
|
157
|
-
img = tf.image.resize(
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
img = tf.image.resize(
|
|
185
|
+
img,
|
|
186
|
+
[self.img_height, self.img_width],
|
|
187
|
+
method=tf.image.ResizeMethod.BILINEAR,
|
|
188
|
+
preserve_aspect_ratio=self.pad_to_aspect_ratio,
|
|
189
|
+
)
|
|
160
190
|
|
|
161
191
|
img_array = tf.keras.utils.img_to_array(img)
|
|
162
192
|
img_preprocessed = tf.expand_dims(img_array, 0) # Create a batch
|
|
@@ -164,7 +194,7 @@ class Classify():
|
|
|
164
194
|
|
|
165
195
|
@tf.function
|
|
166
196
|
def predict(self, img_preprocessed):
|
|
167
|
-
|
|
197
|
+
"""
|
|
168
198
|
Use tensorflow model to classify particles. example here based on the pysilcam network setup.
|
|
169
199
|
|
|
170
200
|
Parameters
|
|
@@ -177,14 +207,14 @@ class Classify():
|
|
|
177
207
|
-------
|
|
178
208
|
prediction : array
|
|
179
209
|
The probability of the roi belonging to each class
|
|
180
|
-
|
|
210
|
+
"""
|
|
181
211
|
|
|
182
212
|
prediction = self.model(img_preprocessed, training=False)
|
|
183
213
|
prediction = tf.nn.softmax(prediction[0])
|
|
184
214
|
return prediction
|
|
185
215
|
|
|
186
216
|
def proc_predict(self, img_input):
|
|
187
|
-
|
|
217
|
+
"""
|
|
188
218
|
Run pre-processing (:meth:`Classify.preprocessing`) and prediction (:meth:`Classify.predict`)
|
|
189
219
|
using tensorflow model to classify particles. example here based on the pysilcam network setup.
|
|
190
220
|
|
|
@@ -197,7 +227,7 @@ class Classify():
|
|
|
197
227
|
-------
|
|
198
228
|
prediction : array
|
|
199
229
|
The probability of the roi belonging to each class
|
|
200
|
-
|
|
230
|
+
"""
|
|
201
231
|
img_preprocessed = self.preprocessing(img_input)
|
|
202
232
|
prediction = self.predict(img_preprocessed)
|
|
203
233
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import urllib.request
|
|
2
|
+
import zipfile
|
|
3
|
+
import os
|
|
4
|
+
import gdown
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_classifier_database_from_pysilcam_blob(download_directory="./"):
|
|
13
|
+
"""Downloads a specified filename from the pysilcam.blob into the working dir. if it doesn't already exist
|
|
14
|
+
|
|
15
|
+
only works for known filenames that are on this blob
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
filename : string
|
|
20
|
+
known filename on the blob
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
if os.path.exists(os.path.join(download_directory)):
|
|
24
|
+
logger.info(download_directory, "already exists. Returning nothing")
|
|
25
|
+
return download_directory
|
|
26
|
+
os.makedirs(download_directory, exist_ok=False)
|
|
27
|
+
url = "https://pysilcam.blob.core.windows.net/test-data/silcam_database.zip"
|
|
28
|
+
logger.info("Downloading....")
|
|
29
|
+
urllib.request.urlretrieve(url, download_directory + "/silcam_database.zip")
|
|
30
|
+
logger.info("Unzipping....")
|
|
31
|
+
with zipfile.ZipFile(
|
|
32
|
+
os.path.join(download_directory, "silcam_database.zip"), "r"
|
|
33
|
+
) as zipit:
|
|
34
|
+
zipit.extractall(os.path.join(download_directory, "../"))
|
|
35
|
+
logger.info("Removing zip file")
|
|
36
|
+
os.remove(os.path.join(download_directory, "silcam_database.zip"))
|
|
37
|
+
logger.info("Done.")
|
|
38
|
+
return download_directory
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_file_from_pysilcam_blob(filename, download_directory="./"):
|
|
42
|
+
"""Downloads a specified filename from the pysilcam.blob into the working dir. if it doesn't already exist
|
|
43
|
+
|
|
44
|
+
only works for known filenames that are on this blob
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
filename : string
|
|
49
|
+
known filename on the blob
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
if os.path.exists(os.path.join(download_directory, filename)):
|
|
53
|
+
return filename
|
|
54
|
+
url = "https://pysilcam.blob.core.windows.net/test-data/" + filename
|
|
55
|
+
urllib.request.urlretrieve(url, os.path.join(download_directory, filename))
|
|
56
|
+
return download_directory
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_example_silc_image(download_directory="./"):
|
|
60
|
+
"""calls `get_file_from_pysilcam_blob` for a silcam iamge
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
string
|
|
65
|
+
filename
|
|
66
|
+
"""
|
|
67
|
+
filename = "D20181101T142731.838206.silc"
|
|
68
|
+
if os.path.isfile(filename):
|
|
69
|
+
logger.info("Example image already exists. Skipping download.")
|
|
70
|
+
return filename
|
|
71
|
+
get_file_from_pysilcam_blob(filename, download_directory)
|
|
72
|
+
return filename
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_example_model(download_directory="./"):
|
|
76
|
+
"""Download PyOPIA default CNN model classifier
|
|
77
|
+
|
|
78
|
+
Download from the pysilcam blob storage into the working dir.
|
|
79
|
+
If the file exists, skip the download.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
string
|
|
84
|
+
model_filename
|
|
85
|
+
"""
|
|
86
|
+
model_filename = "pyopia-default-classifier-20250409.keras"
|
|
87
|
+
model_path = Path(download_directory, model_filename)
|
|
88
|
+
model_url = (
|
|
89
|
+
f"https://pysilcam.blob.core.windows.net/test-data/{str(model_filename)}"
|
|
90
|
+
)
|
|
91
|
+
if not model_path.exists():
|
|
92
|
+
logger.info("Downloading example model...")
|
|
93
|
+
urllib.request.urlretrieve(model_url, model_path)
|
|
94
|
+
return str(model_path)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_example_hologram_and_background(download_directory="./"):
|
|
98
|
+
"""calls `get_file_from_pysilcam_blob` for a raw hologram, and its associated background image.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
string
|
|
103
|
+
holo_filename
|
|
104
|
+
|
|
105
|
+
string
|
|
106
|
+
holo_background_filename
|
|
107
|
+
"""
|
|
108
|
+
holo_filename = "001-2082.pgm"
|
|
109
|
+
holo_background_filename = "imbg-" + holo_filename
|
|
110
|
+
get_file_from_pysilcam_blob(holo_filename, download_directory)
|
|
111
|
+
get_file_from_pysilcam_blob(holo_background_filename, download_directory)
|
|
112
|
+
|
|
113
|
+
holo_filename = os.path.join(download_directory, holo_filename)
|
|
114
|
+
holo_background_filename = os.path.join(
|
|
115
|
+
download_directory, holo_background_filename
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return holo_filename, holo_background_filename
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_folder_from_holo_repository(foldername="holo_test_data_01", existsok=False):
|
|
122
|
+
"""Downloads a specified folder from the holo testing repository into the working dir. if it doesn't already exist
|
|
123
|
+
|
|
124
|
+
only works for known folders that are on the GoogleDrive repository
|
|
125
|
+
by default will download a known-good folder. Additional elif statements can be added to implement additional folders.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
foldername : string
|
|
130
|
+
known filename on the blob
|
|
131
|
+
existsok : (bool, optional)
|
|
132
|
+
if True, then don't download if the specified folder already exists, defaults to False
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if foldername == "holo_test_data_01":
|
|
136
|
+
url = "https://drive.google.com/drive/folders/1yNatOaKdWwYQp-5WVEDItoibr-k0lGsP?usp=share_link"
|
|
137
|
+
|
|
138
|
+
elif foldername == "holo_test_data_02":
|
|
139
|
+
url = "https://drive.google.com/drive/folders/1E5iNSyfeKcVMLVe4PNEwF2Q2mo3WVjF5?usp=share_link"
|
|
140
|
+
|
|
141
|
+
else:
|
|
142
|
+
foldername == "holo_test_data_01"
|
|
143
|
+
url = "https://drive.google.com/drive/folders/1yNatOaKdWwYQp-5WVEDItoibr-k0lGsP?usp=share_link"
|
|
144
|
+
|
|
145
|
+
if os.path.exists(foldername) and existsok:
|
|
146
|
+
logger.info(foldername + " already exists. Skipping download.")
|
|
147
|
+
return foldername
|
|
148
|
+
|
|
149
|
+
gdown.download_folder(url, quiet=True, use_cookies=False)
|
|
150
|
+
return foldername
|