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 | 
            -
             |