PyOPIA 2.10.0__tar.gz → 2.11.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 (33) hide show
  1. {pyopia-2.10.0 → pyopia-2.11.0}/PKG-INFO +9 -3
  2. {pyopia-2.10.0 → pyopia-2.11.0}/README.md +7 -2
  3. pyopia-2.11.0/pyopia/__init__.py +1 -0
  4. pyopia-2.11.0/pyopia/auxillarydata.py +106 -0
  5. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/cf_metadata.json +8 -13
  6. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/classify.py +6 -0
  7. pyopia-2.11.0/pyopia/cli.py +571 -0
  8. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/instrument/silcam.py +14 -3
  9. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/io.py +88 -19
  10. pyopia-2.11.0/pyopia/metadata.py +46 -0
  11. pyopia-2.11.0/pyopia/tests/test_auxillarydata.py +102 -0
  12. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/tests/test_io.py +5 -1
  13. {pyopia-2.10.0 → pyopia-2.11.0}/pyproject.toml +1 -0
  14. pyopia-2.10.0/pyopia/__init__.py +0 -1
  15. pyopia-2.10.0/pyopia/cli.py +0 -309
  16. {pyopia-2.10.0 → pyopia-2.11.0}/.gitignore +0 -0
  17. {pyopia-2.10.0 → pyopia-2.11.0}/LICENSE +0 -0
  18. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/background.py +0 -0
  19. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/exampledata.py +0 -0
  20. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/instrument/__init__.py +0 -0
  21. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/instrument/common.py +0 -0
  22. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/instrument/holo.py +0 -0
  23. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/instrument/uvp.py +0 -0
  24. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/pipeline.py +0 -0
  25. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/plotting.py +0 -0
  26. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/process.py +0 -0
  27. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/simulator/__init__.py +0 -0
  28. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/simulator/silcam.py +0 -0
  29. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/statistics.py +0 -0
  30. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/tests/__init__.py +0 -0
  31. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/tests/test_classify.py +0 -0
  32. {pyopia-2.10.0 → pyopia-2.11.0}/pyopia/tests/test_notebooks.py +0 -0
  33. {pyopia-2.10.0 → pyopia-2.11.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.10.0
3
+ Version: 2.11.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
@@ -25,6 +25,7 @@ Requires-Dist: nbconvert<8,>=7.16.4
25
25
  Requires-Dist: numpy>=1.24.0
26
26
  Requires-Dist: pandas[computation]<3,>=2.1.1
27
27
  Requires-Dist: poetry-version-plugin<0.3,>=0.2.0
28
+ Requires-Dist: pydantic>=2.11.7
28
29
  Requires-Dist: pytest-error-for-skips<3,>=2.0.2
29
30
  Requires-Dist: pytest>=7.2.0
30
31
  Requires-Dist: readthedocs-sphinx-search<0.4,>=0.3.2
@@ -54,10 +55,15 @@ A Python Ocean Particle Image Analysis toolbox
54
55
  # Quick tryout of PyOPIA
55
56
 
56
57
  1) Install [uv](https://docs.astral.sh/uv/getting-started/installation)
57
- 2) Run PyOPIA classification tests on database particles
58
+ 2) Initialize PyOPIA project with a small example image dataset and run processing
58
59
  ```bash
59
- uv run --python 3.12 --with git+https://github.com/SINTEF/pyopia --with tensorflow==2.16.2 --with keras==3.5.0 python -m pyopia.tests.test_classify
60
+ uvx --python 3.12 --from pyopia[classification] pyopia --init-project pyopiatest --example-data
61
+ cd pyopiatest
62
+ uvx --python 3.12 --from pyopia[classification] pyopia process config.toml
60
63
  ```
64
+ 3) Inspect the processed particle statistics in the processed/ folder
65
+
66
+ See the documentation for more information on how to install and use PyOPIA.
61
67
 
62
68
  # Documentation:
63
69
 
@@ -6,10 +6,15 @@ A Python Ocean Particle Image Analysis toolbox
6
6
  # Quick tryout of PyOPIA
7
7
 
8
8
  1) Install [uv](https://docs.astral.sh/uv/getting-started/installation)
9
- 2) Run PyOPIA classification tests on database particles
9
+ 2) Initialize PyOPIA project with a small example image dataset and run processing
10
10
  ```bash
11
- uv run --python 3.12 --with git+https://github.com/SINTEF/pyopia --with tensorflow==2.16.2 --with keras==3.5.0 python -m pyopia.tests.test_classify
11
+ uvx --python 3.12 --from pyopia[classification] pyopia --init-project pyopiatest --example-data
12
+ cd pyopiatest
13
+ uvx --python 3.12 --from pyopia[classification] pyopia process config.toml
12
14
  ```
15
+ 3) Inspect the processed particle statistics in the processed/ folder
16
+
17
+ See the documentation for more information on how to install and use PyOPIA.
13
18
 
14
19
  # Documentation:
15
20
 
@@ -0,0 +1 @@
1
+ __version__ = "2.11.0"
@@ -0,0 +1,106 @@
1
+ import pandas as pd
2
+ import logging
3
+ import xarray as xr
4
+
5
+ logger = logging.getLogger()
6
+
7
+ AUXILLARY_DATA_FILE_TEMPLATE = """% COMMENT LINE: PLEASE UPDATE THIS FILE WITH PROJECT RELEVANT DATA. EACH COLUMN WILL BECOME A NETCDF VARIABLE.
8
+ % COMMENT LINE: ONE LINE PER MEASUREMENT, TIME IS INTERPOLATED TO IMAGE DATA TIMES IN PYOPIA. FOLLOWING LINES ARE UNITS, DESCRIPTION AND VARIABLE NAME.
9
+ ,metres,degC
10
+ Time of measurement,Depth at measurement location,Temperature at measurement location
11
+ time,depth,temperature
12
+ 2022-06-08T18:40:00.00000,0.0,5.0
13
+ 2022-06-08T18:41:00.00000,5.0,6.0
14
+ 2022-06-08T18:42:00.00000,10.0,7.0
15
+ 2022-06-08T18:43:00.00000,20.0,8.0
16
+ """ # noqa: E501
17
+
18
+
19
+ class AuxillaryData:
20
+ """
21
+ Handle auxillary data for PyOPIA particle statistics file.
22
+
23
+ Auxillary data variables may include (image) depth, longitude, latitude, etc.
24
+ This class parses a well-defined defined .csv input format, see example below.
25
+
26
+ Parameters
27
+ ----------
28
+ auxillary_data_path : str
29
+ Path to auxillary data files .csv creates by the enduser
30
+
31
+
32
+ Example of auxillary data file
33
+ ------------------------------
34
+ % COMMENT LINE:
35
+ % COMMENT LINE:
36
+ ,m,degC,psu
37
+ Time of measurement,Depth at measurement location,Temperature at measurement location
38
+ time,depth,temperature,salinity
39
+ 2025-03-19T16:59:29.950729,0.0,5.0,34
40
+ 2025-03-19T17:59:29.950729,0.0,5.0,34
41
+
42
+
43
+ Note
44
+ ----
45
+ Each column will become a netCDF variable
46
+ The two first lines are comments, and are ignored
47
+ Note that the third and fourth rows contain units and description for each variable
48
+ """
49
+
50
+ def __init__(self, auxillary_data_path=None):
51
+ self.auxillary_data_path = auxillary_data_path
52
+
53
+ # Create empty dataframe for cases where no file was specified, or an error occured reading it
54
+ self.auxillary_data = pd.DataFrame(index=pd.Index([], name="time")).to_xarray()
55
+ if auxillary_data_path is not None:
56
+ try:
57
+ self.auxillary_data = self.load_auxillary_data(auxillary_data_path)
58
+ except RuntimeError as e:
59
+ print(f"Failed to load auxillary data from file: {self.auxillary_data}")
60
+ logging.error(
61
+ f"Failed to load auxillary data from file: {self.auxillary_data}"
62
+ )
63
+ logging.error(e)
64
+
65
+ def load_auxillary_data(self, auxillary_data_path):
66
+ """Load and format uxillary data from .csv file"""
67
+
68
+ # Load in the auxillary data file
69
+ auxillary_data = pd.read_csv(auxillary_data_path, skiprows=4)
70
+
71
+ # Load units and description rows
72
+ units = pd.read_csv(auxillary_data_path, skiprows=2, nrows=0).columns
73
+ long_names = pd.read_csv(auxillary_data_path, skiprows=3, nrows=0).columns
74
+
75
+ # Set time as the index and make sure its type is datetime64[ns]
76
+ auxillary_data["time"] = auxillary_data["time"].astype("datetime64[ns]")
77
+ auxillary_data = auxillary_data.set_index("time")
78
+
79
+ # Transform into xarray, add units
80
+ auxillary_data = auxillary_data.to_xarray()
81
+ for i, col in enumerate(auxillary_data.data_vars): # Iterate over each column
82
+ auxillary_data[col].attrs["units"] = units[i + 1]
83
+ auxillary_data[col].attrs["long_name"] = long_names[i + 1]
84
+
85
+ logging.info(auxillary_data)
86
+
87
+ return auxillary_data
88
+
89
+ def add_auxillary_data_to_xstats(self, xstats):
90
+ """Add auxillary data to a PyOPIA xstats object"""
91
+ logging.info("Adding auxillary data to xstats and storing to new file")
92
+
93
+ # Add each auxillary data variable to xstats, interpolated to xstats times
94
+ for (
95
+ data_var
96
+ ) in self.auxillary_data.data_vars: # Iterate over each data variable
97
+ xstats[data_var] = xr.DataArray(
98
+ data=self.auxillary_data[data_var]
99
+ .astype(float)
100
+ .interp(time=xstats["timestamp"]),
101
+ dims=xstats.dims,
102
+ coords=xstats.coords,
103
+ attrs=self.auxillary_data[data_var].attrs,
104
+ )
105
+
106
+ return xstats
@@ -1,49 +1,49 @@
1
1
  {"major_axis_length": {
2
2
  "standard_name": "major_axis_length",
3
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",
4
+ "units": "Pixels",
5
5
  "calculation_method": "Computed using skimage.measure.regionprops (axis_major_length)",
6
6
  "pyopia_process_level": 1},
7
7
  "minor_axis_length": {
8
8
  "standard_name": "minor_axis_length",
9
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",
10
+ "units": "Pixels",
11
11
  "calculation_method": "Computed using skimage.measure.regionprops (axis_minor_length)",
12
12
  "pyopia_process_level": 1},
13
13
  "equivalent_diameter": {
14
14
  "standard_name": "equivalent_circular_diameter",
15
15
  "long_name": "Diameter of a circle with the same area as the particle",
16
- "units": "micrometer",
16
+ "units": "Pixels",
17
17
  "calculation_method": "Computed using skimage.measure.regionprops (equivalent_diameter)",
18
18
  "pyopia_process_level": 1},
19
19
  "minr": {
20
20
  "standard_name": "minimum_row_index",
21
21
  "long_name": "Minimum row index of the particle bounding box",
22
- "units": "pixels",
22
+ "units": "Pixels",
23
23
  "calculation_method": "Extracted from skimage.measure.regionprops (bbox[0])",
24
24
  "pyopia_process_level": 1},
25
25
  "maxr": {
26
26
  "standard_name": "maximum_row_index",
27
27
  "long_name": "Maximum row index of the particle bounding box",
28
- "units": "pixels",
28
+ "units": "Pixels",
29
29
  "calculation_method": "Extracted from skimage.measure.regionprops (bbox[2])",
30
30
  "pyopia_process_level": 1},
31
31
  "minc": {
32
32
  "standard_name": "minimum_column_index",
33
33
  "long_name": "Minimum column index of the particle bounding box",
34
- "units": "pixels",
34
+ "units": "Pixels",
35
35
  "calculation_method": "Extracted from skimage.measure.regionprops (bbox[1])",
36
36
  "pyopia_process_level": 1},
37
37
  "maxc": {
38
38
  "standard_name": "maximum_column_index",
39
39
  "long_name": "Maximum column index of the particle bounding box",
40
- "units": "pixels",
40
+ "units": "Pixels",
41
41
  "calculation_method": "Extracted from skimage.measure.regionprops (bbox[3])",
42
42
  "pyopia_process_level": 1},
43
43
  "saturation": {
44
44
  "standard_name": "image_saturation",
45
45
  "long_name": "Percentage saturation of the image",
46
- "units": "percent",
46
+ "units": "Percent",
47
47
  "calculation_method": "Computed as the percentage of the image covered by particles relative to the maximum acceptable coverage",
48
48
  "pyopia_process_level": 1},
49
49
  "index": {
@@ -58,11 +58,6 @@
58
58
  "units": "",
59
59
  "calculation_method": "Generated during particle export",
60
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
61
  "timestamp": {
67
62
  "standard_name": "timestamp",
68
63
  "long_name": "Timestamp of particle observation",
@@ -3,6 +3,7 @@ Module containing tools for classifying particle ROIs
3
3
  """
4
4
 
5
5
  import os
6
+ import hashlib
6
7
  import numpy as np
7
8
  import pandas as pd
8
9
  import logging
@@ -125,6 +126,11 @@ class Classify:
125
126
  path, filename = os.path.split(model_path)
126
127
  self.model = keras.models.load_model(model_path)
127
128
 
129
+ # Create a hash of the model weights file
130
+ with open(model_path, "rb") as f:
131
+ digest = hashlib.file_digest(f, "sha256")
132
+ self.model_hash = digest.hexdigest()
133
+
128
134
  # Try to create model output class name list from last model layer name
129
135
  class_labels = None
130
136
  try: