ras-commander 0.35.0__py3-none-any.whl → 0.36.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.
- ras_commander/RasCmdr.py +360 -332
- ras_commander/RasExamples.py +113 -80
- ras_commander/RasGeo.py +38 -28
- ras_commander/RasGpt.py +142 -0
- ras_commander/RasHdf.py +170 -253
- ras_commander/RasPlan.py +115 -166
- ras_commander/RasPrj.py +212 -141
- ras_commander/RasUnsteady.py +37 -22
- ras_commander/RasUtils.py +98 -82
- ras_commander/__init__.py +11 -13
- ras_commander/logging_config.py +80 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/METADATA +15 -11
- ras_commander-0.36.0.dist-info/RECORD +17 -0
- ras_commander-0.35.0.dist-info/RECORD +0 -15
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/top_level.txt +0 -0
ras_commander/RasHdf.py
CHANGED
@@ -24,7 +24,27 @@ Example:
|
|
24
24
|
def example_method(cls, hdf_file: h5py.File, other_args):
|
25
25
|
# Method implementation using hdf_file
|
26
26
|
|
27
|
+
This module is part of the ras-commander library and uses a centralized logging configuration.
|
27
28
|
|
29
|
+
Logging Configuration:
|
30
|
+
- The logging is set up in the logging_config.py file.
|
31
|
+
- A @log_call decorator is available to automatically log function calls.
|
32
|
+
- Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
33
|
+
- Logs are written to both console and a rotating file handler.
|
34
|
+
- The default log file is 'ras_commander.log' in the 'logs' directory.
|
35
|
+
- The default log level is INFO.
|
36
|
+
|
37
|
+
To use logging in this module:
|
38
|
+
1. Use the @log_call decorator for automatic function call logging.
|
39
|
+
2. For additional logging, use logger.[level]() calls (e.g., logger.info(), logger.debug()).
|
40
|
+
3. Obtain the logger using: logger = logging.getLogger(__name__)
|
41
|
+
|
42
|
+
Example:
|
43
|
+
@log_call
|
44
|
+
def my_function():
|
45
|
+
logger = logging.getLogger(__name__)
|
46
|
+
logger.debug("Additional debug information")
|
47
|
+
# Function logic here
|
28
48
|
"""
|
29
49
|
import h5py
|
30
50
|
import numpy as np
|
@@ -41,14 +61,10 @@ from .RasPrj import RasPrj, ras, init_ras_project
|
|
41
61
|
from typing import TYPE_CHECKING
|
42
62
|
if TYPE_CHECKING:
|
43
63
|
from .RasPrj import RasPrj
|
64
|
+
from ras_commander import get_logger
|
65
|
+
from ras_commander.logging_config import log_call
|
44
66
|
|
45
|
-
|
46
|
-
logging.basicConfig(
|
47
|
-
level=logging.INFO,
|
48
|
-
format='%(asctime)s - %(levelname)s - %(message)s',
|
49
|
-
handlers=[
|
50
|
-
logging.StreamHandler()
|
51
|
-
])
|
67
|
+
logger = get_logger(__name__)
|
52
68
|
|
53
69
|
class RasHdf:
|
54
70
|
"""
|
@@ -58,53 +74,51 @@ class RasHdf:
|
|
58
74
|
including listing paths, extracting data, and performing analyses on
|
59
75
|
HEC-RAS project data stored in HDF format.
|
60
76
|
"""
|
61
|
-
|
77
|
+
|
78
|
+
|
62
79
|
@staticmethod
|
63
80
|
def hdf_operation(func):
|
81
|
+
"""
|
82
|
+
A decorator for HDF file operations in the RasHdf class.
|
83
|
+
|
84
|
+
This decorator wraps methods that perform operations on HDF files. It handles:
|
85
|
+
1. Resolving the HDF filename from various input types.
|
86
|
+
2. Opening and closing the HDF file.
|
87
|
+
3. Error handling and logging.
|
88
|
+
4. Applying the decorated function as a class method.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
func (Callable): The function to be decorated.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Callable: A wrapped version of the input function as a class method.
|
95
|
+
|
96
|
+
Raises:
|
97
|
+
ValueError: If the HDF file is not found.
|
98
|
+
|
99
|
+
Usage:
|
100
|
+
@RasHdf.hdf_operation
|
101
|
+
def some_hdf_method(cls, hdf_file, ...):
|
102
|
+
# Method implementation
|
103
|
+
"""
|
64
104
|
@wraps(func)
|
65
105
|
def wrapper(cls, hdf_input: Union[str, Path], *args: Any, **kwargs: Any) -> Any:
|
66
106
|
from ras_commander import ras # Import here to avoid circular import
|
67
107
|
ras_obj = kwargs.pop('ras_object', None) or ras
|
68
108
|
try:
|
69
109
|
hdf_filename = cls._get_hdf_filename(hdf_input, ras_obj)
|
110
|
+
if hdf_filename is None:
|
111
|
+
raise ValueError(f"HDF file {hdf_input} not found. Use a try-except block to catch this error.")
|
70
112
|
with h5py.File(hdf_filename, 'r') as hdf_file:
|
71
113
|
return func(cls, hdf_file, *args, **kwargs)
|
72
114
|
except Exception as e:
|
73
|
-
|
115
|
+
logger.error(f"Error in {func.__name__}: {e}")
|
74
116
|
return None
|
75
117
|
return classmethod(wrapper)
|
76
|
-
|
77
|
-
@classmethod
|
78
|
-
def get_hdf_paths_with_properties(cls, hdf_input: Union[str, Path], ras_object=None) -> pd.DataFrame:
|
79
|
-
"""
|
80
|
-
List all paths in the HDF file with their properties.
|
81
118
|
|
82
|
-
Args:
|
83
|
-
hdf_input (Union[str, Path]): The plan number or full path to the HDF file.
|
84
|
-
ras_object (RasPrj, optional): The RAS project object. If None, uses the global ras instance.
|
85
|
-
|
86
|
-
Returns:
|
87
|
-
pd.DataFrame: DataFrame of all paths and their properties in the HDF file.
|
88
|
-
|
89
|
-
Example:
|
90
|
-
>>> paths_df = RasHdf.get_hdf_paths_with_properties("path/to/file.hdf")
|
91
|
-
>>> print(paths_df.head())
|
92
|
-
"""
|
93
|
-
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
94
|
-
paths = []
|
95
|
-
def visitor_func(name: str, node: h5py.Group) -> None:
|
96
|
-
path_info = {
|
97
|
-
"HDF_Path": name,
|
98
|
-
"Type": type(node).__name__,
|
99
|
-
"Shape": getattr(node, "shape", None),
|
100
|
-
"Size": getattr(node, "size", None),
|
101
|
-
"Dtype": getattr(node, "dtype", None)
|
102
|
-
}
|
103
|
-
paths.append(path_info)
|
104
|
-
hdf_file.visititems(visitor_func)
|
105
|
-
return pd.DataFrame(paths)
|
106
119
|
|
107
120
|
@classmethod
|
121
|
+
@log_call
|
108
122
|
def get_runtime_data(cls, hdf_input: Union[str, Path], ras_object=None) -> Optional[pd.DataFrame]:
|
109
123
|
"""
|
110
124
|
Extract runtime and compute time data from a single HDF file.
|
@@ -122,15 +136,15 @@ class RasHdf:
|
|
122
136
|
... print(runtime_df.head())
|
123
137
|
"""
|
124
138
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
125
|
-
|
139
|
+
logger.info(f"Extracting Plan Information from: {Path(hdf_file.filename).name}")
|
126
140
|
plan_info = hdf_file.get('/Plan Data/Plan Information')
|
127
141
|
if plan_info is None:
|
128
|
-
|
142
|
+
logger.warning("Group '/Plan Data/Plan Information' not found.")
|
129
143
|
return None
|
130
144
|
|
131
145
|
plan_name = plan_info.attrs.get('Plan Name', 'Unknown')
|
132
146
|
plan_name = plan_name.decode('utf-8') if isinstance(plan_name, bytes) else plan_name
|
133
|
-
|
147
|
+
logger.info(f"Plan Name: {plan_name}")
|
134
148
|
|
135
149
|
start_time_str = plan_info.attrs.get('Simulation Start Time', 'Unknown')
|
136
150
|
end_time_str = plan_info.attrs.get('Simulation End Time', 'Unknown')
|
@@ -142,13 +156,13 @@ class RasHdf:
|
|
142
156
|
simulation_duration = end_time - start_time
|
143
157
|
simulation_hours = simulation_duration.total_seconds() / 3600
|
144
158
|
|
145
|
-
|
146
|
-
|
147
|
-
|
159
|
+
logger.info(f"Simulation Start Time: {start_time_str}")
|
160
|
+
logger.info(f"Simulation End Time: {end_time_str}")
|
161
|
+
logger.info(f"Simulation Duration (hours): {simulation_hours}")
|
148
162
|
|
149
163
|
compute_processes = hdf_file.get('/Results/Summary/Compute Processes')
|
150
164
|
if compute_processes is None:
|
151
|
-
|
165
|
+
logger.warning("Dataset '/Results/Summary/Compute Processes' not found.")
|
152
166
|
return None
|
153
167
|
|
154
168
|
process_names = [name.decode('utf-8') for name in compute_processes['Process'][:]]
|
@@ -163,8 +177,8 @@ class RasHdf:
|
|
163
177
|
'Compute Time (hours)': completion_times / (1000 * 3600)
|
164
178
|
})
|
165
179
|
|
166
|
-
|
167
|
-
|
180
|
+
logger.debug("Compute processes DataFrame:")
|
181
|
+
logger.debug(compute_processes_df)
|
168
182
|
|
169
183
|
compute_processes_summary = {
|
170
184
|
'Plan Name': [plan_name],
|
@@ -184,15 +198,15 @@ class RasHdf:
|
|
184
198
|
compute_processes_summary['Complete Process Speed (hr/hr)'] = [simulation_hours / compute_processes_summary['Complete Process (hr)'][0] if compute_processes_summary['Complete Process (hr)'][0] != 'N/A' else 'N/A']
|
185
199
|
|
186
200
|
compute_summary_df = pd.DataFrame(compute_processes_summary)
|
187
|
-
|
188
|
-
|
201
|
+
logger.debug("Compute summary DataFrame:")
|
202
|
+
logger.debug(compute_summary_df)
|
189
203
|
|
190
204
|
return compute_summary_df
|
191
205
|
|
192
206
|
# List 2D Flow Area Groups (needed for later functions that extract specific datasets)
|
193
207
|
|
194
208
|
@classmethod
|
195
|
-
@
|
209
|
+
@log_call
|
196
210
|
def get_2d_flow_area_names(cls, hdf_input: Union[str, Path], ras_object=None) -> Optional[List[str]]:
|
197
211
|
"""
|
198
212
|
List 2D Flow Area names from the HDF file.
|
@@ -212,16 +226,15 @@ class RasHdf:
|
|
212
226
|
group = hdf_file['Geometry/2D Flow Areas']
|
213
227
|
group_names = [name for name in group.keys() if isinstance(group[name], h5py.Group)]
|
214
228
|
if not group_names:
|
215
|
-
|
229
|
+
logger.warning("No 2D Flow Areas found in the HDF file")
|
216
230
|
return None
|
217
|
-
|
231
|
+
logger.info(f"Found {len(group_names)} 2D Flow Areas")
|
218
232
|
return group_names
|
219
233
|
else:
|
220
|
-
|
234
|
+
logger.warning("No 2D Flow Areas found in the HDF file")
|
221
235
|
return None
|
222
|
-
|
223
236
|
@classmethod
|
224
|
-
@
|
237
|
+
@log_call
|
225
238
|
def get_2d_flow_area_attributes(cls, hdf_input: Union[str, Path], ras_object=None) -> Optional[pd.DataFrame]:
|
226
239
|
"""
|
227
240
|
Extract 2D Flow Area Attributes from the HDF file.
|
@@ -244,14 +257,12 @@ class RasHdf:
|
|
244
257
|
if 'Geometry/2D Flow Areas/Attributes' in hdf_file:
|
245
258
|
attributes = hdf_file['Geometry/2D Flow Areas/Attributes'][()]
|
246
259
|
attributes_df = pd.DataFrame(attributes)
|
247
|
-
logging.info(f"Extracted 2D Flow Area attributes: {attributes_df.shape[0]} rows, {attributes_df.shape[1]} columns")
|
248
260
|
return attributes_df
|
249
261
|
else:
|
250
|
-
logging.warning("No 2D Flow Area attributes found in the HDF file")
|
251
262
|
return None
|
252
263
|
|
253
264
|
@classmethod
|
254
|
-
@
|
265
|
+
@log_call
|
255
266
|
def get_cell_info(cls, hdf_input: Union[str, Path], ras_object=None) -> Optional[pd.DataFrame]:
|
256
267
|
"""
|
257
268
|
Extract Cell Info from the HDF file.
|
@@ -272,14 +283,10 @@ class RasHdf:
|
|
272
283
|
"""
|
273
284
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
274
285
|
cell_info_df = cls._extract_dataset(hdf_file, 'Geometry/2D Flow Areas/Cell Info', ['Start', 'End'])
|
275
|
-
if cell_info_df is not None:
|
276
|
-
logging.info(f"Extracted Cell Info: {cell_info_df.shape[0]} rows, {cell_info_df.shape[1]} columns")
|
277
|
-
else:
|
278
|
-
logging.warning("No Cell Info found in the HDF file")
|
279
286
|
return cell_info_df
|
280
287
|
|
281
288
|
@classmethod
|
282
|
-
@
|
289
|
+
@log_call
|
283
290
|
def get_cell_points(cls, hdf_input: Union[str, Path], ras_object=None) -> Optional[pd.DataFrame]:
|
284
291
|
"""
|
285
292
|
Extract Cell Points from the HDF file.
|
@@ -300,14 +307,10 @@ class RasHdf:
|
|
300
307
|
"""
|
301
308
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
302
309
|
cell_points_df = cls._extract_dataset(hdf_file, 'Geometry/2D Flow Areas/Cell Points', ['X', 'Y'])
|
303
|
-
if cell_points_df is not None:
|
304
|
-
logging.info(f"Extracted Cell Points: {cell_points_df.shape[0]} rows, {cell_points_df.shape[1]} columns")
|
305
|
-
else:
|
306
|
-
logging.warning("No Cell Points found in the HDF file")
|
307
310
|
return cell_points_df
|
308
311
|
|
309
312
|
@classmethod
|
310
|
-
@
|
313
|
+
@log_call
|
311
314
|
def get_polygon_info_and_parts(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Tuple[Optional[pd.DataFrame], Optional[pd.DataFrame]]:
|
312
315
|
"""
|
313
316
|
Extract Polygon Info and Parts from the HDF file.
|
@@ -334,24 +337,14 @@ class RasHdf:
|
|
334
337
|
... print("Polygon data not found")
|
335
338
|
"""
|
336
339
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
337
|
-
# Retrieve the area name, defaulting to the first found if not provided
|
338
340
|
area_name = cls._get_area_name(hdf_file, area_name, hdf_file.filename)
|
339
|
-
|
340
|
-
# Construct the base path for dataset extraction
|
341
341
|
base_path = f'Geometry/2D Flow Areas'
|
342
|
-
|
343
|
-
# Extract Polygon Info and Parts datasets
|
344
342
|
polygon_info_df = cls._extract_dataset(hdf_file, f'{base_path}/Polygon Info', ['Column1', 'Column2', 'Column3', 'Column4'])
|
345
343
|
polygon_parts_df = cls._extract_dataset(hdf_file, f'{base_path}/Polygon Parts', ['Start', 'Count'])
|
346
|
-
|
347
|
-
# Log warnings if no data is found
|
348
|
-
if polygon_info_df is None and polygon_parts_df is None:
|
349
|
-
logging.warning(f"No Polygon Info or Parts found for 2D Flow Area: {area_name}")
|
350
|
-
|
351
344
|
return polygon_info_df, polygon_parts_df
|
352
345
|
|
353
346
|
@classmethod
|
354
|
-
@
|
347
|
+
@log_call
|
355
348
|
def get_polygon_points(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Optional[pd.DataFrame]:
|
356
349
|
"""
|
357
350
|
Extract Polygon Points from the HDF file.
|
@@ -367,19 +360,16 @@ class RasHdf:
|
|
367
360
|
"""
|
368
361
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
369
362
|
area_name = cls._get_area_name(hdf_file, area_name, hdf_file.filename)
|
370
|
-
# This path does not include the area name
|
371
363
|
polygon_points_path = f'Geometry/2D Flow Areas/Polygon Points'
|
372
364
|
if polygon_points_path in hdf_file:
|
373
365
|
polygon_points = hdf_file[polygon_points_path][()]
|
374
366
|
polygon_points_df = pd.DataFrame(polygon_points, columns=['X', 'Y'])
|
375
|
-
logging.info(f"Extracted Polygon Points for 2D Flow Area {area_name}: {polygon_points_df.shape[0]} rows, {polygon_points_df.shape[1]} columns")
|
376
367
|
return polygon_points_df
|
377
368
|
else:
|
378
|
-
logging.warning(f"No Polygon Points found for 2D Flow Area: {area_name}")
|
379
369
|
return None
|
380
370
|
|
381
371
|
@classmethod
|
382
|
-
@
|
372
|
+
@log_call
|
383
373
|
def get_cells_center_data(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Tuple[Optional[pd.DataFrame], Optional[pd.DataFrame]]:
|
384
374
|
"""
|
385
375
|
Extract Cells Center Coordinates and Manning's n from the HDF file.
|
@@ -405,45 +395,21 @@ class RasHdf:
|
|
405
395
|
... else:
|
406
396
|
... print("Cell center data not found")
|
407
397
|
"""
|
408
|
-
logging.info(f"Entering get_cells_center_data method")
|
409
|
-
logging.info(f"Input parameters: hdf_input={hdf_input}, area_name={area_name}")
|
410
|
-
|
411
398
|
try:
|
412
399
|
hdf_filename = cls._get_hdf_filename(hdf_input, ras_object)
|
413
|
-
logging.info(f"HDF filename: {hdf_filename}")
|
414
|
-
|
415
400
|
with h5py.File(hdf_filename, 'r') as hdf_file:
|
416
|
-
logging.info(f"Successfully opened HDF file: {hdf_filename}")
|
417
|
-
|
418
|
-
logging.info(f"Getting Cells Center Data for 2D Flow Area: {area_name}")
|
419
401
|
area_name = cls._get_area_name(hdf_file, area_name, hdf_file.filename)
|
420
|
-
logging.info(f"Area Name: {area_name}")
|
421
|
-
|
422
402
|
base_path = f'Geometry/2D Flow Areas/{area_name}'
|
423
403
|
cells_center_coord_path = f'{base_path}/Cells Center Coordinate'
|
424
404
|
cells_manning_n_path = f'{base_path}/Cells Center Manning\'s n'
|
425
|
-
|
426
|
-
logging.info(f"Extracting dataset from path: {cells_center_coord_path}")
|
427
405
|
cells_center_coord_df = cls._extract_dataset(hdf_file, cells_center_coord_path, ['X', 'Y'])
|
428
|
-
|
429
|
-
logging.info(f"Extracting dataset from path: {cells_manning_n_path}")
|
430
406
|
cells_manning_n_df = cls._extract_dataset(hdf_file, cells_manning_n_path, ['Manning\'s n'])
|
431
|
-
|
432
|
-
if cells_center_coord_df is not None and cells_manning_n_df is not None:
|
433
|
-
logging.info(f"Extracted Cells Center Data for 2D Flow Area: {area_name}")
|
434
|
-
logging.info(f"Cells Center Coordinates shape: {cells_center_coord_df.shape}, dtype: {cells_center_coord_df.dtypes}")
|
435
|
-
logging.info(f"Cells Manning's n shape: {cells_manning_n_df.shape}, dtype: {cells_manning_n_df.dtypes}")
|
436
|
-
else:
|
437
|
-
logging.warning(f"Cells Center Data not found for 2D Flow Area: {area_name}")
|
438
|
-
|
439
407
|
return cells_center_coord_df, cells_manning_n_df
|
440
|
-
|
441
408
|
except Exception as e:
|
442
|
-
logging.error(f"Error in get_cells_center_data: {str(e)}", exc_info=True)
|
443
409
|
return None, None
|
444
410
|
|
445
411
|
@classmethod
|
446
|
-
@
|
412
|
+
@log_call
|
447
413
|
def get_faces_area_elevation_data(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Optional[pd.DataFrame]:
|
448
414
|
"""
|
449
415
|
Extract Faces Area Elevation Values from the HDF file.
|
@@ -472,17 +438,12 @@ class RasHdf:
|
|
472
438
|
if area_elev_values_path in hdf_file:
|
473
439
|
area_elev_values = hdf_file[area_elev_values_path][()]
|
474
440
|
area_elev_values_df = pd.DataFrame(area_elev_values, columns=['Elevation', 'Area', 'Wetted Perimeter', 'Manning\'s n'])
|
475
|
-
|
476
|
-
logging.info(f"Extracted Faces Area Elevation Values for 2D Flow Area: {area_name}")
|
477
|
-
logging.info(f"Faces Area Elevation Values shape: {area_elev_values.shape}, dtype: {area_elev_values.dtype}")
|
478
|
-
|
479
441
|
return area_elev_values_df
|
480
442
|
else:
|
481
|
-
logging.warning(f"Faces Area Elevation Values not found for 2D Flow Area: {area_name}")
|
482
443
|
return None
|
483
444
|
|
484
445
|
@classmethod
|
485
|
-
@
|
446
|
+
@log_call
|
486
447
|
def get_faces_indexes(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Tuple[Optional[pd.DataFrame], Optional[pd.DataFrame]]:
|
487
448
|
"""
|
488
449
|
Extract Faces Cell and FacePoint Indexes from the HDF file.
|
@@ -518,17 +479,10 @@ class RasHdf:
|
|
518
479
|
cell_indexes_df = cls._extract_dataset(hdf_file, cell_indexes_path, ['Left Cell', 'Right Cell'])
|
519
480
|
facepoint_indexes_df = cls._extract_dataset(hdf_file, facepoint_indexes_path, ['Start FacePoint', 'End FacePoint'])
|
520
481
|
|
521
|
-
if cell_indexes_df is not None and facepoint_indexes_df is not None:
|
522
|
-
logging.info(f"Extracted Faces Indexes for 2D Flow Area: {area_name}")
|
523
|
-
else:
|
524
|
-
logging.warning(f"Faces Indexes not found for 2D Flow Area: {area_name}")
|
525
|
-
|
526
482
|
return cell_indexes_df, facepoint_indexes_df
|
527
483
|
|
528
|
-
|
529
|
-
|
530
484
|
@classmethod
|
531
|
-
@
|
485
|
+
@log_call
|
532
486
|
def get_faces_elevation_data(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Tuple[Optional[pd.DataFrame], Optional[pd.DataFrame]]:
|
533
487
|
"""
|
534
488
|
Extract Faces Low Elevation Centroid and Minimum Elevation from the HDF file.
|
@@ -550,15 +504,10 @@ class RasHdf:
|
|
550
504
|
low_elev_centroid = cls._extract_dataset(hdf_file, f'{base_path}/Faces Low Elevation Centroid', ['Low Elevation Centroid'])
|
551
505
|
min_elevation = cls._extract_dataset(hdf_file, f'{base_path}/Faces Minimum Elevation', ['Minimum Elevation'])
|
552
506
|
|
553
|
-
if low_elev_centroid is not None and min_elevation is not None:
|
554
|
-
logging.info(f"Extracted Faces Elevation Data for 2D Flow Area: {area_name}")
|
555
|
-
else:
|
556
|
-
logging.warning(f"Faces Elevation Data not found for 2D Flow Area: {area_name}")
|
557
|
-
|
558
507
|
return low_elev_centroid, min_elevation
|
559
508
|
|
560
509
|
@classmethod
|
561
|
-
@
|
510
|
+
@log_call
|
562
511
|
def get_faces_vector_data(
|
563
512
|
cls,
|
564
513
|
hdf_input: Union[str, Path],
|
@@ -583,15 +532,10 @@ class RasHdf:
|
|
583
532
|
base_path = f'Geometry/2D Flow Areas/{area_name}'
|
584
533
|
vector_data = cls._extract_dataset(hdf_file, f'{base_path}/Faces NormalUnitVector and Length', ['NormalX', 'NormalY', 'Length'])
|
585
534
|
|
586
|
-
if vector_data is not None:
|
587
|
-
logging.info(f"Extracted Faces Vector Data for 2D Flow Area: {area_name}")
|
588
|
-
else:
|
589
|
-
logging.warning(f"Faces Vector Data not found for 2D Flow Area: {area_name}")
|
590
|
-
|
591
535
|
return vector_data
|
592
536
|
|
593
537
|
@classmethod
|
594
|
-
@
|
538
|
+
@log_call
|
595
539
|
def get_faces_perimeter_data(
|
596
540
|
cls,
|
597
541
|
hdf_input: Union[str, Path],
|
@@ -632,15 +576,10 @@ class RasHdf:
|
|
632
576
|
perimeter_info = cls._extract_dataset(hdf_file, f'{base_path}/Faces Perimeter Info', ['Start', 'Count'])
|
633
577
|
perimeter_values = cls._extract_dataset(hdf_file, f'{base_path}/Faces Perimeter Values', ['X', 'Y'])
|
634
578
|
|
635
|
-
if perimeter_info is not None and perimeter_values is not None:
|
636
|
-
logging.info(f"Extracted Faces Perimeter Data for 2D Flow Area: {area_name}")
|
637
|
-
else:
|
638
|
-
logging.warning(f"Faces Perimeter Data not found for 2D Flow Area: {area_name}")
|
639
|
-
|
640
579
|
return perimeter_info, perimeter_values
|
641
580
|
|
642
581
|
@classmethod
|
643
|
-
@
|
582
|
+
@log_call
|
644
583
|
def get_infiltration_data(
|
645
584
|
cls,
|
646
585
|
hdf_input: Union[str, Path],
|
@@ -661,29 +600,20 @@ class RasHdf:
|
|
661
600
|
DataFrames containing various Infiltration Data
|
662
601
|
"""
|
663
602
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
664
|
-
# Retrieve the area name from the HDF file or use the first found
|
665
603
|
area_name = cls._get_area_name(hdf_file, area_name, hdf_file.filename)
|
666
604
|
|
667
|
-
# Define the base path for the Infiltration data
|
668
605
|
base_path = f'Geometry/2D Flow Areas/{area_name}/Infiltration'
|
669
606
|
|
670
|
-
# Extract various datasets related to infiltration
|
671
607
|
cell_classifications = cls._extract_dataset(hdf_file, f'{base_path}/Cell Center Classifications', ['Cell Classification'])
|
672
608
|
face_classifications = cls._extract_dataset(hdf_file, f'{base_path}/Face Center Classifications', ['Face Classification'])
|
673
609
|
initial_deficit = cls._extract_dataset(hdf_file, f'{base_path}/Initial Deficit', ['Initial Deficit'])
|
674
610
|
maximum_deficit = cls._extract_dataset(hdf_file, f'{base_path}/Maximum Deficit', ['Maximum Deficit'])
|
675
611
|
potential_percolation_rate = cls._extract_dataset(hdf_file, f'{base_path}/Potential Percolation Rate', ['Potential Percolation Rate'])
|
676
612
|
|
677
|
-
# Log the extraction status
|
678
|
-
if all(df is not None for df in [cell_classifications, face_classifications, initial_deficit, maximum_deficit, potential_percolation_rate]):
|
679
|
-
logging.info(f"Extracted Infiltration Data for 2D Flow Area: {area_name}")
|
680
|
-
else:
|
681
|
-
logging.warning(f"Some or all Infiltration Data not found for 2D Flow Area: {area_name}")
|
682
|
-
|
683
613
|
return cell_classifications, face_classifications, initial_deficit, maximum_deficit, potential_percolation_rate
|
684
614
|
|
685
615
|
@classmethod
|
686
|
-
@
|
616
|
+
@log_call
|
687
617
|
def get_percent_impervious_data(
|
688
618
|
cls,
|
689
619
|
hdf_input: Union[str, Path],
|
@@ -711,15 +641,10 @@ class RasHdf:
|
|
711
641
|
face_classifications = cls._extract_dataset(hdf_file, f'{base_path}/Face Center Classifications', ['Face Classification'])
|
712
642
|
percent_impervious = cls._extract_dataset(hdf_file, f'{base_path}/Percent Impervious', ['Percent Impervious'])
|
713
643
|
|
714
|
-
if all([df is not None for df in [cell_classifications, face_classifications, percent_impervious]]):
|
715
|
-
logging.info(f"Extracted Percent Impervious Data for 2D Flow Area: {area_name}")
|
716
|
-
else:
|
717
|
-
logging.warning(f"Some or all Percent Impervious Data not found for 2D Flow Area: {area_name}")
|
718
|
-
|
719
644
|
return cell_classifications, face_classifications, percent_impervious
|
720
645
|
|
721
646
|
@classmethod
|
722
|
-
@
|
647
|
+
@log_call
|
723
648
|
def get_perimeter_data(
|
724
649
|
cls,
|
725
650
|
hdf_input: Union[str, Path],
|
@@ -751,17 +676,10 @@ class RasHdf:
|
|
751
676
|
perimeter_path = f'Geometry/2D Flow Areas/{area_name}/Perimeter'
|
752
677
|
perimeter_df = cls._extract_dataset(hdf_file, perimeter_path, ['X', 'Y'])
|
753
678
|
|
754
|
-
if perimeter_df is not None:
|
755
|
-
logging.info(f"Extracted Perimeter Data for 2D Flow Area: {area_name}")
|
756
|
-
else:
|
757
|
-
logging.warning(f"Perimeter Data not found for 2D Flow Area: {area_name}")
|
758
|
-
|
759
679
|
return perimeter_df
|
760
680
|
|
761
|
-
# Private Class Methods (to save code duplication)
|
762
|
-
|
763
|
-
|
764
681
|
@classmethod
|
682
|
+
@log_call
|
765
683
|
def _get_area_name(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> str:
|
766
684
|
"""
|
767
685
|
Get the 2D Flow Area name from the HDF file.
|
@@ -783,14 +701,13 @@ class RasHdf:
|
|
783
701
|
if not area_names:
|
784
702
|
raise ValueError("No 2D Flow Areas found in the HDF file")
|
785
703
|
area_name = area_names[0]
|
786
|
-
logging.info(f"Using first 2D Flow Area found: {area_name}")
|
787
704
|
else:
|
788
705
|
if area_name not in hdf_file['Geometry/2D Flow Areas']:
|
789
706
|
raise ValueError(f"2D Flow Area '{area_name}' not found in the HDF file")
|
790
|
-
logging.info(f"Using 2D Flow Area provided by user: {area_name}")
|
791
707
|
return area_name
|
792
708
|
|
793
709
|
@classmethod
|
710
|
+
@log_call
|
794
711
|
def _extract_dataset(cls, hdf_input: Union[str, Path], dataset_path: str, column_names: List[str], ras_object=None) -> Optional[pd.DataFrame]:
|
795
712
|
"""
|
796
713
|
Extract a dataset from the HDF file and convert it to a DataFrame.
|
@@ -808,13 +725,12 @@ class RasHdf:
|
|
808
725
|
try:
|
809
726
|
dataset = hdf_file[dataset_path][()]
|
810
727
|
df = pd.DataFrame(dataset, columns=column_names)
|
811
|
-
logging.info(f"Extracted dataset: {dataset_path}")
|
812
728
|
return df
|
813
729
|
except KeyError:
|
814
|
-
logging.warning(f"Dataset not found: {dataset_path}")
|
815
730
|
return None
|
731
|
+
|
816
732
|
@classmethod
|
817
|
-
@
|
733
|
+
@log_call
|
818
734
|
def read_hdf_to_dataframe(cls, hdf_input: Union[str, Path], dataset_path: str, fill_value: Union[int, float, str] = -9999, ras_object=None) -> pd.DataFrame:
|
819
735
|
"""
|
820
736
|
Reads an HDF5 dataset and converts it into a pandas DataFrame, handling byte strings and missing values.
|
@@ -840,14 +756,12 @@ class RasHdf:
|
|
840
756
|
hdf_dataframe[byte_columns] = hdf_dataframe[byte_columns].applymap(lambda x: x.decode('utf-8') if isinstance(x, (bytes, bytearray)) else x)
|
841
757
|
hdf_dataframe = hdf_dataframe.replace({fill_value: np.NaN})
|
842
758
|
|
843
|
-
logging.info(f"Successfully read dataset: {dataset_path}")
|
844
759
|
return hdf_dataframe
|
845
760
|
except KeyError:
|
846
|
-
logging.error(f"Dataset not found: {dataset_path}")
|
847
761
|
raise
|
848
762
|
|
849
763
|
@classmethod
|
850
|
-
@
|
764
|
+
@log_call
|
851
765
|
def get_group_attributes_as_df(cls, hdf_input: Union[str, Path], group_path: str, ras_object=None) -> pd.DataFrame:
|
852
766
|
"""
|
853
767
|
Convert attributes inside a given HDF group to a DataFrame.
|
@@ -894,14 +808,14 @@ class RasHdf:
|
|
894
808
|
|
895
809
|
return pd.DataFrame(attributes)
|
896
810
|
except KeyError:
|
897
|
-
|
898
|
-
raise
|
811
|
+
logger.critical(f"Group path '{group_path}' not found in HDF file '{hdf_filename}'")
|
899
812
|
|
900
813
|
# Last functions from PyHMT2D:
|
901
814
|
|
815
|
+
from ras_commander.logging_config import log_call
|
902
816
|
|
903
817
|
@classmethod
|
904
|
-
@
|
818
|
+
@log_call
|
905
819
|
def get_2d_area_solution_times(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Optional[np.ndarray]:
|
906
820
|
"""
|
907
821
|
Retrieve solution times for a specified 2D Flow Area.
|
@@ -925,13 +839,12 @@ class RasHdf:
|
|
925
839
|
hdf_file['Results']['Unsteady']['Output']['Output Blocks']
|
926
840
|
['Base Output']['Unsteady Time Series']['Time']
|
927
841
|
)
|
928
|
-
logging.info(f"Retrieved {len(solution_times)} solution times for 2D Flow Area: {area_name}")
|
929
842
|
return solution_times
|
930
843
|
except KeyError:
|
931
|
-
logging.warning(f"Solution times not found for 2D Flow Area: {area_name}")
|
932
844
|
return None
|
845
|
+
|
933
846
|
@classmethod
|
934
|
-
@
|
847
|
+
@log_call
|
935
848
|
def get_2d_area_solution_time_dates(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Optional[np.ndarray]:
|
936
849
|
"""
|
937
850
|
Retrieve solution time dates for a specified 2D Flow Area.
|
@@ -955,14 +868,12 @@ class RasHdf:
|
|
955
868
|
hdf_file['Results']['Unsteady']['Output']['Output Blocks']
|
956
869
|
['Base Output']['Unsteady Time Series']['Time Date Stamp']
|
957
870
|
)
|
958
|
-
logging.info(f"Retrieved {len(solution_time_dates)} solution time dates for 2D Flow Area: {area_name}")
|
959
871
|
return solution_time_dates
|
960
872
|
except KeyError:
|
961
|
-
logging.warning(f"Solution time dates not found for 2D Flow Area: {area_name}")
|
962
873
|
return None
|
963
874
|
|
964
875
|
@classmethod
|
965
|
-
@
|
876
|
+
@log_call
|
966
877
|
def load_2d_area_solutions(
|
967
878
|
cls,
|
968
879
|
hdf_file: h5py.File,
|
@@ -988,51 +899,37 @@ class RasHdf:
|
|
988
899
|
- '{Area_Name}_Face_Velocity': Face Normal Velocity DataFrame.
|
989
900
|
"""
|
990
901
|
try:
|
991
|
-
# Extract solution times
|
992
902
|
solution_times_path = '/Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/Time'
|
993
903
|
if solution_times_path not in hdf_file:
|
994
|
-
logging.error(f"Solution times dataset not found at path: {solution_times_path}")
|
995
904
|
return None
|
996
905
|
|
997
906
|
solution_times = hdf_file[solution_times_path][()]
|
998
907
|
solution_times_df = pd.DataFrame({
|
999
908
|
'Time_Step': solution_times
|
1000
909
|
})
|
1001
|
-
logging.info(f"Extracted Solution Times: {solution_times_df.shape[0]} time steps")
|
1002
910
|
|
1003
|
-
# Initialize dictionary to hold all dataframes
|
1004
911
|
solutions_dict = {
|
1005
912
|
'solution_times': solution_times_df
|
1006
913
|
}
|
1007
914
|
|
1008
|
-
# Get list of 2D Flow Areas
|
1009
915
|
two_d_area_names = cls.get_2d_flow_area_names(hdf_file, ras_object=ras_object)
|
1010
916
|
if not two_d_area_names:
|
1011
|
-
logging.error("No 2D Flow Areas found in the HDF file.")
|
1012
917
|
return solutions_dict
|
1013
918
|
|
1014
919
|
for area in two_d_area_names:
|
1015
|
-
logging.info(f"Processing 2D Flow Area: {area}")
|
1016
|
-
|
1017
|
-
# Paths for WSE and Face Velocity datasets
|
1018
920
|
wse_path = f'/Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/2D Flow Areas/{area}/Water Surface'
|
1019
921
|
face_velocity_path = f'/Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/2D Flow Areas/{area}/Face Velocity'
|
1020
922
|
|
1021
|
-
# Extract Water Surface Elevation (WSE)
|
1022
923
|
if wse_path not in hdf_file:
|
1023
|
-
logging.warning(f"WSE dataset not found for area '{area}' at path: {wse_path}")
|
1024
924
|
continue
|
1025
925
|
|
1026
926
|
wse_data = hdf_file[wse_path][()]
|
1027
|
-
# Assuming cell center coordinates are required for WSE
|
1028
927
|
cell_center_coords_path = f'/Geometry/2D Flow Areas/{area}/Cell Center Coordinate'
|
1029
928
|
if cell_center_coords_path not in hdf_file:
|
1030
|
-
logging.warning(f"Cell Center Coordinate dataset not found for area '{area}' at path: {cell_center_coords_path}")
|
1031
929
|
continue
|
1032
930
|
|
1033
931
|
cell_center_coords = hdf_file[cell_center_coords_path][()]
|
1034
932
|
if cell_center_coords.shape[0] != wse_data.shape[1]:
|
1035
|
-
logging.warning(f"Mismatch between Cell Center Coordinates and WSE data for area '{area}'.")
|
1036
933
|
continue
|
1037
934
|
|
1038
935
|
wse_df = pd.DataFrame({
|
@@ -1043,23 +940,17 @@ class RasHdf:
|
|
1043
940
|
'WSE': wse_data.flatten()
|
1044
941
|
})
|
1045
942
|
solutions_dict[f'{area}_WSE'] = wse_df
|
1046
|
-
logging.info(f"Extracted WSE for area '{area}': {wse_df.shape[0]} records")
|
1047
943
|
|
1048
|
-
# Extract Face Normal Velocity
|
1049
944
|
if face_velocity_path not in hdf_file:
|
1050
|
-
logging.warning(f"Face Velocity dataset not found for area '{area}' at path: {face_velocity_path}")
|
1051
945
|
continue
|
1052
946
|
|
1053
947
|
face_velocity_data = hdf_file[face_velocity_path][()]
|
1054
|
-
# Assuming face center points are required for velocities
|
1055
948
|
face_center_coords_path = f'/Geometry/2D Flow Areas/{area}/Face Points Coordinates'
|
1056
949
|
if face_center_coords_path not in hdf_file:
|
1057
|
-
logging.warning(f"Face Points Coordinates dataset not found for area '{area}' at path: {face_center_coords_path}")
|
1058
950
|
continue
|
1059
951
|
|
1060
952
|
face_center_coords = hdf_file[face_center_coords_path][()]
|
1061
953
|
if face_center_coords.shape[0] != face_velocity_data.shape[1]:
|
1062
|
-
logging.warning(f"Mismatch between Face Center Coordinates and Face Velocity data for area '{area}'.")
|
1063
954
|
continue
|
1064
955
|
|
1065
956
|
face_velocity_df = pd.DataFrame({
|
@@ -1070,17 +961,45 @@ class RasHdf:
|
|
1070
961
|
'Normal_Velocity_ft_s': face_velocity_data.flatten()
|
1071
962
|
})
|
1072
963
|
solutions_dict[f'{area}_Face_Velocity'] = face_velocity_df
|
1073
|
-
logging.info(f"Extracted Face Velocity for area '{area}': {face_velocity_df.shape[0]} records")
|
1074
964
|
|
1075
965
|
return solutions_dict
|
1076
966
|
|
1077
967
|
except Exception as e:
|
1078
|
-
logging.error(f"An error occurred while loading 2D area solutions: {e}", exc_info=True)
|
1079
968
|
return None
|
1080
969
|
|
970
|
+
@classmethod
|
971
|
+
@log_call
|
972
|
+
def get_hdf_paths_with_properties(cls, hdf_input: Union[str, Path], ras_object=None) -> pd.DataFrame:
|
973
|
+
"""
|
974
|
+
List all paths in the HDF file with their properties.
|
975
|
+
|
976
|
+
Args:
|
977
|
+
hdf_input (Union[str, Path]): The plan number or full path to the HDF file.
|
978
|
+
ras_object (RasPrj, optional): The RAS project object. If None, uses the global ras instance.
|
979
|
+
|
980
|
+
Returns:
|
981
|
+
pd.DataFrame: DataFrame of all paths and their properties in the HDF file.
|
1081
982
|
|
983
|
+
Example:
|
984
|
+
>>> paths_df = RasHdf.get_hdf_paths_with_properties("path/to/file.hdf")
|
985
|
+
>>> print(paths_df.head())
|
986
|
+
"""
|
987
|
+
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
988
|
+
paths = []
|
989
|
+
def visitor_func(name: str, node: h5py.Group) -> None:
|
990
|
+
path_info = {
|
991
|
+
"HDF_Path": name,
|
992
|
+
"Type": type(node).__name__,
|
993
|
+
"Shape": getattr(node, "shape", None),
|
994
|
+
"Size": getattr(node, "size", None),
|
995
|
+
"Dtype": getattr(node, "dtype", None)
|
996
|
+
}
|
997
|
+
paths.append(path_info)
|
998
|
+
hdf_file.visititems(visitor_func)
|
999
|
+
return pd.DataFrame(paths)
|
1000
|
+
|
1082
1001
|
@classmethod
|
1083
|
-
@
|
1002
|
+
@log_call
|
1084
1003
|
def build_2d_area_face_hydraulic_information(cls, hdf_input: Union[str, Path, h5py.File], area_name: Optional[str] = None, ras_object=None) -> Optional[List[List[np.ndarray]]]:
|
1085
1004
|
"""
|
1086
1005
|
Build face hydraulic information tables (elevation, area, wetted perimeter, Manning's n) for each face in 2D Flow Areas.
|
@@ -1115,18 +1034,16 @@ class RasHdf:
|
|
1115
1034
|
start_row, count = face
|
1116
1035
|
face_data = face_elev_values[start_row:start_row + count].copy()
|
1117
1036
|
area_hydraulic_info.append(face_data)
|
1118
|
-
logging.info(f"Processed hydraulic information for face {face} in 2D Flow Area: {area}")
|
1119
1037
|
|
1120
1038
|
hydraulic_info_table.append(area_hydraulic_info)
|
1121
1039
|
|
1122
1040
|
return hydraulic_info_table
|
1123
1041
|
|
1124
|
-
except KeyError
|
1125
|
-
logging.error(f"Error building face hydraulic information: {e}")
|
1042
|
+
except KeyError:
|
1126
1043
|
return None
|
1127
1044
|
|
1128
1045
|
@classmethod
|
1129
|
-
@
|
1046
|
+
@log_call
|
1130
1047
|
def build_2d_area_face_point_coordinates_list(cls, hdf_input: Union[str, Path, h5py.File], area_name: Optional[str] = None, ras_object=None) -> Optional[List[np.ndarray]]:
|
1131
1048
|
"""
|
1132
1049
|
Build a list of face point coordinates for each 2D Flow Area.
|
@@ -1152,16 +1069,14 @@ class RasHdf:
|
|
1152
1069
|
for area in two_d_area_names:
|
1153
1070
|
face_points = np.array(hdf_file[f'Geometry/2D Flow Areas/{area}/Face Points Coordinates'])
|
1154
1071
|
face_point_coords_list.append(face_points)
|
1155
|
-
logging.info(f"Built face point coordinates list for 2D Flow Area: {area}")
|
1156
1072
|
|
1157
1073
|
return face_point_coords_list
|
1158
1074
|
|
1159
|
-
except KeyError
|
1160
|
-
logging.error(f"Error building face point coordinates list: {e}")
|
1075
|
+
except KeyError:
|
1161
1076
|
return None
|
1162
1077
|
|
1163
1078
|
@classmethod
|
1164
|
-
@
|
1079
|
+
@log_call
|
1165
1080
|
def build_2d_area_face_profile(cls, hdf_input: Union[str, Path, h5py.File], area_name: Optional[str] = None, ras_object=None, n_face_profile_points: int = 10) -> Optional[List[np.ndarray]]:
|
1166
1081
|
"""
|
1167
1082
|
Build face profiles representing sub-grid terrain for each face in 2D Flow Areas.
|
@@ -1183,8 +1098,6 @@ class RasHdf:
|
|
1183
1098
|
try:
|
1184
1099
|
with h5py.File(cls._get_hdf_filename(hdf_input, ras_object), 'r') as hdf_file:
|
1185
1100
|
two_d_area_names = cls.get_2d_flow_area_names(hdf_file, ras_object=ras_object)
|
1186
|
-
print(f"Building face profiles for {len(two_d_area_names)} 2D Flow Areas")
|
1187
|
-
print(f"Area names: {two_d_area_names}")
|
1188
1101
|
face_profiles = []
|
1189
1102
|
|
1190
1103
|
for area in two_d_area_names:
|
@@ -1205,11 +1118,9 @@ class RasHdf:
|
|
1205
1118
|
for i in range(n_face_profile_points)
|
1206
1119
|
])
|
1207
1120
|
|
1208
|
-
# Interpolate Z coordinates (assuming a method exists)
|
1209
1121
|
interpolated_points = cls.interpolate_z_coords(interpolated_points)
|
1210
1122
|
|
1211
1123
|
profile_points_all_faces.append(interpolated_points)
|
1212
|
-
logging.info(f"Built face profile for face {face} in 2D Flow Area: {area}")
|
1213
1124
|
|
1214
1125
|
face_profiles.append(profile_points_all_faces)
|
1215
1126
|
|
@@ -1220,7 +1131,7 @@ class RasHdf:
|
|
1220
1131
|
return None
|
1221
1132
|
|
1222
1133
|
@classmethod
|
1223
|
-
@
|
1134
|
+
@log_call
|
1224
1135
|
def build_face_facepoints(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Optional[List[np.ndarray]]:
|
1225
1136
|
"""
|
1226
1137
|
Build face's facepoint list for each 2D Flow Area.
|
@@ -1246,16 +1157,16 @@ class RasHdf:
|
|
1246
1157
|
for area in two_d_area_names:
|
1247
1158
|
face_facepoints = np.array(hdf_file[f'Geometry/2D Flow Areas/{area}/Faces FacePoint Indexes'])
|
1248
1159
|
face_facepoints_list.append(face_facepoints)
|
1249
|
-
logging.info(f"Built face facepoints list for 2D Flow Area: {area}")
|
1250
1160
|
|
1251
1161
|
return face_facepoints_list
|
1252
1162
|
|
1253
1163
|
except KeyError as e:
|
1254
|
-
logging.
|
1164
|
+
logger = logging.getLogger(__name__)
|
1165
|
+
logger.error(f"Error building face facepoints list: {e}")
|
1255
1166
|
return None
|
1256
1167
|
|
1257
1168
|
@classmethod
|
1258
|
-
@
|
1169
|
+
@log_call
|
1259
1170
|
def build_2d_area_boundaries(cls, hdf_input: Union[str, Path], area_name: Optional[str] = None, ras_object=None) -> Optional[Tuple[int, np.ndarray, List[str], List[str], List[str], np.ndarray, np.ndarray]]:
|
1260
1171
|
"""
|
1261
1172
|
Build boundaries with their point lists for each 2D Flow Area.
|
@@ -1289,7 +1200,8 @@ class RasHdf:
|
|
1289
1200
|
for area in two_d_area_names:
|
1290
1201
|
boundary_points = np.array(hdf_file[f'Geometry/2D Flow Areas/{area}/Boundary Points'])
|
1291
1202
|
if boundary_points.size == 0:
|
1292
|
-
logging.
|
1203
|
+
logger = logging.getLogger(__name__)
|
1204
|
+
logger.warning(f"No boundary points found for 2D Flow Area: {area}")
|
1293
1205
|
continue
|
1294
1206
|
|
1295
1207
|
current_boundary_id = boundary_points[0][0]
|
@@ -1322,18 +1234,17 @@ class RasHdf:
|
|
1322
1234
|
boundary_points_list.append(np.array(current_boundary_points))
|
1323
1235
|
total_boundaries += 1
|
1324
1236
|
|
1325
|
-
logging.info(f"Built boundaries for 2D Flow Area: {area}, Total Boundaries: {total_boundaries}")
|
1326
|
-
|
1327
1237
|
return (total_boundaries, np.array(boundary_ids), boundary_names, flow_area_names, boundary_types, np.array(total_points_per_boundary), np.array(boundary_points_list))
|
1328
1238
|
|
1329
1239
|
except KeyError as e:
|
1330
|
-
logging.
|
1240
|
+
logger = logging.getLogger(__name__)
|
1241
|
+
logger.error(f"Error building boundaries: {e}")
|
1331
1242
|
return None
|
1332
1243
|
|
1333
1244
|
# Helper Methods for New Functionalities
|
1334
1245
|
|
1335
|
-
|
1336
1246
|
@classmethod
|
1247
|
+
@log_call
|
1337
1248
|
def horizontal_distance(cls, coord1: np.ndarray, coord2: np.ndarray) -> float:
|
1338
1249
|
"""
|
1339
1250
|
Calculate the horizontal distance between two coordinate points.
|
@@ -1353,6 +1264,7 @@ class RasHdf:
|
|
1353
1264
|
return np.linalg.norm(coord2 - coord1)
|
1354
1265
|
|
1355
1266
|
@classmethod
|
1267
|
+
@log_call
|
1356
1268
|
def interpolate_z_coords(cls, points: np.ndarray) -> np.ndarray:
|
1357
1269
|
"""
|
1358
1270
|
Interpolate Z coordinates for a set of points.
|
@@ -1373,16 +1285,9 @@ class RasHdf:
|
|
1373
1285
|
# This should be replaced with the appropriate interpolation method
|
1374
1286
|
z_coords = np.zeros((points.shape[0], 1)) # Assuming Z=0 for simplicity
|
1375
1287
|
return np.hstack((points, z_coords))
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
1288
|
|
1384
1289
|
@classmethod
|
1385
|
-
@
|
1290
|
+
@log_call
|
1386
1291
|
def extract_string_from_hdf(
|
1387
1292
|
cls,
|
1388
1293
|
hdf_input: Union[str, Path],
|
@@ -1425,10 +1330,12 @@ class RasHdf:
|
|
1425
1330
|
else:
|
1426
1331
|
return f"Unsupported object type: {type(hdf_object)}"
|
1427
1332
|
except KeyError:
|
1333
|
+
logger = logging.getLogger(__name__)
|
1334
|
+
logger.error(f"Path not found: {hdf_path}")
|
1428
1335
|
raise KeyError(f"Path not found: {hdf_path}")
|
1429
1336
|
|
1430
1337
|
@classmethod
|
1431
|
-
@
|
1338
|
+
@log_call
|
1432
1339
|
def decode_byte_strings(dataframe: pd.DataFrame) -> pd.DataFrame:
|
1433
1340
|
"""
|
1434
1341
|
Decodes byte strings in a DataFrame to regular string objects.
|
@@ -1456,7 +1363,7 @@ class RasHdf:
|
|
1456
1363
|
return dataframe
|
1457
1364
|
|
1458
1365
|
@classmethod
|
1459
|
-
@
|
1366
|
+
@log_call
|
1460
1367
|
def perform_kdtree_query(
|
1461
1368
|
reference_points: np.ndarray,
|
1462
1369
|
query_points: np.ndarray,
|
@@ -1486,7 +1393,7 @@ class RasHdf:
|
|
1486
1393
|
return snap
|
1487
1394
|
|
1488
1395
|
@classmethod
|
1489
|
-
@
|
1396
|
+
@log_call
|
1490
1397
|
def find_nearest_neighbors(points: np.ndarray, max_distance: float = 2.0) -> np.ndarray:
|
1491
1398
|
"""
|
1492
1399
|
Creates a self KDTree for dataset points and finds nearest neighbors excluding self,
|
@@ -1519,7 +1426,7 @@ class RasHdf:
|
|
1519
1426
|
return snapped
|
1520
1427
|
|
1521
1428
|
@classmethod
|
1522
|
-
@
|
1429
|
+
@log_call
|
1523
1430
|
def consolidate_dataframe(
|
1524
1431
|
dataframe: pd.DataFrame,
|
1525
1432
|
group_by: Optional[Union[str, List[str]]] = None,
|
@@ -1564,7 +1471,7 @@ class RasHdf:
|
|
1564
1471
|
return result
|
1565
1472
|
|
1566
1473
|
@classmethod
|
1567
|
-
@
|
1474
|
+
@log_call
|
1568
1475
|
def find_nearest_value(array: Union[list, np.ndarray], target_value: Union[int, float]) -> Union[int, float]:
|
1569
1476
|
"""
|
1570
1477
|
Finds the nearest value in a NumPy array to the specified target value.
|
@@ -1587,7 +1494,8 @@ class RasHdf:
|
|
1587
1494
|
return array[idx]
|
1588
1495
|
|
1589
1496
|
@staticmethod
|
1590
|
-
|
1497
|
+
@log_call
|
1498
|
+
def _get_hdf_filename(hdf_input: Union[str, Path, h5py.File], ras_object=None) -> Optional[Path]:
|
1591
1499
|
"""
|
1592
1500
|
Get the HDF filename from the input.
|
1593
1501
|
|
@@ -1596,11 +1504,10 @@ class RasHdf:
|
|
1596
1504
|
ras_object (RasPrj, optional): The RAS project object. If None, uses the global ras instance.
|
1597
1505
|
|
1598
1506
|
Returns:
|
1599
|
-
Path: The full path to the HDF file as a Path object.
|
1507
|
+
Optional[Path]: The full path to the HDF file as a Path object, or None if an error occurs.
|
1600
1508
|
|
1601
|
-
|
1602
|
-
|
1603
|
-
FileNotFoundError: If the specified HDF file does not exist.
|
1509
|
+
Note:
|
1510
|
+
This method logs critical errors instead of raising exceptions.
|
1604
1511
|
"""
|
1605
1512
|
|
1606
1513
|
# If hdf_input is already an h5py.File object, return its filename
|
@@ -1608,30 +1515,39 @@ class RasHdf:
|
|
1608
1515
|
return Path(hdf_input.filename)
|
1609
1516
|
|
1610
1517
|
# Convert to Path object if it's a string
|
1611
|
-
|
1518
|
+
if isinstance(hdf_input, str):
|
1519
|
+
hdf_input = Path(hdf_input)
|
1612
1520
|
|
1613
1521
|
# If hdf_input is a file path, return it directly
|
1614
|
-
if hdf_input.is_file():
|
1522
|
+
if isinstance(hdf_input, Path) and hdf_input.is_file():
|
1615
1523
|
return hdf_input
|
1616
1524
|
|
1617
1525
|
# If hdf_input is not a file path, assume it's a plan number and require ras_object
|
1618
1526
|
ras_obj = ras_object or ras
|
1619
1527
|
if not ras_obj.initialized:
|
1620
|
-
|
1528
|
+
logger.critical("ras_object is not initialized. ras_object is required when hdf_input is not a direct file path.")
|
1529
|
+
return None
|
1621
1530
|
|
1622
1531
|
plan_info = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == str(hdf_input)]
|
1623
1532
|
if plan_info.empty:
|
1624
|
-
|
1533
|
+
logger.critical(f"No HDF file found for plan number {hdf_input}")
|
1534
|
+
return None
|
1625
1535
|
|
1626
|
-
hdf_filename =
|
1627
|
-
if
|
1628
|
-
|
1536
|
+
hdf_filename = plan_info.iloc[0]['HDF_Results_Path']
|
1537
|
+
if hdf_filename is None:
|
1538
|
+
logger.critical(f"HDF_Results_Path is None for plan number {hdf_input}")
|
1539
|
+
return None
|
1629
1540
|
|
1630
|
-
|
1541
|
+
hdf_path = Path(hdf_filename)
|
1542
|
+
if not hdf_path.is_file():
|
1543
|
+
logger.critical(f"HDF file not found: {hdf_path}")
|
1544
|
+
return None
|
1631
1545
|
|
1546
|
+
return hdf_path
|
1632
1547
|
|
1633
1548
|
|
1634
1549
|
|
1550
|
+
@log_call
|
1635
1551
|
def save_dataframe_to_hdf(
|
1636
1552
|
dataframe: pd.DataFrame,
|
1637
1553
|
hdf_parent_group: h5py.Group,
|
@@ -1678,6 +1594,7 @@ def save_dataframe_to_hdf(
|
|
1678
1594
|
# Identify string columns and ensure consistency
|
1679
1595
|
string_cols = df.select_dtypes(include=['object']).columns
|
1680
1596
|
if not string_cols.equals(df.select_dtypes(include=['object']).columns):
|
1597
|
+
logger.error("Inconsistent string columns detected")
|
1681
1598
|
raise ValueError("Inconsistent string columns detected")
|
1682
1599
|
|
1683
1600
|
# Encode string columns to bytes
|
@@ -1688,6 +1605,7 @@ def save_dataframe_to_hdf(
|
|
1688
1605
|
|
1689
1606
|
# Remove existing dataset if it exists
|
1690
1607
|
if dataset_name in hdf_parent_group:
|
1608
|
+
logger.warning(f"Existing dataset {dataset_name} will be overwritten")
|
1691
1609
|
del hdf_parent_group[dataset_name]
|
1692
1610
|
|
1693
1611
|
# Create the dataset in the HDF5 file
|
@@ -1697,6 +1615,5 @@ def save_dataframe_to_hdf(
|
|
1697
1615
|
if attributes:
|
1698
1616
|
dataset.attrs.update(attributes)
|
1699
1617
|
|
1700
|
-
|
1618
|
+
logger.info(f"Successfully saved DataFrame to dataset: {dataset_name}")
|
1701
1619
|
return dataset
|
1702
|
-
|