ras-commander 0.48.0__py3-none-any.whl → 0.50.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/Decorators.py +18 -1
- ras_commander/HdfBase.py +307 -197
- ras_commander/HdfBndry.py +94 -287
- ras_commander/HdfFluvialPluvial.py +256 -273
- ras_commander/HdfInfiltration.py +410 -0
- ras_commander/HdfMesh.py +222 -114
- ras_commander/HdfPipe.py +127 -175
- ras_commander/HdfPlan.py +144 -58
- ras_commander/HdfPlot.py +104 -0
- ras_commander/HdfPump.py +76 -28
- ras_commander/HdfResultsMesh.py +190 -183
- ras_commander/HdfResultsPlan.py +76 -220
- ras_commander/HdfResultsPlot.py +182 -0
- ras_commander/HdfResultsXsec.py +185 -145
- ras_commander/HdfStruc.py +65 -35
- ras_commander/HdfUtils.py +435 -518
- ras_commander/HdfXsec.py +137 -127
- ras_commander/LoggingConfig.py +13 -3
- ras_commander/RasCmdr.py +13 -0
- ras_commander/RasExamples.py +14 -0
- ras_commander/RasGeo.py +11 -0
- ras_commander/RasGpt.py +8 -0
- ras_commander/RasMapper.py +105 -0
- ras_commander/RasPlan.py +30 -0
- ras_commander/RasPrj.py +34 -0
- ras_commander/RasToGo.py +16 -0
- ras_commander/RasUnsteady.py +15 -0
- ras_commander/RasUtils.py +31 -0
- ras_commander/__init__.py +10 -0
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/METADATA +77 -9
- ras_commander-0.50.0.dist-info/RECORD +34 -0
- ras_commander-0.48.0.dist-info/RECORD +0 -30
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfMesh.py
CHANGED
@@ -1,11 +1,36 @@
|
|
1
1
|
"""
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
A static class for handling mesh-related operations on HEC-RAS HDF files.
|
3
|
+
|
4
|
+
This class provides static methods to extract and analyze mesh data from HEC-RAS HDF files,
|
5
|
+
including mesh area names, mesh areas, cell polygons, cell points, cell faces, and
|
6
|
+
2D flow area attributes. No instantiation is required to use these methods.
|
7
|
+
|
8
|
+
All methods are designed to work with the mesh geometry data stored in
|
9
|
+
HEC-RAS HDF files, providing functionality to retrieve and process various aspects
|
10
|
+
of the 2D flow areas and their associated mesh structures.
|
11
|
+
|
12
|
+
|
13
|
+
List of Functions:
|
14
|
+
-----------------
|
15
|
+
get_mesh_area_names()
|
16
|
+
Returns list of 2D mesh area names
|
17
|
+
get_mesh_areas()
|
18
|
+
Returns 2D flow area perimeter polygons
|
19
|
+
get_mesh_cell_polygons()
|
20
|
+
Returns 2D flow mesh cell polygons
|
21
|
+
get_mesh_cell_points()
|
22
|
+
Returns 2D flow mesh cell center points
|
23
|
+
get_mesh_cell_faces()
|
24
|
+
Returns 2D flow mesh cell faces
|
25
|
+
get_mesh_area_attributes()
|
26
|
+
Returns geometry 2D flow area attributes
|
27
|
+
get_mesh_face_property_tables()
|
28
|
+
Returns Face Property Tables for each Face in all 2D Flow Areas
|
29
|
+
get_mesh_cell_property_tables()
|
30
|
+
Returns Cell Property Tables for each Cell in all 2D Flow Areas
|
31
|
+
|
32
|
+
Each function is decorated with @standardize_input and @log_call for consistent
|
33
|
+
input handling and logging functionality.
|
9
34
|
"""
|
10
35
|
from pathlib import Path
|
11
36
|
import h5py
|
@@ -44,9 +69,9 @@ class HdfMesh:
|
|
44
69
|
|
45
70
|
@staticmethod
|
46
71
|
@standardize_input(file_type='plan_hdf')
|
47
|
-
def
|
72
|
+
def get_mesh_area_names(hdf_path: Path) -> List[str]:
|
48
73
|
"""
|
49
|
-
Return a list of the 2D mesh area names
|
74
|
+
Return a list of the 2D mesh area names from the RAS geometry.
|
50
75
|
|
51
76
|
Parameters
|
52
77
|
----------
|
@@ -56,7 +81,8 @@ class HdfMesh:
|
|
56
81
|
Returns
|
57
82
|
-------
|
58
83
|
List[str]
|
59
|
-
A list of the 2D mesh area names
|
84
|
+
A list of the 2D mesh area names within the RAS geometry.
|
85
|
+
Returns an empty list if no 2D areas exist or if there's an error.
|
60
86
|
"""
|
61
87
|
try:
|
62
88
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
@@ -64,7 +90,7 @@ class HdfMesh:
|
|
64
90
|
return list()
|
65
91
|
return list(
|
66
92
|
[
|
67
|
-
HdfUtils.
|
93
|
+
HdfUtils.convert_ras_string(n.decode('utf-8'))
|
68
94
|
for n in hdf_file["Geometry/2D Flow Areas/Attributes"][()]["Name"]
|
69
95
|
]
|
70
96
|
)
|
@@ -74,7 +100,7 @@ class HdfMesh:
|
|
74
100
|
|
75
101
|
@staticmethod
|
76
102
|
@standardize_input(file_type='geom_hdf')
|
77
|
-
def
|
103
|
+
def get_mesh_areas(hdf_path: Path) -> GeoDataFrame:
|
78
104
|
"""
|
79
105
|
Return 2D flow area perimeter polygons.
|
80
106
|
|
@@ -90,7 +116,7 @@ class HdfMesh:
|
|
90
116
|
"""
|
91
117
|
try:
|
92
118
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
93
|
-
mesh_area_names = HdfMesh.
|
119
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
94
120
|
if not mesh_area_names:
|
95
121
|
return GeoDataFrame()
|
96
122
|
mesh_area_polygons = [
|
@@ -100,7 +126,7 @@ class HdfMesh:
|
|
100
126
|
return GeoDataFrame(
|
101
127
|
{"mesh_name": mesh_area_names, "geometry": mesh_area_polygons},
|
102
128
|
geometry="geometry",
|
103
|
-
crs=
|
129
|
+
crs=HdfBase.get_projection(hdf_file),
|
104
130
|
)
|
105
131
|
except Exception as e:
|
106
132
|
logger.error(f"Error reading mesh areas from {hdf_path}: {str(e)}")
|
@@ -108,7 +134,7 @@ class HdfMesh:
|
|
108
134
|
|
109
135
|
@staticmethod
|
110
136
|
@standardize_input(file_type='geom_hdf')
|
111
|
-
def
|
137
|
+
def get_mesh_cell_polygons(hdf_path: Path) -> GeoDataFrame:
|
112
138
|
"""
|
113
139
|
Return 2D flow mesh cell polygons.
|
114
140
|
|
@@ -120,63 +146,64 @@ class HdfMesh:
|
|
120
146
|
Returns
|
121
147
|
-------
|
122
148
|
GeoDataFrame
|
123
|
-
A GeoDataFrame containing the 2D flow mesh cell polygons
|
149
|
+
A GeoDataFrame containing the 2D flow mesh cell polygons with columns:
|
150
|
+
- mesh_name: name of the mesh area
|
151
|
+
- cell_id: unique identifier for each cell
|
152
|
+
- geometry: polygon geometry of the cell
|
153
|
+
Returns an empty GeoDataFrame if no 2D areas exist or if there's an error.
|
124
154
|
"""
|
125
155
|
try:
|
126
156
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
127
|
-
mesh_area_names = HdfMesh.
|
157
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
128
158
|
if not mesh_area_names:
|
129
159
|
return GeoDataFrame()
|
130
160
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
for
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
][()][:, 0]
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
)
|
171
|
-
)(face_id_lists)
|
172
|
-
)
|
173
|
-
return GeoDataFrame(cell_dict, geometry="geometry", crs=HdfUtils.projection(hdf_file))
|
161
|
+
# Get face geometries once
|
162
|
+
face_gdf = HdfMesh.get_mesh_cell_faces(hdf_path)
|
163
|
+
|
164
|
+
# Pre-allocate lists for better memory efficiency
|
165
|
+
all_mesh_names = []
|
166
|
+
all_cell_ids = []
|
167
|
+
all_geometries = []
|
168
|
+
|
169
|
+
for mesh_name in mesh_area_names:
|
170
|
+
# Get cell face info in one read
|
171
|
+
cell_face_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Info"][()]
|
172
|
+
cell_face_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Values"][()][:, 0]
|
173
|
+
|
174
|
+
# Create face lookup dictionary for this mesh
|
175
|
+
mesh_faces_dict = dict(face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]].values)
|
176
|
+
|
177
|
+
# Process each cell
|
178
|
+
for cell_id, (start, length) in enumerate(cell_face_info[:, :2]):
|
179
|
+
face_ids = cell_face_values[start:start + length]
|
180
|
+
face_geoms = [mesh_faces_dict[face_id] for face_id in face_ids]
|
181
|
+
|
182
|
+
# Create polygon
|
183
|
+
polygons = list(polygonize(face_geoms))
|
184
|
+
if polygons:
|
185
|
+
all_mesh_names.append(mesh_name)
|
186
|
+
all_cell_ids.append(cell_id)
|
187
|
+
all_geometries.append(Polygon(polygons[0]))
|
188
|
+
|
189
|
+
# Create GeoDataFrame in one go
|
190
|
+
return GeoDataFrame(
|
191
|
+
{
|
192
|
+
"mesh_name": all_mesh_names,
|
193
|
+
"cell_id": all_cell_ids,
|
194
|
+
"geometry": all_geometries
|
195
|
+
},
|
196
|
+
geometry="geometry",
|
197
|
+
crs=HdfBase.get_projection(hdf_file)
|
198
|
+
)
|
199
|
+
|
174
200
|
except Exception as e:
|
175
201
|
logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
|
176
202
|
return GeoDataFrame()
|
203
|
+
|
177
204
|
@staticmethod
|
178
205
|
@standardize_input(file_type='plan_hdf')
|
179
|
-
def
|
206
|
+
def get_mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
|
180
207
|
"""
|
181
208
|
Return 2D flow mesh cell center points.
|
182
209
|
|
@@ -192,30 +219,43 @@ class HdfMesh:
|
|
192
219
|
"""
|
193
220
|
try:
|
194
221
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
195
|
-
mesh_area_names = HdfMesh.
|
222
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
196
223
|
if not mesh_area_names:
|
197
224
|
return GeoDataFrame()
|
198
225
|
|
199
|
-
|
226
|
+
# Pre-allocate lists
|
227
|
+
all_mesh_names = []
|
228
|
+
all_cell_ids = []
|
229
|
+
all_points = []
|
230
|
+
|
200
231
|
for mesh_name in mesh_area_names:
|
201
|
-
|
202
|
-
|
232
|
+
# Get all cell centers in one read
|
233
|
+
cell_centers = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Center Coordinate"][()]
|
234
|
+
cell_count = len(cell_centers)
|
203
235
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
236
|
+
# Extend lists efficiently
|
237
|
+
all_mesh_names.extend([mesh_name] * cell_count)
|
238
|
+
all_cell_ids.extend(range(cell_count))
|
239
|
+
all_points.extend(Point(coords) for coords in cell_centers)
|
240
|
+
|
241
|
+
# Create GeoDataFrame in one go
|
242
|
+
return GeoDataFrame(
|
243
|
+
{
|
244
|
+
"mesh_name": all_mesh_names,
|
245
|
+
"cell_id": all_cell_ids,
|
246
|
+
"geometry": all_points
|
247
|
+
},
|
248
|
+
geometry="geometry",
|
249
|
+
crs=HdfBase.get_projection(hdf_file)
|
250
|
+
)
|
251
|
+
|
212
252
|
except Exception as e:
|
213
253
|
logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
|
214
254
|
return GeoDataFrame()
|
215
255
|
|
216
256
|
@staticmethod
|
217
257
|
@standardize_input(file_type='plan_hdf')
|
218
|
-
def
|
258
|
+
def get_mesh_cell_faces(hdf_path: Path) -> GeoDataFrame:
|
219
259
|
"""
|
220
260
|
Return 2D flow mesh cell faces.
|
221
261
|
|
@@ -231,45 +271,53 @@ class HdfMesh:
|
|
231
271
|
"""
|
232
272
|
try:
|
233
273
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
234
|
-
mesh_area_names = HdfMesh.
|
274
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
235
275
|
if not mesh_area_names:
|
236
276
|
return GeoDataFrame()
|
237
|
-
|
277
|
+
|
278
|
+
# Pre-allocate lists
|
279
|
+
all_mesh_names = []
|
280
|
+
all_face_ids = []
|
281
|
+
all_geometries = []
|
282
|
+
|
238
283
|
for mesh_name in mesh_area_names:
|
239
|
-
|
240
|
-
|
241
|
-
][()]
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
"Geometry/2D Flow Areas/{}/Faces Perimeter Values".format(mesh_name)
|
250
|
-
][()]
|
251
|
-
face_id = -1
|
252
|
-
for pnt_a_index, pnt_b_index in facepoints_index:
|
253
|
-
face_id += 1
|
254
|
-
face_dict["mesh_name"].append(mesh_name)
|
255
|
-
face_dict["face_id"].append(face_id)
|
256
|
-
coordinates = list()
|
257
|
-
coordinates.append(facepoints_coordinates[pnt_a_index])
|
258
|
-
starting_row, count = faces_perimeter_info[face_id]
|
284
|
+
# Read all data at once
|
285
|
+
facepoints_index = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"][()]
|
286
|
+
facepoints_coords = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"][()]
|
287
|
+
faces_perim_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"][()]
|
288
|
+
faces_perim_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"][()]
|
289
|
+
|
290
|
+
# Process each face
|
291
|
+
for face_id, ((pnt_a_idx, pnt_b_idx), (start_row, count)) in enumerate(zip(facepoints_index, faces_perim_info)):
|
292
|
+
coords = [facepoints_coords[pnt_a_idx]]
|
293
|
+
|
259
294
|
if count > 0:
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
295
|
+
coords.extend(faces_perim_values[start_row:start_row + count])
|
296
|
+
|
297
|
+
coords.append(facepoints_coords[pnt_b_idx])
|
298
|
+
|
299
|
+
all_mesh_names.append(mesh_name)
|
300
|
+
all_face_ids.append(face_id)
|
301
|
+
all_geometries.append(LineString(coords))
|
302
|
+
|
303
|
+
# Create GeoDataFrame in one go
|
304
|
+
return GeoDataFrame(
|
305
|
+
{
|
306
|
+
"mesh_name": all_mesh_names,
|
307
|
+
"face_id": all_face_ids,
|
308
|
+
"geometry": all_geometries
|
309
|
+
},
|
310
|
+
geometry="geometry",
|
311
|
+
crs=HdfBase.get_projection(hdf_file)
|
312
|
+
)
|
313
|
+
|
266
314
|
except Exception as e:
|
267
|
-
|
315
|
+
logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
|
268
316
|
return GeoDataFrame()
|
269
317
|
|
270
318
|
@staticmethod
|
271
319
|
@standardize_input(file_type='geom_hdf')
|
272
|
-
def
|
320
|
+
def get_mesh_area_attributes(hdf_path: Path) -> pd.DataFrame:
|
273
321
|
"""
|
274
322
|
Return geometry 2D flow area attributes from a HEC-RAS HDF file.
|
275
323
|
|
@@ -280,8 +328,8 @@ class HdfMesh:
|
|
280
328
|
|
281
329
|
Returns
|
282
330
|
-------
|
283
|
-
|
284
|
-
A
|
331
|
+
pd.DataFrame
|
332
|
+
A DataFrame containing the 2D flow area attributes.
|
285
333
|
"""
|
286
334
|
try:
|
287
335
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
@@ -293,20 +341,20 @@ class HdfMesh:
|
|
293
341
|
value = d2_flow_area[name][()]
|
294
342
|
if isinstance(value, bytes):
|
295
343
|
value = value.decode('utf-8') # Decode as UTF-8
|
296
|
-
result[name] = value
|
344
|
+
result[name] = value if not isinstance(value, bytes) else value.decode('utf-8')
|
297
345
|
except Exception as e:
|
298
346
|
logger.warning(f"Error converting attribute '{name}': {str(e)}")
|
299
|
-
return result
|
347
|
+
return pd.DataFrame.from_dict(result, orient='index', columns=['Value'])
|
300
348
|
else:
|
301
349
|
logger.info("No 2D Flow Area attributes found or invalid dataset.")
|
302
|
-
return
|
350
|
+
return pd.DataFrame() # Return an empty DataFrame
|
303
351
|
except Exception as e:
|
304
352
|
logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
|
305
|
-
return
|
353
|
+
return pd.DataFrame() # Return an empty DataFrame
|
306
354
|
|
307
355
|
@staticmethod
|
308
356
|
@standardize_input(file_type='geom_hdf')
|
309
|
-
def
|
357
|
+
def get_mesh_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
310
358
|
"""
|
311
359
|
Extract Face Property Tables for each Face in all 2D Flow Areas.
|
312
360
|
|
@@ -318,12 +366,19 @@ class HdfMesh:
|
|
318
366
|
Returns
|
319
367
|
-------
|
320
368
|
Dict[str, pd.DataFrame]
|
321
|
-
A dictionary where
|
322
|
-
|
369
|
+
A dictionary where:
|
370
|
+
- keys: mesh area names (str)
|
371
|
+
- values: DataFrames with columns:
|
372
|
+
- Face ID: unique identifier for each face
|
373
|
+
- Z: elevation
|
374
|
+
- Area: face area
|
375
|
+
- Wetted Perimeter: wetted perimeter length
|
376
|
+
- Manning's n: Manning's roughness coefficient
|
377
|
+
Returns an empty dictionary if no 2D areas exist or if there's an error.
|
323
378
|
"""
|
324
379
|
try:
|
325
380
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
326
|
-
mesh_area_names = HdfMesh.
|
381
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
327
382
|
if not mesh_area_names:
|
328
383
|
return {}
|
329
384
|
|
@@ -351,3 +406,56 @@ class HdfMesh:
|
|
351
406
|
except Exception as e:
|
352
407
|
logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
|
353
408
|
return {}
|
409
|
+
|
410
|
+
@staticmethod
|
411
|
+
@standardize_input(file_type='geom_hdf')
|
412
|
+
def get_mesh_cell_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
413
|
+
"""
|
414
|
+
Extract Cell Property Tables for each Cell in all 2D Flow Areas.
|
415
|
+
|
416
|
+
Parameters
|
417
|
+
----------
|
418
|
+
hdf_path : Path
|
419
|
+
Path to the HEC-RAS geometry HDF file.
|
420
|
+
|
421
|
+
Returns
|
422
|
+
-------
|
423
|
+
Dict[str, pd.DataFrame]
|
424
|
+
A dictionary where:
|
425
|
+
- keys: mesh area names (str)
|
426
|
+
- values: DataFrames with columns:
|
427
|
+
- Cell ID: unique identifier for each cell
|
428
|
+
- Z: elevation
|
429
|
+
- Volume: cell volume
|
430
|
+
- Surface Area: cell surface area
|
431
|
+
Returns an empty dictionary if no 2D areas exist or if there's an error.
|
432
|
+
"""
|
433
|
+
try:
|
434
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
435
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
436
|
+
if not mesh_area_names:
|
437
|
+
return {}
|
438
|
+
|
439
|
+
result = {}
|
440
|
+
for mesh_name in mesh_area_names:
|
441
|
+
cell_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Elevation Volume Info"][()]
|
442
|
+
cell_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Elevation Volume Values"][()]
|
443
|
+
|
444
|
+
cell_data = []
|
445
|
+
for cell_id, (start_index, count) in enumerate(cell_elevation_info):
|
446
|
+
cell_values = cell_elevation_values[start_index:start_index+count]
|
447
|
+
for z, volume, surface_area in cell_values:
|
448
|
+
cell_data.append({
|
449
|
+
'Cell ID': cell_id,
|
450
|
+
'Z': str(z),
|
451
|
+
'Volume': str(volume),
|
452
|
+
'Surface Area': str(surface_area)
|
453
|
+
})
|
454
|
+
|
455
|
+
result[mesh_name] = pd.DataFrame(cell_data)
|
456
|
+
|
457
|
+
return result
|
458
|
+
|
459
|
+
except Exception as e:
|
460
|
+
logger.error(f"Error extracting cell property tables from {hdf_path}: {str(e)}")
|
461
|
+
return {}
|