ngiab-data-preprocess 4.3.0__py3-none-any.whl → 4.4.0__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.
@@ -6,6 +6,8 @@ import sqlite3
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
8
  from typing import Dict, Optional
9
+ import psutil
10
+ import os
9
11
 
10
12
  import pandas
11
13
  import requests
@@ -257,7 +259,27 @@ def configure_troute(
257
259
  with open(file_paths.template_troute_config, "r") as file:
258
260
  troute_template = file.read()
259
261
  time_step_size = 300
262
+ gpkg_file_path=f"{config_dir}/{cat_id}_subset.gpkg"
260
263
  nts = (end_time - start_time).total_seconds() / time_step_size
264
+ with sqlite3.connect(gpkg_file_path) as conn:
265
+ ncats_df = pandas.read_sql_query("SELECT COUNT(id) FROM 'divides';", conn)
266
+ ncats = ncats_df['COUNT(id)'][0]
267
+
268
+ est_bytes_required = nts * ncats * 45 # extremely rough calculation based on about 3 tests :)
269
+ local_ram_available = 0.8 * psutil.virtual_memory().available # buffer to not accidentally explode machine
270
+
271
+ if est_bytes_required > local_ram_available:
272
+ max_loop_size = nts // (est_bytes_required // local_ram_available)
273
+ binary_nexus_file_folder_comment = ""
274
+ parent_dir = config_dir.parent
275
+ output_parquet_path = Path(f"{parent_dir}/outputs/parquet/")
276
+
277
+ if not output_parquet_path.exists():
278
+ os.makedirs(output_parquet_path)
279
+ else:
280
+ max_loop_size = nts
281
+ binary_nexus_file_folder_comment = "#"
282
+
261
283
  filled_template = troute_template.format(
262
284
  # hard coded to 5 minutes
263
285
  time_step_size=time_step_size,
@@ -266,7 +288,8 @@ def configure_troute(
266
288
  geo_file_path=f"./config/{cat_id}_subset.gpkg",
267
289
  start_datetime=start_time.strftime("%Y-%m-%d %H:%M:%S"),
268
290
  nts=nts,
269
- max_loop_size=nts,
291
+ max_loop_size=max_loop_size,
292
+ binary_nexus_file_folder_comment=binary_nexus_file_folder_comment
270
293
  )
271
294
 
272
295
  with open(config_dir / "troute.yaml", "w") as file:
@@ -7,9 +7,9 @@ from typing import List, Literal, Optional, Tuple, Union
7
7
  import geopandas as gpd
8
8
  import numpy as np
9
9
  import xarray as xr
10
+ from dask.distributed import Client, Future, progress
11
+ from data_processing.dask_utils import no_cluster, temp_cluster
10
12
  from xarray.core.types import InterpOptions
11
- from dask.distributed import Client, progress, Future
12
- from data_processing.dask_utils import use_cluster
13
13
 
14
14
  logger = logging.getLogger(__name__)
15
15
 
@@ -117,13 +117,14 @@ def clip_dataset_to_bounds(
117
117
  return dataset
118
118
 
119
119
 
120
+ @no_cluster
120
121
  def interpolate_nan_values(
121
122
  dataset: xr.Dataset,
122
123
  variables: Optional[List[str]] = None,
123
124
  dim: str = "time",
124
125
  method: InterpOptions = "nearest",
125
126
  fill_value: str = "extrapolate",
126
- ) -> None:
127
+ ) -> bool:
127
128
  """
128
129
  Interpolates NaN values in specified (or all numeric time-dependent)
129
130
  variables of an xarray.Dataset. Operates inplace on the dataset.
@@ -145,6 +146,7 @@ def interpolate_nan_values(
145
146
  Set to "extrapolate" to fill with the nearest valid value when using 'nearest' or 'linear'.
146
147
  Default is "extrapolate".
147
148
  """
149
+ interpolation_used = False
148
150
  for name, var in dataset.data_vars.items():
149
151
  # if the variable is non-numeric, skip
150
152
  if not np.issubdtype(var.dtype, np.number):
@@ -158,9 +160,35 @@ def interpolate_nan_values(
158
160
  method=method,
159
161
  fill_value=fill_value if method in ["nearest", "linear"] else None,
160
162
  )
163
+ interpolation_used = True
164
+ return interpolation_used
161
165
 
162
166
 
163
- @use_cluster
167
+ @no_cluster
168
+ def save_dataset_no_cluster(
169
+ ds_to_save: xr.Dataset,
170
+ target_path: Path,
171
+ engine: Literal["netcdf4", "scipy", "h5netcdf"] = "h5netcdf",
172
+ ):
173
+ """
174
+ This explicitly does not use dask distributed.
175
+ Helper function to compute and save an xarray.Dataset to a NetCDF file.
176
+ Uses a temporary file and rename for avoid leaving a half written file.
177
+ """
178
+ if not target_path.parent.exists():
179
+ target_path.parent.mkdir(parents=True, exist_ok=True)
180
+
181
+ temp_file_path = target_path.with_name(target_path.name + ".saving.nc")
182
+ if temp_file_path.exists():
183
+ os.remove(temp_file_path)
184
+
185
+ ds_to_save.to_netcdf(temp_file_path, engine=engine, compute=True)
186
+
187
+ os.rename(str(temp_file_path), str(target_path))
188
+ logger.info(f"Successfully saved data to: {target_path}")
189
+
190
+
191
+ @temp_cluster
164
192
  def save_dataset(
165
193
  ds_to_save: xr.Dataset,
166
194
  target_path: Path,
@@ -184,20 +212,21 @@ def save_dataset(
184
212
  logger.debug(
185
213
  f"NetCDF write task submitted to Dask. Waiting for completion to {temp_file_path}..."
186
214
  )
215
+ logger.info("For more detailed progress, see the Dask dashboard http://localhost:8787/status")
187
216
  progress(future)
188
217
  future.result()
189
218
  os.rename(str(temp_file_path), str(target_path))
190
219
  logger.info(f"Successfully saved data to: {target_path}")
191
220
 
192
221
 
193
- @use_cluster
222
+ @no_cluster
194
223
  def save_to_cache(
195
224
  stores: xr.Dataset, cached_nc_path: Path, interpolate_nans: bool = True
196
225
  ) -> xr.Dataset:
197
226
  """
198
227
  Compute the store and save it to a cached netCDF file. This is not required but will save time and bandwidth.
199
228
  """
200
- logger.info(f"Processing dataset for caching. Final cache target: {cached_nc_path}")
229
+ logger.debug(f"Processing dataset for caching. Final cache target: {cached_nc_path}")
201
230
 
202
231
  # lasily cast all numbers to f32
203
232
  for name, var in stores.data_vars.items():
@@ -206,13 +235,18 @@ def save_to_cache(
206
235
 
207
236
  # save dataset locally before manipulating it
208
237
  save_dataset(stores, cached_nc_path)
209
- stores = xr.open_mfdataset(cached_nc_path, parallel=True, engine="h5netcdf")
210
238
 
211
239
  if interpolate_nans:
212
- interpolate_nan_values(dataset=stores)
213
- save_dataset(stores, cached_nc_path)
214
- stores = xr.open_mfdataset(cached_nc_path, parallel=True, engine="h5netcdf")
240
+ stores = xr.open_mfdataset(
241
+ cached_nc_path,
242
+ parallel=True,
243
+ engine="h5netcdf",
244
+ )
245
+ was_interpolated = interpolate_nan_values(dataset=stores)
246
+ if was_interpolated:
247
+ save_dataset_no_cluster(stores, cached_nc_path)
215
248
 
249
+ stores = xr.open_mfdataset(cached_nc_path, parallel=True, engine="h5netcdf")
216
250
  return stores
217
251
 
218
252
 
@@ -169,7 +169,6 @@ def get_upstream_cats(names: Union[str, List[str]]) -> Set[str]:
169
169
  node_index = graph.vs.find(cat=name).index
170
170
  else:
171
171
  node_index = graph.vs.find(name=name).index
172
- node_index = graph.vs.find(cat=name).index
173
172
  upstream_nodes = graph.subcomponent(node_index, mode="IN")
174
173
  for node in upstream_nodes:
175
174
  parent_ids.add(graph.vs[node]["name"])
@@ -178,7 +177,6 @@ def get_upstream_cats(names: Union[str, List[str]]) -> Set[str]:
178
177
  logger.critical(f"Catchment {name} not found in the hydrofabric graph.")
179
178
  except ValueError:
180
179
  logger.critical(f"Catchment {name} not found in the hydrofabric graph.")
181
-
182
180
  # sometimes returns None, which isn't helpful
183
181
  if None in cat_ids:
184
182
  cat_ids.remove(None)
data_processing/subset.py CHANGED
@@ -12,9 +12,11 @@ from data_processing.gpkg_utils import (
12
12
  update_geopackage_metadata,
13
13
  )
14
14
  from data_processing.graph_utils import get_upstream_ids
15
+ from rich.console import Console
16
+ from rich.prompt import Prompt
15
17
 
16
18
  logger = logging.getLogger(__name__)
17
-
19
+ console = Console()
18
20
  subset_tables = [
19
21
  "divides",
20
22
  "divide-attributes", # requires divides
@@ -30,15 +32,33 @@ subset_tables = [
30
32
 
31
33
 
32
34
  def create_subset_gpkg(
33
- ids: Union[List[str], str], hydrofabric: Path, output_gpkg_path: Path, is_vpu: bool = False
35
+ ids: Union[List[str], str],
36
+ hydrofabric: Path,
37
+ output_gpkg_path: Path,
38
+ is_vpu: bool = False,
39
+ override_gpkg: bool = True,
34
40
  ):
35
41
  # ids is a list of nexus and wb ids, or a single vpu id
36
42
  if not isinstance(ids, list):
37
43
  ids = [ids]
38
44
  output_gpkg_path.parent.mkdir(parents=True, exist_ok=True)
39
45
 
40
- if os.path.exists(output_gpkg_path):
41
- os.remove(output_gpkg_path)
46
+ if not override_gpkg:
47
+ if os.path.exists(output_gpkg_path):
48
+ response = Prompt.ask(
49
+ f"Subset geopackage at {output_gpkg_path} already exists. Are you sure you want to overwrite it?",
50
+ default="n",
51
+ choices=["y", "n"],
52
+ )
53
+ if response == "y":
54
+ console.print(f"Removing {output_gpkg_path}...", style="yellow")
55
+ os.remove(output_gpkg_path)
56
+ else:
57
+ console.print("Exiting...", style="bold red")
58
+ exit()
59
+ else:
60
+ if os.path.exists(output_gpkg_path):
61
+ os.remove(output_gpkg_path)
42
62
 
43
63
  create_empty_gpkg(output_gpkg_path)
44
64
  logger.info(f"Subsetting tables: {subset_tables}")
@@ -55,8 +75,18 @@ def create_subset_gpkg(
55
75
  def subset_vpu(
56
76
  vpu_id: str, output_gpkg_path: Path, hydrofabric: Path = file_paths.conus_hydrofabric
57
77
  ):
58
- if output_gpkg_path.exists():
59
- os.remove(output_gpkg_path)
78
+ if os.path.exists(output_gpkg_path):
79
+ response = Prompt.ask(
80
+ f"Subset geopackage at {output_gpkg_path} already exists. Are you sure you want to overwrite it?",
81
+ default="n",
82
+ choices=["y", "n"],
83
+ )
84
+ if response == "y":
85
+ console.print(f"Removing {output_gpkg_path}...", style="yellow")
86
+ os.remove(output_gpkg_path)
87
+ else:
88
+ console.print("Exiting...", style="bold red")
89
+ exit()
60
90
 
61
91
  create_subset_gpkg(vpu_id, hydrofabric, output_gpkg_path=output_gpkg_path, is_vpu=True)
62
92
  logger.info(f"Subset complete for VPU {vpu_id}")
@@ -68,6 +98,7 @@ def subset(
68
98
  hydrofabric: Path = file_paths.conus_hydrofabric,
69
99
  output_gpkg_path: Path = Path(),
70
100
  include_outlet: bool = True,
101
+ override_gpkg: bool = True,
71
102
  ):
72
103
  upstream_ids = list(get_upstream_ids(cat_ids, include_outlet))
73
104
 
@@ -78,6 +109,6 @@ def subset(
78
109
  paths = file_paths(output_folder_name)
79
110
  output_gpkg_path = paths.geopackage_path
80
111
 
81
- create_subset_gpkg(upstream_ids, hydrofabric, output_gpkg_path)
112
+ create_subset_gpkg(upstream_ids, hydrofabric, output_gpkg_path, override_gpkg=override_gpkg)
82
113
  logger.info(f"Subset complete for {len(upstream_ids)} features (catchments + nexuses)")
83
114
  logger.debug(f"Subset complete for {upstream_ids} catchments")
@@ -62,7 +62,7 @@ compute_parameters:
62
62
  qlat_input_folder: ./outputs/ngen/
63
63
  qlat_file_pattern_filter: "nex-*"
64
64
 
65
- #binary_nexus_file_folder: ./outputs/parquet/ # if nexus_file_pattern_filter="nex-*" and you want it to reformat them as parquet, you need this
65
+ {binary_nexus_file_folder_comment}binary_nexus_file_folder: ./outputs/parquet/ # if nexus_file_pattern_filter="nex-*" and you want it to reformat them as parquet, you need this
66
66
  #coastal_boundary_input_file : channel_forcing/schout_1.nc
67
67
  nts: {nts} #288 for 1day
68
68
  max_loop_size: {max_loop_size} # [number of timesteps]
@@ -141,6 +141,20 @@ def download_and_update_hf():
141
141
  bucket="communityhydrofabric",
142
142
  key="hydrofabrics/community/conus_nextgen.tar.gz",
143
143
  )
144
+
145
+ if file_paths.hydrofabric_graph.is_file():
146
+ console.print(
147
+ f"Hydrofabric graph already exists at {file_paths.hydrofabric_graph}, removing it to download the latest version.",
148
+ style="bold yellow",
149
+ )
150
+ file_paths.hydrofabric_graph.unlink()
151
+
152
+ download_from_s3(
153
+ file_paths.hydrofabric_graph,
154
+ bucket="communityhydrofabric",
155
+ key="hydrofabrics/community/conus_igraph_network.gpickle"
156
+ )
157
+
144
158
  status, headers = get_headers()
145
159
 
146
160
  if status == 200:
@@ -153,11 +167,10 @@ def download_and_update_hf():
153
167
  file_paths.conus_hydrofabric.parent,
154
168
  )
155
169
 
156
-
157
170
  def validate_hydrofabric():
158
171
  if not file_paths.conus_hydrofabric.is_file():
159
172
  response = Prompt.ask(
160
- "Hydrofabric is missing. Would you like to download it now?",
173
+ "Hydrofabric files are missing. Would you like to download them now?",
161
174
  default="y",
162
175
  choices=["y", "n"],
163
176
  )
@@ -4,67 +4,44 @@ async function subset() {
4
4
  alert('Please select at least one basin in the map before subsetting');
5
5
  return;
6
6
  }
7
- console.log('subsetting');
8
- document.getElementById('subset-button').disabled = true;
9
- document.getElementById('subset-loading').style.visibility = "visible";
10
- const startTime = performance.now(); // Start the timer
11
- document.getElementById('output-path').innerHTML = "Subsetting...";
12
- fetch('/subset', {
7
+ fetch('/subset_check', {
13
8
  method: 'POST',
14
9
  headers: { 'Content-Type': 'application/json' },
15
10
  body: JSON.stringify([cat_id]),
16
11
  })
17
- .then(response => response.text())
18
- .then(filename => {
19
- console.log(filename);
20
- const endTime = performance.now(); // Stop the timer
21
- const duration = endTime - startTime; // Calculate the duration in milliseconds
22
- console.log('Request took ' + duration / 1000 + ' milliseconds');
23
- document.getElementById('output-path').innerHTML = "Done in " + duration / 1000 + "s, subset to <a href='file://" + filename + "'>" + filename + "</a>";
24
- })
25
- .catch(error => {
26
- console.error('Error:', error);
27
- }).finally(() => {
28
- document.getElementById('subset-button').disabled = false;
29
- document.getElementById('subset-loading').style.visibility = "hidden";
30
- });
12
+ .then((response) => {
13
+ // 409 if that subset gpkg path already exists
14
+ if (response.status == 409) {
15
+ console.log("check response")
16
+ if (!confirm('A geopackage already exists with that catchment name. Overwrite?')) {
17
+ alert("Subset canceled.");
18
+ return;
19
+ }
20
+ }
21
+ const startTime = performance.now(); // Start the timer
22
+ fetch('/subset', {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify([cat_id]),
26
+ })
27
+ .then(response => response.text())
28
+ .then(filename => {
29
+ console.log(filename);
30
+ const endTime = performance.now(); // Stop the timer
31
+ const duration = endTime - startTime; // Calculate the duration in milliseconds
32
+ console.log('Request took ' + duration / 1000 + ' milliseconds');
33
+ document.getElementById('output-path').innerHTML = "Done in " + (duration / 1000).toFixed(2) + "s, subset to <a href='file://" + filename + "'>" + filename + "</a>";
34
+ })
35
+ .catch(error => {
36
+ console.error('Error:', error);
37
+ }).finally(() => {
38
+ document.getElementById('subset-button').disabled = false;
39
+ document.getElementById('subset-loading').style.visibility = "hidden";
40
+ });
41
+ });
31
42
  }
32
43
 
33
-
34
- // async function subset_to_file() {
35
- // if (Object.keys(cat_id_dict).length === 0) {
36
- // alert('Please select at least one basin in the map before subsetting');
37
- // return;
38
- // }
39
- // console.log('subsetting to file');
40
- // document.getElementById('subset-to-file-button').disabled = true;
41
- // document.getElementById('subset-to-file-loading').style.visibility = "visible";
42
- // const startTime = performance.now(); // Start the timer
43
- // document.getElementById('output-path').innerHTML = "Subsetting...";
44
- // fetch('/subset_to_file', {
45
- // method: 'POST',
46
- // headers: { 'Content-Type': 'application/json' },
47
- // body: JSON.stringify(cat_id_dict),
48
- // })
49
- // .then(response => response.text())
50
- // .then(filename => {
51
- // console.log(filename);
52
- // const endTime = performance.now(); // Stop the timer
53
- // const duration = endTime - startTime; // Calculate the duration in milliseconds
54
- // console.log('Request took ' + duration / 1000 + ' milliseconds');
55
- // document.getElementById('output-path').innerHTML = "Done in " + duration / 1000 + "s, subset to <a href='file://" + filename + "'>" + filename + "</a>";
56
- // })
57
- // .catch(error => {
58
- // console.error('Error:', error);
59
- // }).finally(() => {
60
- // document.getElementById('subset-to-file-button').disabled = false;
61
- // document.getElementById('subset-to-file-loading').style.visibility = "hidden";
62
- // });
63
- // }
64
-
65
44
  async function forcings() {
66
-
67
-
68
45
  if (document.getElementById('output-path').textContent === '') {
69
46
  alert('Please subset the data before getting forcings');
70
47
  return;
@@ -139,6 +116,5 @@ async function realization() {
139
116
 
140
117
  // These functions are exported by data_processing.js
141
118
  document.getElementById('subset-button').addEventListener('click', subset);
142
- // document.getElementById('subset-to-file-button').addEventListener('click', subset_to_file);
143
119
  document.getElementById('forcings-button').addEventListener('click', forcings);
144
120
  document.getElementById('realization-button').addEventListener('click', realization);
map_app/static/js/main.js CHANGED
@@ -133,7 +133,6 @@ function update_map(cat_id, e) {
133
133
  $('#selected-basins').text(cat_id)
134
134
  map.setFilter('selected-catchments', ['any', ['in', 'divide_id', cat_id]]);
135
135
  map.setFilter('upstream-catchments', ['any', ['in', 'divide_id', ""]])
136
-
137
136
  fetch('/get_upstream_catids', {
138
137
  method: 'POST',
139
138
  headers: { 'Content-Type': 'application/json' },
map_app/views.py CHANGED
@@ -27,7 +27,9 @@ def index():
27
27
  @main.route("/get_upstream_catids", methods=["POST"])
28
28
  def get_upstream_catids():
29
29
  cat_id = json.loads(request.data.decode("utf-8"))
30
- upstream_cats = get_upstream_cats(cat_id)
30
+ # give wb_id to get_upstream_cats because the graph search is 1000x faster
31
+ wb_id = "wb-" + cat_id.split("-")[-1]
32
+ upstream_cats = get_upstream_cats(wb_id)
31
33
  if cat_id in upstream_cats:
32
34
  upstream_cats.remove(cat_id)
33
35
  return list(upstream_cats), 200
@@ -41,13 +43,25 @@ def get_upstream_wbids():
41
43
  return [id for id in upstream_ids if id.startswith("wb")], 200
42
44
 
43
45
 
46
+ @main.route("/subset_check", methods=["POST"])
47
+ def subset_check():
48
+ cat_ids = list(json.loads(request.data.decode("utf-8")))
49
+ logger.info(cat_ids)
50
+ subset_name = cat_ids[0]
51
+ run_paths = file_paths(subset_name)
52
+ if run_paths.geopackage_path.exists():
53
+ return "check required", 409
54
+ else:
55
+ return "success", 200
56
+
57
+
44
58
  @main.route("/subset", methods=["POST"])
45
59
  def subset_selection():
46
60
  cat_ids = list(json.loads(request.data.decode("utf-8")))
47
61
  logger.info(cat_ids)
48
62
  subset_name = cat_ids[0]
49
63
  run_paths = file_paths(subset_name)
50
- subset(cat_ids, output_gpkg_path=run_paths.geopackage_path)
64
+ subset(cat_ids, output_gpkg_path=run_paths.geopackage_path, override_gpkg=True)
51
65
  return str(run_paths.geopackage_path), 200
52
66
 
53
67
 
@@ -1,13 +1,13 @@
1
1
  from typing import Tuple
2
+
2
3
  import rich.status
3
4
 
4
5
  # add a status bar for these imports so the cli feels more responsive
5
- with rich.status.Status("Initializing...") as status:
6
+ with rich.status.Status("loading") as status:
6
7
  import argparse
7
8
  import logging
8
9
  import subprocess
9
10
  import time
10
- from typing import List
11
11
 
12
12
  import geopandas as gpd
13
13
  from data_processing.create_realization import create_em_realization, create_realization
@@ -19,7 +19,7 @@ with rich.status.Status("Initializing...") as status:
19
19
  from data_processing.gpkg_utils import get_cat_from_gage_id, get_catid_from_point
20
20
  from data_processing.graph_utils import get_upstream_cats
21
21
  from data_processing.subset import subset, subset_vpu
22
- from data_sources.source_validation import validate_output_dir, validate_hydrofabric
22
+ from data_sources.source_validation import validate_hydrofabric, validate_output_dir
23
23
  from ngiab_data_cli.arguments import parse_arguments
24
24
  from ngiab_data_cli.custom_logging import set_logging_to_critical_only, setup_logging
25
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngiab_data_preprocess
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: Graphical Tools for creating Next Gen Water model input data.
5
5
  Author-email: Josh Cunningham <jcunningham8@ua.edu>
6
6
  Project-URL: Homepage, https://github.com/CIROH-UA/NGIAB_data_preprocess
@@ -32,6 +32,7 @@ Requires-Dist: colorama==0.4.6
32
32
  Requires-Dist: bokeh==3.5.1
33
33
  Requires-Dist: boto3
34
34
  Requires-Dist: numcodecs<0.16.0
35
+ Requires-Dist: scipy>=1.15.3
35
36
  Provides-Extra: eval
36
37
  Requires-Dist: ngiab_eval; extra == "eval"
37
38
  Provides-Extra: plot
@@ -40,55 +41,92 @@ Dynamic: license-file
40
41
 
41
42
  # NGIAB Data Preprocess
42
43
 
43
- This repository contains tools for preparing data to run a [next gen](https://github.com/NOAA-OWP/ngen) simulation using [NGIAB](https://github.com/CIROH-UA/NGIAB-CloudInfra). The tools allow you to select a catchment of interest on an interactive map, choose a date range, and prepare the data with just a few clicks!
44
+ This repository contains tools for preparing data to run a [NextGen](https://github.com/NOAA-OWP/ngen)-based simulation using [NGIAB](https://github.com/CIROH-UA/NGIAB-CloudInfra). The tools allow you to select a catchment of interest on an interactive map, choose a date range, and prepare the data with just a few clicks!
44
45
 
45
46
  ![map screenshot](https://github.com/CIROH-UA/NGIAB_data_preprocess/blob/main/modules/map_app/static/resources/screenshot.jpg)
46
47
 
48
+ | | |
49
+ | --- | --- |
50
+ | ![CIROH Logo](./ciroh-bgsafe.png) | Funding for this project was provided by the National Oceanic & Atmospheric Administration (NOAA), awarded to the Cooperative Institute for Research to Operations in Hydrology (CIROH) through the NOAA Cooperative Agreement with The University of Alabama (NA22NWS4320003). |
51
+
47
52
  ## Table of Contents
48
53
 
49
54
  1. [What does this tool do?](#what-does-this-tool-do)
50
- 2. [What does it not do?](#what-does-it-not-do)
55
+ 2. [Limitations](#limitations)
56
+ - [Custom realizations](#custom-realizations)
57
+ - [Calibration](#calibration)
51
58
  - [Evaluation](#evaluation)
52
59
  - [Visualisation](#visualisation)
53
60
  3. [Requirements](#requirements)
54
- 4. [Installation and Running](#installation-and-running)
61
+ 4. [Installation and running](#installation-and-running)
55
62
  - [Running without install](#running-without-install)
56
- 5. [For legacy pip installation](#for-legacy-pip-installation)
57
- 6. [Development Installation](#development-installation)
58
- 7. [Usage](#usage)
59
- 8. [CLI Documentation](#cli-documentation)
63
+ - [For uv installation](#for-uv-installation)
64
+ - [For legacy pip installation](#for-legacy-pip-installation)
65
+ - [Development installation](#development-installation)
66
+ 5. [Map interface documentation](#map-interface-documentation)
67
+ - [Running the map interface app](#running-the-map-interface-app)
68
+ - [Using the map interace](#using-the-map-interface)
69
+ 6. [CLI documentation](#cli-documentation)
70
+ - [Running the CLI](#running-the-cli)
60
71
  - [Arguments](#arguments)
61
- - [Usage Notes](#usage-notes)
72
+ - [Usage notes](#usage-notes)
62
73
  - [Examples](#examples)
74
+ 7. [Realization information](#realization-information)
75
+ - [NOAH + CFE](#noah--cfe)
63
76
 
64
77
  ## What does this tool do?
65
78
 
66
- This tool prepares data to run a next gen simulation by creating a run package that can be used with NGIAB.
79
+ This tool prepares data to run a NextGen-based simulation by creating a run package that can be used with NGIAB.
67
80
  It uses geometry and model attributes from the [v2.2 hydrofabric](https://lynker-spatial.s3-us-west-2.amazonaws.com/hydrofabric/v2.2/conus/conus_nextgen.gpkg) more information on [all data sources here](https://lynker-spatial.s3-us-west-2.amazonaws.com/hydrofabric/v2.2/hfv2.2-data_model.html).
68
81
  The raw forcing data is [nwm retrospective v3 forcing](https://noaa-nwm-retrospective-3-0-pds.s3.amazonaws.com/index.html#CONUS/zarr/forcing/) data or the [AORC 1km gridded data](https://noaa-nws-aorc-v1-1-1km.s3.amazonaws.com/index.html) depending on user input
69
82
 
70
- 1. **Subset** (delineate) everything upstream of your point of interest (catchment, gage, flowpath etc). Outputs as a geopackage.
71
- 2. **Calculates** Forcings as a weighted mean of the gridded AORC forcings. Weights are calculated using [exact extract](https://isciences.github.io/exactextract/) and computed with numpy.
72
- 3. Creates **configuration files** needed to run nextgen.
83
+ 1. **Subsets** (delineates) everything upstream of your point of interest (catchment, gage, flowpath etc) from the hydrofabric. This subset is output as a geopackage (.gpkg).
84
+ 2. Calculates **forcings** as a weighted mean of the gridded NWM or AORC forcings. Weights are calculated using [exact extract](https://isciences.github.io/exactextract/) and computed with numpy.
85
+ 3. Creates **configuration files** for a default NGIAB model run.
73
86
  - realization.json - ngen model configuration
74
87
  - troute.yaml - routing configuration.
75
88
  - **per catchment** model configuration
76
- 4. Optionally Runs a non-interactive [Next gen in a box](https://github.com/CIROH-UA/NGIAB-CloudInfra).
89
+ 4. Optionally performs a non-interactive [Docker-based NGIAB](https://github.com/CIROH-UA/NGIAB-CloudInfra) run.
90
+
91
+ ## Limitations
92
+ This tool cannot do the following:
93
+
94
+ ### Custom realizations
95
+ This tool currently only outputs a single, default realization, which is described in "[Realization information](#realization-information)". Support for additional model configurations is planned, but not currently available.
96
+
97
+ ### Calibration
98
+ If available, this repository will download [calibrated parameters](https://communityhydrofabric.s3.us-east-1.amazonaws.com/index.html#hydrofabrics/community/gage_parameters/) from the [Community Hydrofabric](https://github.com/CIROH-UA/community_hf_patcher) AWS S3 bucket.
99
+ However, many gages and catchments will not have such parameters available. In these cases, Data Preprocess will output realizations with default values.
77
100
 
78
- ## What does it not do?
101
+ For automatic calibration, please see [ngiab-cal](https://github.com/CIROH-UA/ngiab-cal), which is under active development.
79
102
 
80
103
  ### Evaluation
81
- For automatic evaluation using [Teehr](https://github.com/RTIInternational/teehr), please run [NGIAB](https://github.com/CIROH-UA/NGIAB-CloudInfra) interactively using the `guide.sh` script.
104
+ For automatic evaluation using [TEEHR](https://github.com/RTIInternational/teehr), please run [NGIAB](https://github.com/CIROH-UA/NGIAB-CloudInfra) interactively using the `guide.sh` script.
82
105
 
83
106
  ### Visualisation
84
107
  For automatic interactive visualisation, please run [NGIAB](https://github.com/CIROH-UA/NGIAB-CloudInfra) interactively using the `guide.sh` script
85
108
 
86
- ## Requirements
109
+ # Requirements
87
110
 
88
- * This tool is officially supported on macOS or Ubuntu (tested on 22.04 & 24.04). To use it on Windows, please install [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
111
+ This tool is **officially supported** on **macOS** and **Ubuntu** (tested on 22.04 & 24.04). To use it on Windows, please install [**WSL**](https://learn.microsoft.com/en-us/windows/wsl/install).
89
112
 
90
- ## Installation and Running
91
- It is highly recommended to use [Astral UV](https://docs.astral.sh/uv/) to install and run this tool. It works similarly to pip and conda, and I would also recommend you use it for other python projects as it is so useful.
113
+ It is also **highly recommended** to use [Astral UV](https://docs.astral.sh/uv/) to install and run this tool. Installing the project via `pip` without the use of a virtual environment creates a **severe risk** of dependency conflicts.
114
+
115
+ # Installation and running
116
+
117
+ ### Running without install
118
+ This package supports pipx and uvx, which means you can run the tool without installing it. No virtual environment needed, just UV.
119
+ ```bash
120
+ # Run these from anywhere!
121
+ uvx --from ngiab-data-preprocess cli --help # Running the CLI
122
+ uvx ngiab-prep --help # Alias for the CLI
123
+ uvx --from ngiab-data-preprocess map_app # Running the map interface
124
+ ```
125
+
126
+ ### For uv installation
127
+
128
+ <details>
129
+ <summary>Click here to expand</summary>
92
130
 
93
131
  ```bash
94
132
  # Install UV
@@ -111,16 +149,10 @@ uv run map_app
111
149
 
112
150
  UV automatically detects any virtual environments in the current directory and will use them when you use `uv run`.
113
151
 
114
- ### Running without install
115
- This package supports pipx and uvx which means you can run the tool without installing it. No virtual environment needed, just UV.
116
- ```bash
117
- # run this from anywhere
118
- uvx --from ngiab_data_preprocess cli --help
119
- # for the map
120
- uvx --from ngiab_data_preprocess map_app
121
- ```
152
+ </details>
153
+
154
+ ### For legacy pip installation
122
155
 
123
- ## For legacy pip installation
124
156
  <details>
125
157
  <summary>Click here to expand</summary>
126
158
 
@@ -142,7 +174,7 @@ python -m map_app
142
174
  ```
143
175
  </details>
144
176
 
145
- ## Development Installation
177
+ ### Development installation
146
178
 
147
179
  <details>
148
180
  <summary>Click to expand installation steps</summary>
@@ -168,11 +200,17 @@ To install and run the tool, follow these steps:
168
200
  ```
169
201
  </details>
170
202
 
171
- ## Usage
203
+ # Map interface documentation
204
+
205
+ ## Running the map interface app
172
206
 
173
- Running the command `uv run map_app` will open the app in a new browser tab.
207
+ Running the `map_app` tool will open the app in a new browser tab.
208
+
209
+ Install-free: `uvx --from ngiab-data-preprocess map_app`
210
+ Installed with uv: `uv run map_app`
211
+
212
+ ## Using the map interface
174
213
 
175
- To use the tool:
176
214
  1. Select the catchment you're interested in on the map.
177
215
  2. Pick the time period you want to simulate.
178
216
  3. Click the following buttons in order:
@@ -184,7 +222,12 @@ Once all the steps are finished, you can run NGIAB on the folder shown underneat
184
222
 
185
223
  **Note:** When using the tool, the default output will be stored in the `~/ngiab_preprocess_output/<your-input-feature>/` folder. There is no overwrite protection on the folders.
186
224
 
187
- # CLI Documentation
225
+ # CLI documentation
226
+
227
+ ## Running the CLI
228
+
229
+ Install-free: `uvx ngiab-prep`
230
+ Installed with uv: `uv run cli`
188
231
 
189
232
  ## Arguments
190
233
 
@@ -201,11 +244,11 @@ Once all the steps are finished, you can run NGIAB on the folder shown underneat
201
244
  - `-o OUTPUT_NAME`, `--output_name OUTPUT_NAME`: Name of the output folder.
202
245
  - `--source` : The datasource you want to use, either `nwm` for retrospective v3 or `aorc`. Default is `nwm`
203
246
  - `-D`, `--debug`: Enable debug logging.
204
- - `--run`: Automatically run Next Gen against the output folder.
205
- - `--validate`: Run every missing step required to run ngiab.
206
- - `-a`, `--all`: Run all operations: subset, forcings, realization, run Next Gen
247
+ - `--run`: Automatically run [NGIAB's docker distribution](https://github.com/CIROH-UA/NGIAB-CloudInfra) against the output folder.
248
+ - `--validate`: Run every missing step required to run NGIAB.
249
+ - `-a`, `--all`: Run all operations. Equivalent to `-sfr` and `--run`.
207
250
 
208
- ## Usage Notes
251
+ ## Usage notes
209
252
  - If your input has a prefix of `gage-`, you do not need to pass `-g`.
210
253
  - The `-l`, `-g`, `-s`, `-f`, `-r` flags can be combined like normal CLI flags. For example, to subset, generate forcings, and create a realization, you can use `-sfr` or `-s -f -r`.
211
254
  - When using the `--all` flag, it automatically sets `subset`, `forcings`, `realization`, and `run` to `True`.
@@ -213,50 +256,53 @@ Once all the steps are finished, you can run NGIAB on the folder shown underneat
213
256
 
214
257
  ## Examples
215
258
 
216
- 0. Prepare everything for a nextgen run at a given gage:
259
+ 1. Prepare everything for an NGIAB run at a given gage:
217
260
  ```bash
218
- python -m ngiab_data_cli -i gage-10154200 -sfr --start 2022-01-01 --end 2022-02-28
219
- # add --run or replace -sfr with --all to run nextgen in a box too
261
+ uvx ngiab-prep -i gage-10154200 -sfr --start 2022-01-01 --end 2022-02-28
262
+ # add --run or replace -sfr with --all to run NGIAB, too
220
263
  # to name the folder, add -o folder_name
221
264
  ```
222
265
 
223
- 1. Subset hydrofabric using catchment ID or VPU:
266
+ 2. Subset the hydrofabric using a catchment ID or VPU:
224
267
  ```bash
225
- python -m ngiab_data_cli -i cat-7080 -s
226
- python -m ngiab_data_cli --vpu 01 -s
268
+ uvx ngiab-prep -i cat-7080 -s
269
+ uvx ngiab-prep --vpu 01 -s
227
270
  ```
228
271
 
229
- 2. Generate forcings using a single catchment ID:
272
+ 3. Generate forcings using a single catchment ID:
230
273
  ```bash
231
- python -m ngiab_data_cli -i cat-5173 -f --start 2022-01-01 --end 2022-02-28
274
+ uvx ngiab-prep -i cat-5173 -f --start 2022-01-01 --end 2022-02-28
232
275
  ```
233
276
 
234
- 3. Create realization using a lat/lon pair and output to a named folder:
277
+ 4. Create realization using a latitude/longitude pair and output to a named folder:
235
278
  ```bash
236
- python -m ngiab_data_cli -i 33.22,-87.54 -l -r --start 2022-01-01 --end 2022-02-28 -o custom_output
279
+ uvx ngiab-prep -i 33.22,-87.54 -l -r --start 2022-01-01 --end 2022-02-28 -o custom_output
237
280
  ```
238
281
 
239
- 4. Perform all operations using a lat/lon pair:
282
+ 5. Perform all operations using a latitude/longitude pair:
240
283
  ```bash
241
- python -m ngiab_data_cli -i 33.22,-87.54 -l -s -f -r --start 2022-01-01 --end 2022-02-28
284
+ uvx ngiab-prep -i 33.22,-87.54 -l -s -f -r --start 2022-01-01 --end 2022-02-28
242
285
  ```
243
286
 
244
- 5. Subset hydrofabric using gage ID:
287
+ 6. Subset the hydrofabric using a gage ID:
245
288
  ```bash
246
- python -m ngiab_data_cli -i 10154200 -g -s
289
+ uvx ngiab-prep -i 10154200 -g -s
247
290
  # or
248
- python -m ngiab_data_cli -i gage-10154200 -s
291
+ uvx ngiab-prep -i gage-10154200 -s
249
292
  ```
250
293
 
251
- 6. Generate forcings using a single gage ID:
294
+ 7. Generate forcings using a single gage ID:
252
295
  ```bash
253
- python -m ngiab_data_cli -i 01646500 -g -f --start 2022-01-01 --end 2022-02-28
296
+ uvx ngiab-prep -i 01646500 -g -f --start 2022-01-01 --end 2022-02-28
254
297
  ```
255
298
 
256
- 7. Run all operations, including Next Gen and evaluation/plotting:
257
- ```bash
258
- python -m ngiab_data_cli -i cat-5173 -a --start 2022-01-01 --end 2022-02-28
259
- ```
299
+ # Realization information
260
300
 
301
+ This tool currently offers one default realization.
261
302
 
303
+ ## NOAH + CFE
262
304
 
305
+ [This realization](https://github.com/CIROH-UA/NGIAB_data_preprocess/blob/main/modules/data_sources/cfe-nowpm-realization-template.json) is intended to be roughly comparable to earlier versions of the National Water Model.
306
+ - [NOAH-OWP-Modular](https://github.com/NOAA-OWP/NOAH-OWP-Modular): A refactoring of Noah-MP, a land-surface model. Used to model groundwater properties.
307
+ - [Conceptual Functional Equivalent (CFE)](https://github.com/NOAA-OWP/CFE): A simplified conceptual approximation of versions 1.2, 2.0, and 2.1 of the National Water Model. Used to model precipitation and evaporation.
308
+ - [SLoTH](https://github.com/NOAA-OWP/SLoTH): A module used to feed through unchanged values. In this default configuration, it simply forces certain soil moisture and ice fraction properties to zero.
@@ -1,43 +1,43 @@
1
- data_processing/create_realization.py,sha256=WZCnYps-d3xd6_F4-Fy95nyXoh3GX4DzpUBWXvSvzKY,14953
1
+ data_processing/create_realization.py,sha256=mdse8W2DgPg5Lj2_ErUsLJh-touTmShKwQrrOWO0jlY,15958
2
2
  data_processing/dask_utils.py,sha256=A2IP94WAz8W9nek3etXKEKTOxGPf0NWSFLh8cZ5S-xU,2454
3
- data_processing/dataset_utils.py,sha256=CMDy-YfjFQ9FM_BbRHnRKUFwERWK9ATJ0wn4wI0gUwY,10024
3
+ data_processing/dataset_utils.py,sha256=AJOxE2nRfZnWYon_qqGcfkpRZuRW8Yy8YI86SxVDU3M,11168
4
4
  data_processing/datasets.py,sha256=_EJ1uZSWTU1HWpvF7TQSikneJqWZFikTrdo9usCV8A0,4665
5
5
  data_processing/file_paths.py,sha256=l2iCUFt_pk-jjzl7OS7npROAnQxwqFfZ7b2wRjViqiU,4720
6
6
  data_processing/forcings.py,sha256=k-JhBncTnXcdjSieam1Q2cDx5Xt9hH5Aywv0gDY4O2U,19010
7
7
  data_processing/gpkg_utils.py,sha256=tSSIMlHeqqgxTJQyF3X9tPmunQTJYx0xrCNHqUBQxkg,20590
8
- data_processing/graph_utils.py,sha256=-0vmLZvuhi9jLFSUfA-3Lo-wGfX4hMfB2QQ6A2D2FO8,8362
8
+ data_processing/graph_utils.py,sha256=qvHw6JlzQxLi--eMsGgC_rUBP4nDatl6X9mSa03Xxyo,8306
9
9
  data_processing/s3fs_utils.py,sha256=ki1EmA0ezV0r26re6dRWIGzL5FudGdwF9Qw1eVLR0Bc,2747
10
- data_processing/subset.py,sha256=15rzjKlTPAHtFYZusKDtb4-zhG-8sTKU68ou9BA-_9Q,2610
10
+ data_processing/subset.py,sha256=XoojOgWCwxOi5Q4KXHXARNQeoZlobJp-mqhIIvTRtTw,3793
11
11
  data_sources/cfe-nowpm-realization-template.json,sha256=8an6q1drWD8wU1ocvdPab-GvZDvlQ-0di_-NommH3QI,3528
12
12
  data_sources/cfe-template.ini,sha256=6e5-usqjWtm3MWVvtm8CTeZTJJMxO1ZswkOXq0L9mnc,2033
13
13
  data_sources/em-catchment-template.yml,sha256=M08ixazEUHYI2PNavtI0xPZeSzcQ9bg2g0XzNT-8_u4,292
14
14
  data_sources/em-config.yml,sha256=y0J8kEA70rxLWXJjz-CQ7sawcVyhQcayofeLlq4Svbo,1330
15
15
  data_sources/em-realization-template.json,sha256=DJvB7N8lCeS2vLFenmbTzysBDR-xPaJ09XA8heu1ijY,1466
16
16
  data_sources/forcing_template.nc,sha256=uRuVAqX3ngdlougZINavtwl_wC2VLD8fHqG7_CLim1s,85284
17
- data_sources/ngen-routing-template.yaml,sha256=RV28MAbyQNx9U8FAYmZhD2Fv8Yu6o_08Ekoc77KNdH4,4622
17
+ data_sources/ngen-routing-template.yaml,sha256=wM5v6jj0kwcJBVatLFuy2big6g8nlSXxzc8a23nwI5s,4655
18
18
  data_sources/noah-owp-modular-init.namelist.input,sha256=Vb7mp40hFpJogruOrXrDHwVW1bKi9h1ciDNyDvTzn20,3045
19
- data_sources/source_validation.py,sha256=vrCuh2nFy9x-8MKqbUtxpdWCm3ohKK6UFcGR87n4I7I,9029
19
+ data_sources/source_validation.py,sha256=RmvyPLjuDetpuNOUqCclgDfe8zd_Ojr7pfbUoUya2pQ,9498
20
20
  data_sources/template.sql,sha256=ZnFqAqleEq9wgmAhNO90Wue_L9k0JAn8KF99DYtcxgs,10457
21
21
  data_sources/triggers.sql,sha256=G0d_175eNsamKAFhsbphPATvzMPuPL_iCleIhlToduQ,14906
22
22
  map_app/__init__.py,sha256=OarJao9X98kcbLyiwewN4ObWNAYkKDichcxbuWywTsA,818
23
23
  map_app/__main__.py,sha256=Uj7__cJUyPQkZo2tNQ2x2g6rwizsyg1DcNtJkQniHzY,1650
24
- map_app/views.py,sha256=SMrnXDjoIMk8yMrBsrif41GLS-QLuN79cWYbA-uqKX8,5138
24
+ map_app/views.py,sha256=ajU_QSd-Oa7UrRQEZPX4rmOlaKwo76Q8UPQNXtt-e2k,5622
25
25
  map_app/static/css/console.css,sha256=xN6G2MMFyKc9YW9HEVpUUTUjx2o2nokBR4nCX5c18UM,803
26
26
  map_app/static/css/main.css,sha256=HmRIfhWeHTrNLOCHGpaKuzwGj05LkkUiQy538D-ZRLY,6464
27
27
  map_app/static/css/toggle.css,sha256=Ep6tXT7gCrPRRITuEMpXyisuiTQgiLIEKFFTWRmC82o,1913
28
28
  map_app/static/js/console.js,sha256=BnG0pED5B9d563sLWshDNt_q-SyoTY48sETvVoOVJkU,1377
29
- map_app/static/js/data_processing.js,sha256=X6NSuggOGNIJUF-LEyGGYJjtiA5J29xmkXgFFmfBw18,6711
30
- map_app/static/js/main.js,sha256=JkvZqDuzYQaNtVmGeOdg0Za6OUBIG7hGOR3CB-uoviQ,9691
29
+ map_app/static/js/data_processing.js,sha256=wXv0p_bPmNOrSpU_p6Yqtfd17vqOFRJFAmLdUUWLF7s,5486
30
+ map_app/static/js/main.js,sha256=_Yq1tuzyREqWU24rFQJSh5zIaXtAXEGlfZPo36QLHvI,9690
31
31
  map_app/static/resources/loading.gif,sha256=ggdkZf1AD7rSwIpSJwfiIqANgmVV1WHlxGuKxQKv7uY,72191
32
32
  map_app/static/resources/screenshot.jpg,sha256=Ia358aX-OHM9BP4B8lX05cLnguF2fHUIimno9bnFLYw,253730
33
33
  map_app/templates/index.html,sha256=Jy2k1Ob2_et--BPpfmTYO22Yin3vrG6IOeNlwzUoEqY,7878
34
- ngiab_data_cli/__main__.py,sha256=LIRuzYCT2bF1eeW51hJIrAeeMmyHL7MevpTftcWbvR0,10605
34
+ ngiab_data_cli/__main__.py,sha256=13W3RnD73weQNYZdq6munx_0oMBgzc-yzluKEm5nSxg,10570
35
35
  ngiab_data_cli/arguments.py,sha256=yBULJnFgUvgP4YZmZ5HhR7g0EfdMtBCdQuDkDuYSXCQ,4322
36
36
  ngiab_data_cli/custom_logging.py,sha256=iS2XozaxudcxQj17qAsrCgbVK9LJAYAPmarJuVWJo1k,1280
37
37
  ngiab_data_cli/forcing_cli.py,sha256=eIWRxRWUwPqR16fihFDEIV4VzGlNuvcD6lJW5VYjkPU,3635
38
- ngiab_data_preprocess-4.3.0.dist-info/licenses/LICENSE,sha256=6dMSprwwnsRzEm02mEDbKHD9dUbL8bPIt9Vhrhb0Ulk,1081
39
- ngiab_data_preprocess-4.3.0.dist-info/METADATA,sha256=zzIirFNOmhxVhaYD09onH14VbLTRV3EUIVxdCnh1EdA,10465
40
- ngiab_data_preprocess-4.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
- ngiab_data_preprocess-4.3.0.dist-info/entry_points.txt,sha256=spwlhKEJ3ZnNETQsJGeTjD7Vwy8O_zGHb9GdX8ACCtw,128
42
- ngiab_data_preprocess-4.3.0.dist-info/top_level.txt,sha256=CjhYAUZrdveR2fOK6rxffU09VIN2IuPD7hk4V3l3pV0,52
43
- ngiab_data_preprocess-4.3.0.dist-info/RECORD,,
38
+ ngiab_data_preprocess-4.4.0.dist-info/licenses/LICENSE,sha256=6dMSprwwnsRzEm02mEDbKHD9dUbL8bPIt9Vhrhb0Ulk,1081
39
+ ngiab_data_preprocess-4.4.0.dist-info/METADATA,sha256=8PlfoGwOJIpuKhFwtfWmfxdMaDeXBfFRz9CAeZ3sZKk,13344
40
+ ngiab_data_preprocess-4.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
+ ngiab_data_preprocess-4.4.0.dist-info/entry_points.txt,sha256=spwlhKEJ3ZnNETQsJGeTjD7Vwy8O_zGHb9GdX8ACCtw,128
42
+ ngiab_data_preprocess-4.4.0.dist-info/top_level.txt,sha256=CjhYAUZrdveR2fOK6rxffU09VIN2IuPD7hk4V3l3pV0,52
43
+ ngiab_data_preprocess-4.4.0.dist-info/RECORD,,