hydroanomaly 0.6.0__py3-none-any.whl → 0.7.1__py3-none-any.whl

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.
hydroanomaly/__init__.py CHANGED
@@ -2,15 +2,15 @@
2
2
  HydroAnomaly: Simple Water Data Analysis Package
3
3
 
4
4
  A simple Python package with just 3 modules:
5
- 1. USGS turbidity data retrieval
5
+ 1. USGS turbidity data retrieval (returns data and site coordinates)
6
6
  2. Sentinel satellite bands retrieval
7
7
  3. Time series visualization
8
8
 
9
9
  That's it - nothing else!
10
10
  """
11
11
 
12
- __version__ = "0.6.0"
13
- __author__ = "HydroAnomaly Team"
12
+ __version__ = "0.7.1"
13
+ __author__ = "Ehsan Kahrizi (Ehsan.kahrizi@usu.edu)"
14
14
 
15
15
  # Import the 3 simple modules
16
16
  from .usgs_turbidity import get_turbidity, get_usgs_turbidity
@@ -38,9 +38,9 @@ __all__ = [
38
38
  'visualize'
39
39
  ]
40
40
 
41
- print(f"🌊 HydroAnomaly v{__version__} - Simple Water Data Package")
42
- print("📚 Available functions:")
43
- print(" • get_turbidity() - Get USGS turbidity data")
41
+ print(f"HydroAnomaly v{__version__} - Simple Water Data Package")
42
+ print("Available functions:")
43
+ print(" • get_turbidity() - Get USGS turbidity data and site coordinates")
44
44
  print(" • get_sentinel_bands() - Get satellite data")
45
45
  print(" • plot_timeseries() - Visualize data")
46
- print("💡 Try: help(hydroanomaly.get_turbidity) for examples")
46
+ print("Try: help(hydroanomaly.get_turbidity) for examples")
@@ -1,8 +1,8 @@
1
1
  """
2
- Simple Sentinel Satellite Data Retrieval
2
+ Sentinel-2 Satellite Data Retrieval using Google Earth Engine (GEE)
3
3
 
4
- This module provides one simple function to get Sentinel satellite bands.
5
- That's it - nothing else!
4
+ This module provides a function to retrieve Sentinel-2 satellite band data
5
+ for a specified location and time period, with masking and cloud filtering.
6
6
  """
7
7
 
8
8
  import pandas as pd
@@ -11,147 +11,94 @@ from datetime import datetime, timedelta
11
11
  import requests
12
12
  import warnings
13
13
 
14
-
15
- def get_sentinel_bands(latitude: float, longitude: float, start_date: str, end_date: str, bands: list = None) -> pd.DataFrame:
14
+ def get_sentinel_bands_gee(
15
+ latitude: float,
16
+ longitude: float,
17
+ start_date: str,
18
+ end_date: str,
19
+ bands: list = None,
20
+ buffer_meters: int = 20,
21
+ cloudy_pixel_percentage: int = 20,
22
+ masks_to_apply: list = None
23
+ ) -> pd.DataFrame:
16
24
  """
17
- Get Sentinel satellite band data for a location.
18
-
25
+ Retrieve Sentinel-2 bands from Google Earth Engine, applying custom masking.
26
+
19
27
  Args:
20
- latitude (float): Latitude coordinate (e.g., 30.2672)
21
- longitude (float): Longitude coordinate (e.g., -97.7431)
22
- start_date (str): Start date as "YYYY-MM-DD"
23
- end_date (str): End date as "YYYY-MM-DD"
24
- bands (list): List of bands to retrieve (default: ['B2', 'B3', 'B4', 'B8'])
25
-
28
+ latitude (float): Latitude of center point.
29
+ longitude (float): Longitude of center point.
30
+ start_date (str): Start date as "YYYY-MM-DD".
31
+ end_date (str): End date as "YYYY-MM-DD".
32
+ bands (list): List of bands to retrieve (default is common Sentinel-2 bands).
33
+ buffer_meters (int): Buffer size around the point, in meters.
34
+ cloudy_pixel_percentage (int): Maximum allowed cloud percentage for each image.
35
+ masks_to_apply (list): Masking strategies (e.g., ["water", "no_cloud_shadow", ...]).
36
+
26
37
  Returns:
27
- pd.DataFrame: Time series data with datetime index and band values
28
-
38
+ pd.DataFrame: DataFrame with band reflectance values per date.
39
+
29
40
  Example:
30
- >>> data = get_sentinel_bands(30.2672, -97.7431, "2023-01-01", "2023-12-31")
31
- >>> print(f"Got {len(data)} satellite observations")
41
+ >>> import ee
42
+ >>> ee.Authenticate()
43
+ >>> ee.Initialize()
44
+ >>> df = get_sentinel_bands_gee(29.77, -95.06, "2021-01-01", "2021-12-31")
45
+ >>> print(df.head())
32
46
  """
33
-
34
47
  if bands is None:
35
- bands = ['B2', 'B3', 'B4', 'B8'] # Blue, Green, Red, NIR
36
-
37
- print(f"🛰️ Getting Sentinel data for location ({latitude}, {longitude})")
38
- print(f"📅 Date range: {start_date} to {end_date}")
39
- print(f"📡 Bands: {', '.join(bands)}")
40
-
41
- try:
42
- # Try to get real Sentinel data (this would normally use Google Earth Engine or similar)
43
- # For now, we'll create realistic synthetic data
44
- print("⚠️ Creating synthetic Sentinel data (real API integration would go here)")
45
- return _create_synthetic_sentinel(latitude, longitude, start_date, end_date, bands)
46
-
47
- except Exception as e:
48
- print(f"❌ Error: {e}")
49
- print("🔄 Creating synthetic Sentinel data...")
50
- return _create_synthetic_sentinel(latitude, longitude, start_date, end_date, bands)
48
+ bands = ['B2','B3','B4','B8','SCL']
49
+ if masks_to_apply is None:
50
+ masks_to_apply = ["water", "no_cloud_shadow", "no_clouds", "no_snow_ice", "no_saturated"]
51
51
 
52
+ point = ee.Geometry.Point([longitude, latitude])
53
+ buffered_point = point.buffer(buffer_meters)
52
54
 
53
- def _create_synthetic_sentinel(lat: float, lon: float, start_date: str, end_date: str, bands: list) -> pd.DataFrame:
54
- """Create realistic synthetic Sentinel satellite data."""
55
-
56
- # Generate dates (Sentinel-2 has ~5-day revisit time)
57
- start_dt = datetime.strptime(start_date, "%Y-%m-%d")
58
- end_dt = datetime.strptime(end_date, "%Y-%m-%d")
59
-
60
- # Create observation dates (every 5-10 days, accounting for clouds)
61
- observation_dates = []
62
- current_date = start_dt
63
-
64
- while current_date <= end_dt:
65
- # Add some randomness to simulate real satellite schedule
66
- if np.random.random() > 0.3: # 70% chance of successful observation
67
- observation_dates.append(current_date)
68
-
69
- # Next observation in 5-10 days
70
- days_to_add = np.random.randint(5, 11)
71
- current_date += timedelta(days=days_to_add)
72
-
73
- if len(observation_dates) == 0:
74
- print("⚠️ No observation dates generated")
75
- return pd.DataFrame()
76
-
77
- # Generate realistic band values
78
- data_dict = {'datetime': observation_dates}
79
-
80
- for band in bands:
81
- if band == 'B2': # Blue (450-520nm)
82
- base_value = 1200
83
- variation = 300
84
- elif band == 'B3': # Green (520-600nm)
85
- base_value = 1400
86
- variation = 350
87
- elif band == 'B4': # Red (630-690nm)
88
- base_value = 1300
89
- variation = 400
90
- elif band == 'B8': # NIR (760-900nm)
91
- base_value = 2800
92
- variation = 800
93
- else: # Generic band
94
- base_value = 1500
95
- variation = 400
96
-
97
- # Generate values with seasonal and random variation
98
- band_values = []
99
- for i, date in enumerate(observation_dates):
100
- # Seasonal variation (higher vegetation in summer)
101
- day_of_year = date.timetuple().tm_yday
102
- seasonal_factor = np.sin(2 * np.pi * day_of_year / 365) * 0.2
103
-
104
- # Random variation
105
- noise = np.random.normal(0, variation * 0.3)
106
-
107
- # Calculate final value
108
- value = base_value * (1 + seasonal_factor) + noise
109
- value = max(0, int(value)) # Ensure positive integer values
110
-
111
- band_values.append(value)
112
-
113
- data_dict[band] = band_values
114
-
115
- # Create DataFrame
116
- result = pd.DataFrame(data_dict)
117
- result['datetime'] = pd.to_datetime(result['datetime'])
118
- result = result.set_index('datetime')
119
-
120
- print(f"📊 Created {len(result)} synthetic Sentinel observations")
121
- print(f"📡 Bands included: {', '.join(bands)}")
122
-
123
- return result
55
+ s2 = (ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
56
+ .filterBounds(buffered_point)
57
+ .filterDate(start_date, end_date)
58
+ .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', cloudy_pixel_percentage))
59
+ .select(bands))
124
60
 
61
+ def dynamic_scl_mask(image):
62
+ scl = image.select('SCL')
63
+ mask = ee.Image.constant(1)
64
+ if "water" in masks_to_apply:
65
+ mask = mask.And(scl.eq(6))
66
+ if "no_cloud_shadow" in masks_to_apply:
67
+ mask = mask.And(scl.neq(3))
68
+ if "no_clouds" in masks_to_apply:
69
+ cloud_mask = scl.neq(8).And(scl.neq(9)).And(scl.neq(10))
70
+ mask = mask.And(cloud_mask)
71
+ if "no_snow_ice" in masks_to_apply:
72
+ mask = mask.And(scl.neq(11))
73
+ if "no_saturated" in masks_to_apply:
74
+ mask = mask.And(scl.neq(1))
75
+ return image.updateMask(mask)
125
76
 
126
- def calculate_ndvi(sentinel_data: pd.DataFrame) -> pd.DataFrame:
127
- """
128
- Calculate NDVI from Sentinel data.
129
-
130
- Args:
131
- sentinel_data (pd.DataFrame): DataFrame with B4 (Red) and B8 (NIR) bands
132
-
133
- Returns:
134
- pd.DataFrame: DataFrame with NDVI values
135
- """
136
-
137
- if 'B4' not in sentinel_data.columns or 'B8' not in sentinel_data.columns:
138
- print("❌ Need B4 (Red) and B8 (NIR) bands to calculate NDVI")
139
- return pd.DataFrame()
140
-
141
- # Calculate NDVI = (NIR - Red) / (NIR + Red)
142
- red = sentinel_data['B4']
143
- nir = sentinel_data['B8']
144
-
145
- ndvi = (nir - red) / (nir + red)
146
-
147
- result = pd.DataFrame({'NDVI': ndvi}, index=sentinel_data.index)
148
-
149
- print(f"📊 Calculated NDVI for {len(result)} observations")
150
- print(f"🌱 NDVI range: {ndvi.min():.3f} to {ndvi.max():.3f}")
151
-
152
- return result
77
+ s2_masked = s2.map(dynamic_scl_mask)
78
+
79
+ def extract_features(image):
80
+ date = image.date().format('YYYY-MM-dd HH:mm:ss')
81
+ values = image.reduceRegion(
82
+ reducer=ee.Reducer.mean(),
83
+ geometry=buffered_point,
84
+ scale=20,
85
+ maxPixels=1e8
86
+ )
87
+ return ee.Feature(None, values.set('date', date))
88
+
89
+ features = s2_masked.map(extract_features)
90
+ fc = ee.FeatureCollection(features).filter(ee.Filter.notNull(['B2']))
153
91
 
92
+ data = fc.getInfo()
93
+ rows = [f['properties'] for f in data['features']]
94
+ df = pd.DataFrame(rows)
95
+ if not df.empty:
96
+ df['date'] = pd.to_datetime(df['date'])
97
+ df = df.sort_values('date')
98
+ df = df.set_index('date')
99
+ return df
154
100
 
155
- # Simple aliases
156
- get_satellite_data = get_sentinel_bands
157
- get_sentinel = get_sentinel_bands
101
+ # Aliases for user convenience
102
+ get_sentinel_bands = get_sentinel_bands_gee
103
+ get_satellite_data = get_sentinel_bands_gee
104
+ get_sentinel = get_sentinel_bands_gee
@@ -11,7 +11,7 @@ from io import StringIO
11
11
  from datetime import datetime
12
12
  import numpy as np
13
13
 
14
-
14
+ # Function for retrive data ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
15
15
  def get_turbidity(site_number: str, start_date: str, end_date: str) -> pd.DataFrame:
16
16
  """
17
17
  Get turbidity data from a USGS station.
@@ -22,50 +22,75 @@ def get_turbidity(site_number: str, start_date: str, end_date: str) -> pd.DataFr
22
22
  end_date (str): End date as "YYYY-MM-DD"
23
23
 
24
24
  Returns:
25
- pd.DataFrame: Time series data with datetime index and turbidity values
25
+ tuple: (pd.DataFrame, (latitude, longitude)) or (empty DataFrame, (None, None)) if not found.
26
+ * Note: pd.DataFrame: Time series data with datetime index and turbidity values
26
27
 
27
28
  Example:
28
29
  >>> data = get_turbidity("294643095035200", "2023-01-01", "2023-12-31")
29
30
  >>> print(f"Got {len(data)} turbidity measurements")
30
31
  """
32
+
33
+ # --- Validate inputs ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
34
+ print(f"Getting turbidity data for site {site_number}")
35
+ print(f"Date range: {start_date} to {end_date}")
36
+
37
+ # --- Retrieve site metadata (lat/lon) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
38
+ site_url = (
39
+ f"https://waterservices.usgs.gov/nwis/site/"
40
+ f"?sites={site_number}"
41
+ f"&format=rdb")
42
+ try:
43
+ site_resp = requests.get(site_url, timeout=15)
44
+ if site_resp.status_code != 200:
45
+ print(f"Could not get site metadata: {site_resp.status_code}")
46
+ lat, lon = None, None
47
+ else:
48
+ df_meta = pd.read_csv(StringIO(site_resp.text), sep="\t", comment="#")
49
+ df_meta = df_meta.dropna(axis=1, how="all")
50
+ lat, lon = None, None
51
+ if not df_meta.empty:
52
+ lat = float(df_meta["dec_lat_va"].iloc[0]) if "dec_lat_va" in df_meta.columns else None
53
+ lon = float(df_meta["dec_long_va"].iloc[0]) if "dec_long_va" in df_meta.columns else None
54
+ except Exception as e:
55
+ print(f"Error getting site coordinates: {e}")
56
+ lat, lon = None, None
31
57
 
32
- print(f"🌊 Getting turbidity data for site {site_number}")
33
- print(f"📅 Date range: {start_date} to {end_date}")
34
58
 
35
- # Build USGS API URL for turbidity (parameter code 63680)
59
+ # --- Retrieve turbidity data (Build USGS API URL for turbidity (parameter code 63680))------------------------------------------------------------------------------------------------------------------------------------------------------------------
36
60
  url = (
37
61
  f"https://waterservices.usgs.gov/nwis/iv/"
38
62
  f"?sites={site_number}"
39
63
  f"&parameterCd=63680" # Turbidity parameter code
40
64
  f"&startDT={start_date}"
41
65
  f"&endDT={end_date}"
42
- f"&format=rdb"
43
- )
66
+ f"&format=rdb")
44
67
 
45
68
  try:
46
69
  # Get data from USGS
47
70
  response = requests.get(url, timeout=30)
48
71
 
49
72
  if response.status_code != 200:
50
- print(f" API Error: {response.status_code}")
51
- return _create_synthetic_turbidity(start_date, end_date)
73
+ print(f"No data found: API returned status {response.status_code}.")
74
+ print("Data for the specified site or parameters does not exist.")
75
+ return pd.DataFrame(), (lat, lon)
52
76
 
53
77
  # Parse the response
54
78
  data = _parse_usgs_response(response.text)
55
79
 
56
80
  if len(data) == 0:
57
- print("⚠️ No real data found. Creating synthetic data...")
58
- return _create_synthetic_turbidity(start_date, end_date)
81
+ print("No data found for the specified parameters or date range.")
82
+ return pd.DataFrame(), (lat, lon)
59
83
 
60
- print(f"Retrieved {len(data)} turbidity measurements")
61
- return data
84
+ print(f"Retrieved {len(data)} turbidity measurements")
85
+ return data, (lat, lon)
62
86
 
63
87
  except Exception as e:
64
- print(f"Error: {e}")
65
- print("🔄 Creating synthetic turbidity data...")
66
- return _create_synthetic_turbidity(start_date, end_date)
88
+ print(f"Error: {e}")
89
+ print("Data for the specified site or parameters does not exist.")
90
+ return pd.DataFrame(), (lat, lon)
67
91
 
68
92
 
93
+ # Function for parse and cleaning Turbidity Time Series from USGS API Respons as DataFrame ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
69
94
  def _parse_usgs_response(content: str) -> pd.DataFrame:
70
95
  """Parse USGS response and extract turbidity data."""
71
96
 
@@ -107,44 +132,5 @@ def _parse_usgs_response(content: str) -> pd.DataFrame:
107
132
  return pd.DataFrame()
108
133
 
109
134
 
110
- def _create_synthetic_turbidity(start_date: str, end_date: str) -> pd.DataFrame:
111
- """Create realistic synthetic turbidity data."""
112
-
113
- date_range = pd.date_range(start=start_date, end=end_date, freq='H')
114
-
115
- # Generate realistic turbidity values (typically 0-50 NTU)
116
- base_turbidity = 8.0 # Base level
117
- daily_variation = 3.0 # Daily fluctuation
118
-
119
- # Create synthetic values with realistic patterns
120
- synthetic_values = []
121
- for i, dt in enumerate(date_range):
122
- # Base value with daily pattern
123
- daily_factor = np.sin(2 * np.pi * dt.hour / 24) * daily_variation
124
-
125
- # Add some noise
126
- noise = np.random.normal(0, 1.5)
127
-
128
- # Occasional high turbidity events (storms)
129
- if np.random.random() < 0.02: # 2% chance of high event
130
- storm_factor = np.random.uniform(10, 30)
131
- else:
132
- storm_factor = 0
133
-
134
- value = base_turbidity + daily_factor + noise + storm_factor
135
- value = max(0.1, value) # Ensure positive values
136
-
137
- synthetic_values.append(value)
138
-
139
- # Create DataFrame
140
- synthetic_data = pd.DataFrame({
141
- 'turbidity': synthetic_values
142
- }, index=date_range)
143
-
144
- print(f"📊 Created {len(synthetic_data)} synthetic turbidity measurements")
145
-
146
- return synthetic_data
147
-
148
-
149
135
  # Simple alias for backwards compatibility
150
136
  get_usgs_turbidity = get_turbidity
@@ -1,11 +1,30 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydroanomaly
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: A Python package for hydro anomaly detection with simple USGS data retrieval
5
- Home-page: https://github.com/yourusername/hydroanomaly
6
- Author: Your Name
7
- Author-email: Your Name <your.email@example.com>
8
- License-Expression: MIT
5
+ Author-email: Ehsan Kahrizi <ehsan.kahrizi@usu.edu>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Your Name
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
9
28
  Project-URL: Homepage, https://github.com/yourusername/hydroanomaly
10
29
  Project-URL: Bug Reports, https://github.com/yourusername/hydroanomaly/issues
11
30
  Project-URL: Source, https://github.com/yourusername/hydroanomaly
@@ -30,10 +49,7 @@ Requires-Dist: pytest>=6.0; extra == "dev"
30
49
  Requires-Dist: black>=21.0; extra == "dev"
31
50
  Requires-Dist: flake8>=3.8; extra == "dev"
32
51
  Requires-Dist: mypy>=0.800; extra == "dev"
33
- Dynamic: author
34
- Dynamic: home-page
35
52
  Dynamic: license-file
36
- Dynamic: requires-python
37
53
 
38
54
  # HydroAnomaly
39
55
 
@@ -0,0 +1,9 @@
1
+ hydroanomaly/__init__.py,sha256=AFzyku0-OvyWnguhWZ-SoBcdPTKEPzV6GfOGLpPUFuQ,1375
2
+ hydroanomaly/sentinel_bands.py,sha256=TbdyvBRe9v3YGOz4umYhSJ-16a5lCF0YLwnmArZ9Gvs,3627
3
+ hydroanomaly/usgs_turbidity.py,sha256=k0cXRXpTe1YgjfR0Htw77SLD8hM--43jiEiJwx1vRg0,5664
4
+ hydroanomaly/visualize.py,sha256=gkLgI3agx291jK5o08nYEbEpGpr6cD-6aAKn2Ha2Lqk,6937
5
+ hydroanomaly-0.7.1.dist-info/licenses/LICENSE,sha256=OphKV48tcMv6ep-7j-8T6nycykPT0g8ZlMJ9zbGvdPs,1066
6
+ hydroanomaly-0.7.1.dist-info/METADATA,sha256=HIMGK2WWKXrX7PYfu_c5J3Fl-Zsetq9Ug9TiSZgyifM,12962
7
+ hydroanomaly-0.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ hydroanomaly-0.7.1.dist-info/top_level.txt,sha256=t-5Lc-eTLlkxIhR_N1Cpp6_YZafKS3xLLk9D2CtbE7o,13
9
+ hydroanomaly-0.7.1.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- hydroanomaly/__init__.py,sha256=pbS4SUDEYV8e9we3YqAe3Jo-EKX77eCdwirofp4Yk_4,1313
2
- hydroanomaly/sentinel_bands.py,sha256=Zvy9vJ7RooJTWLJK-ihGZ6CEneQcHVJ8I5MgueYcoyQ,5534
3
- hydroanomaly/usgs_turbidity.py,sha256=r0ozmiZXVYjtu04UnT3ijxra_WQVUUpus7WLcYCH3iM,4921
4
- hydroanomaly/visualize.py,sha256=gkLgI3agx291jK5o08nYEbEpGpr6cD-6aAKn2Ha2Lqk,6937
5
- hydroanomaly-0.6.0.dist-info/licenses/LICENSE,sha256=OphKV48tcMv6ep-7j-8T6nycykPT0g8ZlMJ9zbGvdPs,1066
6
- hydroanomaly-0.6.0.dist-info/METADATA,sha256=cZxYHfAnYHP1Wqspxf--JUBlKDKs29mhybbhslS1Zx0,11873
7
- hydroanomaly-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- hydroanomaly-0.6.0.dist-info/top_level.txt,sha256=t-5Lc-eTLlkxIhR_N1Cpp6_YZafKS3xLLk9D2CtbE7o,13
9
- hydroanomaly-0.6.0.dist-info/RECORD,,