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/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
- # Configure logging
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
- logging.error(f"Error in {func.__name__}: {e}")
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
- logging.info(f"Extracting Plan Information from: {Path(hdf_file.filename).name}")
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
- logging.warning("Group '/Plan Data/Plan Information' not found.")
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
- logging.info(f"Plan Name: {plan_name}")
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
- logging.info(f"Simulation Start Time: {start_time_str}")
146
- logging.info(f"Simulation End Time: {end_time_str}")
147
- logging.info(f"Simulation Duration (hours): {simulation_hours}")
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
- logging.warning("Dataset '/Results/Summary/Compute Processes' not found.")
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
- logging.debug("Compute processes DataFrame:")
167
- logging.debug(compute_processes_df)
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
- logging.debug("Compute summary DataFrame:")
188
- logging.debug(compute_summary_df)
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
- @hdf_operation
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
- logging.warning("No 2D Flow Areas found in the HDF file")
229
+ logger.warning("No 2D Flow Areas found in the HDF file")
216
230
  return None
217
- logging.info(f"Found {len(group_names)} 2D Flow Areas")
231
+ logger.info(f"Found {len(group_names)} 2D Flow Areas")
218
232
  return group_names
219
233
  else:
220
- logging.warning("No 2D Flow Areas found in the HDF file")
234
+ logger.warning("No 2D Flow Areas found in the HDF file")
221
235
  return None
222
-
223
236
  @classmethod
224
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- logging.error(f"Group not found: {group_path}")
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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
- @hdf_operation
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 as e:
1125
- logging.error(f"Error building face hydraulic information: {e}")
1042
+ except KeyError:
1126
1043
  return None
1127
1044
 
1128
1045
  @classmethod
1129
- @hdf_operation
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 as e:
1160
- logging.error(f"Error building face point coordinates list: {e}")
1075
+ except KeyError:
1161
1076
  return None
1162
1077
 
1163
1078
  @classmethod
1164
- @hdf_operation
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
- @hdf_operation
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.error(f"Error building face facepoints list: {e}")
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
- @hdf_operation
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.warning(f"No boundary points found for 2D Flow Area: {area}")
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.error(f"Error building boundaries: {e}")
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
- @hdf_operation
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
- @staticmethod
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
- @staticmethod
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
- @staticmethod
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
- @staticmethod
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
- @staticmethod
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
- def _get_hdf_filename(hdf_input: Union[str, Path, h5py.File], ras_object=None) -> Path:
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
- Raises:
1602
- ValueError: If no HDF file is found for the given plan number or if the input type is invalid.
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
- hdf_input = Path(hdf_input)
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
- raise ValueError("ras_object is not initialized. ras_object is required when hdf_input is not a direct file path.")
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
- raise ValueError(f"No HDF file found for plan number {hdf_input}")
1533
+ logger.critical(f"No HDF file found for plan number {hdf_input}")
1534
+ return None
1625
1535
 
1626
- hdf_filename = Path(plan_info.iloc[0]['HDF_Results_Path'])
1627
- if not hdf_filename.is_file():
1628
- raise FileNotFoundError(f"HDF file not found: {hdf_filename}")
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
- return hdf_filename
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
- logging.info(f"Successfully saved DataFrame to dataset: {dataset_name}")
1618
+ logger.info(f"Successfully saved DataFrame to dataset: {dataset_name}")
1701
1619
  return dataset
1702
-