Skip to content

Commit eb450a6

Browse files
bugfix for epc rels export
1 parent 2e2d5d4 commit eb450a6

20 files changed

Lines changed: 7132 additions & 2496 deletions

energyml-utils/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ docs/*.md
6262
*.geojson
6363
*.vtk
6464
*.stl
65+
rc/specs
66+
rc/**/*.epc
67+
rc/**/*.h5
68+
rc/**/*.hdf5
6569

6670

6771
# WIP

energyml-utils/example/main_stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from energyml.utils.introspection import get_obj_uri
1515
from energyml.utils.constants import EpcExportVersion
16-
from energyml.utils.epc_stream import read_epc_stream
16+
from energyml.utils.epc_stream_old import read_epc_stream
1717
from energyml.utils.epc import (
1818
Epc,
1919
create_energyml_object,
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import os
2+
import sys
3+
import logging
4+
from energyml.utils.epc_stream import EpcStreamReader, RelsUpdateMode
5+
from energyml.eml.v2_3.commonv2 import Citation
6+
from energyml.resqml.v2_2.resqmlv2 import (
7+
TriangulatedSetRepresentation,
8+
BoundaryFeatureInterpretation,
9+
BoundaryFeature,
10+
HorizonInterpretation,
11+
)
12+
from energyml.utils.introspection import epoch_to_date, epoch
13+
from energyml.utils.epc import as_dor, gen_uuid, get_obj_identifier
14+
from energyml.utils.constants import EPCRelsRelationshipType
15+
16+
17+
def sample_objects():
18+
"""Create sample EnergyML objects for testing."""
19+
# Create a BoundaryFeature
20+
bf = BoundaryFeature(
21+
citation=Citation(
22+
title="Test Boundary Feature",
23+
originator="Test",
24+
creation=epoch_to_date(epoch()),
25+
),
26+
uuid="25773477-ffee-4cc2-867d-000000000001",
27+
object_version="1.0",
28+
)
29+
30+
# Create a BoundaryFeatureInterpretation
31+
bfi = BoundaryFeatureInterpretation(
32+
citation=Citation(
33+
title="Test Boundary Feature Interpretation",
34+
originator="Test",
35+
creation=epoch_to_date(epoch()),
36+
),
37+
uuid="25773477-ffee-4cc2-867d-000000000002",
38+
object_version="1.0",
39+
interpreted_feature=as_dor(bf),
40+
)
41+
42+
# Create a HorizonInterpretation (independent object)
43+
horizon_interp = HorizonInterpretation(
44+
citation=Citation(
45+
title="Test HorizonInterpretation",
46+
originator="Test",
47+
creation=epoch_to_date(epoch()),
48+
),
49+
interpreted_feature=as_dor(bf),
50+
uuid="25773477-ffee-4cc2-867d-000000000003",
51+
object_version="1.0",
52+
domain="depth",
53+
)
54+
55+
# Create a TriangulatedSetRepresentation
56+
trset = TriangulatedSetRepresentation(
57+
citation=Citation(
58+
title="Test TriangulatedSetRepresentation",
59+
originator="Test",
60+
creation=epoch_to_date(epoch()),
61+
),
62+
uuid="25773477-ffee-4cc2-867d-000000000004",
63+
object_version="1.0",
64+
represented_object=as_dor(horizon_interp),
65+
)
66+
67+
return {
68+
"bf": bf,
69+
"bfi": bfi,
70+
"trset": trset,
71+
"horizon_interp": horizon_interp,
72+
}
73+
74+
75+
def main(epc_file_path: str):
76+
epc = EpcStreamReader(
77+
epc_file_path=epc_file_path, enable_parallel_rels=True, rels_update_mode=RelsUpdateMode.UPDATE_AT_MODIFICATION
78+
)
79+
80+
# logging.info(epc.get_statistics())
81+
82+
for obj in epc.list_objects():
83+
logging.info(f"Object: {obj}")
84+
85+
86+
def test_create_epc(path: str):
87+
# delete file if exists
88+
if os.path.exists(path):
89+
os.remove(path)
90+
logging.info(f"==> Creating new EPC at {path}...")
91+
epc = EpcStreamReader(epc_file_path=path, rels_update_mode=RelsUpdateMode.UPDATE_AT_MODIFICATION)
92+
93+
data = sample_objects()
94+
95+
logging.info("==> Creating sample objects and adding to EPC...")
96+
97+
logging.info("==> Adding horizon interpretation")
98+
epc.add_object(data["horizon_interp"])
99+
logging.info(f"horizon rels : {epc.get_obj_rels(data['horizon_interp'])}")
100+
101+
logging.info("==> Adding boundary feature")
102+
epc.add_object(data["bf"])
103+
logging.info(f"boundary feature rels : {epc.get_obj_rels(data['bf'])}")
104+
105+
logging.info("==> Adding boundary feature interpretation")
106+
epc.add_object(data["bfi"])
107+
logging.info("==> Adding triangulated set representation")
108+
epc.add_object(data["trset"])
109+
110+
# Debug: Print all metadata identifiers
111+
logging.info(f"==> All metadata identifiers: {list(epc._metadata_mgr._metadata.keys())}")
112+
113+
logging.info("==> All objects added. Closing EPC to write to disk.")
114+
115+
horizon_id = get_obj_identifier(data["horizon_interp"])
116+
logging.info(f"==> Horizon identifier: {horizon_id}")
117+
logging.info(f"==> Horizon in metadata: {horizon_id in epc._metadata_mgr._metadata}")
118+
119+
# Debug: Test _id_from_uri_or_identifier
120+
resolved_id = epc._id_from_uri_or_identifier(data["horizon_interp"])
121+
logging.info(f"==> Resolved ID from object: {resolved_id}")
122+
logging.info(
123+
f"==> Resolved ID in metadata: {resolved_id in epc._metadata_mgr._metadata if resolved_id else 'ID is None'}"
124+
)
125+
126+
horizon_rels = epc.get_obj_rels(data["horizon_interp"])
127+
assert (
128+
len(horizon_rels) == 2
129+
), f"Expected 2 relationships in horizon rels since both bfi and trset should refer to horizon as interpreted feature {horizon_rels}"
130+
epc.close()
131+
132+
epc_reopen = EpcStreamReader(epc_file_path=path, rels_update_mode=RelsUpdateMode.UPDATE_AT_MODIFICATION)
133+
134+
horizon_rels = epc_reopen.get_obj_rels(data["horizon_interp"])
135+
assert (
136+
len(horizon_rels) == 2
137+
), f"Expected 2 relationships in horizon rels since both bfi and trset should refer to horizon as interpreted feature {horizon_rels}"
138+
139+
logging.info("==> Reopened EPC, listing objects:")
140+
for obj in epc_reopen.list_objects():
141+
logging.info(f"Object: {obj}")
142+
obj_rels = epc_reopen.get_obj_rels(obj)
143+
logging.info(f"\tObject rels: {obj_rels}")
144+
dest_rels = [r for r in obj_rels if r.type_value == str(EPCRelsRelationshipType.DESTINATION_OBJECT)]
145+
logging.info(f"\tObject DESTINATION rels: {dest_rels}")
146+
147+
# remove trset to check if horizon has no more source rels
148+
epc_reopen.remove_object(data["trset"])
149+
150+
horizon_rels_after_removal = epc_reopen.get_obj_rels(data["horizon_interp"])
151+
logging.info(f"Horizon interpretation rels after removing trset: {horizon_rels_after_removal}")
152+
source_rels_after_removal = [
153+
r for r in horizon_rels_after_removal if r.type_value == str(EPCRelsRelationshipType.SOURCE_OBJECT)
154+
]
155+
logging.info(f"Horizon interpretation SOURCE rels after removing trset: {source_rels_after_removal}")
156+
assert (
157+
len(source_rels_after_removal) == 0
158+
), "Expected no SOURCE relationships in horizon rels after removing trset since trset was the only destination referring to horizon"
159+
160+
assert (
161+
len(horizon_rels_after_removal) == 1
162+
), "Expected 1 relationship in horizon rels after removing trset since bfi should still refer to horizon as interpreted feature"
163+
164+
epc_reopen.close()
165+
166+
167+
def test_create_epc_v2(path: str):
168+
169+
if os.path.exists(path):
170+
os.remove(path)
171+
logging.info(f"==> Creating new EPC at {path}...")
172+
epc = EpcStreamReader(epc_file_path=path, rels_update_mode=RelsUpdateMode.UPDATE_AT_MODIFICATION)
173+
174+
data = sample_objects()
175+
176+
epc.add_object(data["bf"])
177+
# epc.add_object(data["bfi"])
178+
epc.add_object(data["horizon_interp"])
179+
epc.add_object(data["trset"])
180+
181+
hi_rels = epc.get_obj_rels(data["horizon_interp"])
182+
183+
logging.info(f"Horizon interpretation rels: {hi_rels}")
184+
185+
186+
if __name__ == "__main__":
187+
logging.basicConfig(level=logging.DEBUG)
188+
189+
# main((sys.argv[1] if len(sys.argv) > 1 else None) or "wip/80wells_surf.epc")
190+
191+
# test_create_epc("wip/test_create.epc")
192+
test_create_epc_v2("wip/test_create.epc")
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2023-2024 Geosiris.
3+
# SPDX-License-Identifier: Apache-2.0
4+
"""
5+
Example script demonstrating EPC validation.
6+
7+
This script shows how to validate EPC files and generate reports.
8+
"""
9+
10+
import sys
11+
from pathlib import Path
12+
13+
from energyml.utils.epc_validator import validate_epc_file
14+
15+
16+
def validate_single_file(epc_path: str) -> None:
17+
"""Validate a single EPC file and print results."""
18+
print(f"\n{'=' * 70}")
19+
print(f"Validating: {epc_path}")
20+
print(f"{'=' * 70}\n")
21+
22+
try:
23+
result = validate_epc_file(epc_path, strict=True, check_relationships=True)
24+
25+
print(result)
26+
27+
if result.is_valid:
28+
print("\n✓ Validation PASSED!")
29+
else:
30+
print("\n✗ Validation FAILED!")
31+
sys.exit(1)
32+
33+
except Exception as e:
34+
print(f"\n✗ Error during validation: {e}")
35+
sys.exit(1)
36+
37+
38+
def validate_directory(directory: str) -> None:
39+
"""Validate all EPC files in a directory."""
40+
print(f"\n{'=' * 70}")
41+
print(f"Validating all EPC files in: {directory}")
42+
print(f"{'=' * 70}\n")
43+
44+
epc_files = list(Path(directory).glob("**/*.epc"))
45+
46+
if not epc_files:
47+
print(f"No EPC files found in {directory}")
48+
return
49+
50+
print(f"Found {len(epc_files)} EPC file(s)\n")
51+
52+
results = {}
53+
for epc_file in epc_files:
54+
print(f"Validating {epc_file.name}...", end=" ")
55+
result = validate_epc_file(str(epc_file))
56+
57+
if result.is_valid:
58+
print("✓ PASSED")
59+
else:
60+
print("✗ FAILED")
61+
for error in result.errors[:3]: # Show first 3 errors
62+
print(f" - {error}")
63+
if len(result.errors) > 3:
64+
print(f" ... and {len(result.errors) - 3} more errors")
65+
66+
results[epc_file.name] = result
67+
68+
# Summary
69+
print(f"\n{'=' * 70}")
70+
print("SUMMARY")
71+
print(f"{'=' * 70}")
72+
passed = sum(1 for r in results.values() if r.is_valid)
73+
failed = len(results) - passed
74+
print(f"Total files: {len(results)}")
75+
print(f"Passed: {passed}")
76+
print(f"Failed: {failed}")
77+
78+
79+
def main():
80+
"""Main entry point."""
81+
if len(sys.argv) < 2:
82+
print("Usage:")
83+
print(f" {sys.argv[0]} <epc_file> # Validate a single file")
84+
print(f" {sys.argv[0]} <directory> # Validate all EPC files in directory")
85+
sys.exit(1)
86+
87+
path = sys.argv[1]
88+
89+
if Path(path).is_file():
90+
validate_single_file(path)
91+
elif Path(path).is_dir():
92+
validate_directory(path)
93+
else:
94+
print(f"Error: '{path}' is neither a file nor a directory")
95+
sys.exit(1)
96+
97+
98+
if __name__ == "__main__":
99+
main()
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)