subsurface-terra 2025.1.0rc14__py3-none-any.whl → 2025.1.0rc16__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.
- subsurface/__init__.py +31 -31
- subsurface/_version.py +34 -21
- subsurface/api/__init__.py +13 -13
- subsurface/api/interfaces/__init__.py +3 -3
- subsurface/api/interfaces/stream.py +136 -136
- subsurface/api/reader/read_wells.py +78 -78
- subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
- subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -0
- subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -0
- subsurface/core/geological_formats/boreholes/boreholes.py +140 -116
- subsurface/core/geological_formats/boreholes/collars.py +26 -26
- subsurface/core/geological_formats/boreholes/survey.py +86 -380
- subsurface/core/geological_formats/fault.py +47 -47
- subsurface/core/reader_helpers/reader_unstruct.py +11 -11
- subsurface/core/reader_helpers/readers_data.py +130 -130
- subsurface/core/reader_helpers/readers_wells.py +13 -13
- subsurface/core/structs/__init__.py +3 -3
- subsurface/core/structs/base_structures/__init__.py +2 -2
- subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
- subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
- subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
- subsurface/core/structs/base_structures/structured_data.py +282 -282
- subsurface/core/structs/base_structures/unstructured_data.py +319 -319
- subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
- subsurface/core/structs/structured_elements/structured_grid.py +59 -59
- subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
- subsurface/core/structs/unstructured_elements/__init__.py +3 -3
- subsurface/core/structs/unstructured_elements/line_set.py +72 -72
- subsurface/core/structs/unstructured_elements/point_set.py +43 -43
- subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
- subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
- subsurface/core/utils/utils_core.py +38 -38
- subsurface/modules/reader/__init__.py +13 -13
- subsurface/modules/reader/faults/faults.py +80 -80
- subsurface/modules/reader/from_binary.py +46 -46
- subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
- subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
- subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
- subsurface/modules/reader/mesh/dxf_reader.py +177 -177
- subsurface/modules/reader/mesh/glb_reader.py +30 -30
- subsurface/modules/reader/mesh/mx_reader.py +232 -232
- subsurface/modules/reader/mesh/obj_reader.py +53 -53
- subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
- subsurface/modules/reader/mesh/surface_reader.py +56 -56
- subsurface/modules/reader/mesh/surfaces_api.py +41 -41
- subsurface/modules/reader/profiles/__init__.py +3 -3
- subsurface/modules/reader/profiles/profiles_core.py +197 -197
- subsurface/modules/reader/read_netcdf.py +38 -38
- subsurface/modules/reader/topography/__init__.py +7 -7
- subsurface/modules/reader/topography/topo_core.py +100 -100
- subsurface/modules/reader/volume/read_grav3d.py +478 -428
- subsurface/modules/reader/volume/read_volume.py +327 -230
- subsurface/modules/reader/volume/segy_reader.py +105 -105
- subsurface/modules/reader/volume/seismic.py +173 -173
- subsurface/modules/reader/volume/volume_utils.py +43 -43
- subsurface/modules/reader/wells/DEP/__init__.py +43 -43
- subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
- subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
- subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
- subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
- subsurface/modules/reader/wells/_read_to_df.py +57 -57
- subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
- subsurface/modules/reader/wells/wells_utils.py +68 -68
- subsurface/modules/tools/mocking_aux.py +104 -104
- subsurface/modules/visualization/__init__.py +2 -2
- subsurface/modules/visualization/to_pyvista.py +320 -320
- subsurface/modules/writer/to_binary.py +12 -12
- subsurface/modules/writer/to_rex/common.py +78 -78
- subsurface/modules/writer/to_rex/data_struct.py +74 -74
- subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
- subsurface/modules/writer/to_rex/material_encoder.py +44 -44
- subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
- subsurface/modules/writer/to_rex/to_rex.py +115 -115
- subsurface/modules/writer/to_rex/utils.py +15 -15
- subsurface/optional_requirements.py +116 -116
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/METADATA +194 -194
- subsurface_terra-2025.1.0rc16.dist-info/RECORD +98 -0
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/WHEEL +1 -1
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/licenses/LICENSE +203 -203
- subsurface_terra-2025.1.0rc14.dist-info/RECORD +0 -96
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import pathlib
|
|
3
|
-
import pandas as pd
|
|
4
|
-
|
|
5
|
-
from subsurface.core.utils.utils_core import get_extension
|
|
6
|
-
from pydantic import BaseModel, Field, model_validator
|
|
7
|
-
from typing import Union, List, Optional
|
|
8
|
-
|
|
9
|
-
if pd.__version__ < '1.4.0':
|
|
10
|
-
pass
|
|
11
|
-
elif pd.__version__ >= '1.4.0':
|
|
12
|
-
from pandas._typing import FilePath, ReadCsvBuffer
|
|
13
|
-
|
|
14
|
-
fb = Union[FilePath, ReadCsvBuffer[bytes], ReadCsvBuffer[str]]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SupportedFormats(str, enum.Enum):
|
|
18
|
-
DXF = "dxf"
|
|
19
|
-
DXFStream = "dxfstream"
|
|
20
|
-
CSV = "csv"
|
|
21
|
-
JSON = "json"
|
|
22
|
-
XLXS = "xlsx"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class GenericReaderFilesHelper(BaseModel):
|
|
26
|
-
file_or_buffer: Union[str, bytes, pathlib.Path, dict]
|
|
27
|
-
usecols: Optional[Union[List[str], List[int]]] = None
|
|
28
|
-
col_names: Optional[List[Union[str, int]]] = None
|
|
29
|
-
drop_cols: Optional[List[str]] = None
|
|
30
|
-
format: Optional[SupportedFormats] = None
|
|
31
|
-
separator: Optional[str] = None
|
|
32
|
-
index_map: Optional[dict] = None # Adjusted for serialization
|
|
33
|
-
columns_map: Optional[dict] = None # Adjusted for serialization
|
|
34
|
-
additional_reader_kwargs: dict = Field(default_factory=dict)
|
|
35
|
-
encoding: str = "ISO-8859-1"
|
|
36
|
-
index_col: Optional[Union[int, str, bool]] = False
|
|
37
|
-
header: Union[None, int, List[int]] = 0
|
|
38
|
-
|
|
39
|
-
# Computed fields
|
|
40
|
-
file_or_buffer_type: str = Field(init=False)
|
|
41
|
-
|
|
42
|
-
@model_validator(mode="before")
|
|
43
|
-
def set_format_and_file_type(cls, values):
|
|
44
|
-
file_or_buffer = values.get('file_or_buffer')
|
|
45
|
-
format = values.get('format')
|
|
46
|
-
|
|
47
|
-
# Determine format if not provided
|
|
48
|
-
if format is None and file_or_buffer is not None:
|
|
49
|
-
extension = get_extension(file_or_buffer)
|
|
50
|
-
format_map = {
|
|
51
|
-
".dxf" : SupportedFormats.DXF,
|
|
52
|
-
".csv" : SupportedFormats.CSV,
|
|
53
|
-
".json": SupportedFormats.JSON,
|
|
54
|
-
".xlsx": SupportedFormats.XLXS,
|
|
55
|
-
}
|
|
56
|
-
format = format_map.get(extension.lower())
|
|
57
|
-
values['format'] = format
|
|
58
|
-
|
|
59
|
-
# Set file_or_buffer_type as a string representation
|
|
60
|
-
if file_or_buffer is not None:
|
|
61
|
-
values['file_or_buffer_type'] = type(file_or_buffer).__name__
|
|
62
|
-
else:
|
|
63
|
-
values['file_or_buffer_type'] = None
|
|
64
|
-
|
|
65
|
-
return values
|
|
66
|
-
|
|
67
|
-
# Custom validation for index_col to explicitly handle None
|
|
68
|
-
|
|
69
|
-
@model_validator(mode="before")
|
|
70
|
-
def validate_additional_reader_kwargs(cls, values):
|
|
71
|
-
additional_reader_kwargs = values.get('additional_reader_kwargs')
|
|
72
|
-
# Make sure that if any of the values is a regex expression that it is properly parsed like "delimiter":"\\\\s{2,}" to delimiter="\s{2,}"
|
|
73
|
-
if additional_reader_kwargs is not None:
|
|
74
|
-
for key, value in additional_reader_kwargs.items():
|
|
75
|
-
if isinstance(value, str):
|
|
76
|
-
additional_reader_kwargs[key] = value.replace("\\\\", "\\")
|
|
77
|
-
|
|
78
|
-
return values
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@model_validator(mode="before")
|
|
82
|
-
def validate_index_col(cls, values):
|
|
83
|
-
index_col = values.get('index_col')
|
|
84
|
-
# Allow None explicitly
|
|
85
|
-
if index_col is None:
|
|
86
|
-
values['index_col'] = False
|
|
87
|
-
else:
|
|
88
|
-
# Ensure index_col is either int, str, or bool
|
|
89
|
-
if not isinstance(index_col, (int, str, bool)):
|
|
90
|
-
raise ValueError(f"Invalid value for index_col: {index_col}. Must be int, str, bool, or None.")
|
|
91
|
-
|
|
92
|
-
return values
|
|
93
|
-
|
|
94
|
-
# Validator to handle negative header values. If -1 is the same as null, other raise an error
|
|
95
|
-
@model_validator(mode="before")
|
|
96
|
-
def validate_header(cls, values):
|
|
97
|
-
header = values.get('header')
|
|
98
|
-
if header == -1:
|
|
99
|
-
values['header'] = None
|
|
100
|
-
header = None
|
|
101
|
-
if header is not None and header < 0:
|
|
102
|
-
raise ValueError(f"Invalid value for header: {header}. Must be None, 0, or positive integer.")
|
|
103
|
-
return values
|
|
104
|
-
|
|
105
|
-
# If col names is null or empy list it should be None
|
|
106
|
-
@model_validator(mode="before")
|
|
107
|
-
def validate_col_names(cls, values):
|
|
108
|
-
col_names = values.get('col_names')
|
|
109
|
-
if col_names is None or col_names == []:
|
|
110
|
-
values['col_names'] = None
|
|
111
|
-
return values
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def pandas_reader_kwargs(self):
|
|
115
|
-
attr_dict = {
|
|
116
|
-
"names" : self.col_names,
|
|
117
|
-
"header" : self.header,
|
|
118
|
-
"index_col": self.index_col,
|
|
119
|
-
"usecols" : self.usecols,
|
|
120
|
-
"encoding" : self.encoding
|
|
121
|
-
}
|
|
122
|
-
# Check if delimiter or separator is in additional_reader_kwargs if not add it here
|
|
123
|
-
if self.additional_reader_kwargs:
|
|
124
|
-
delimiter = self.additional_reader_kwargs.get("delimiter", None)
|
|
125
|
-
else:
|
|
126
|
-
delimiter = None
|
|
127
|
-
if self.separator is not None and delimiter is None:
|
|
128
|
-
attr_dict["sep"] = self.separator
|
|
129
|
-
|
|
130
|
-
return {**attr_dict, **self.additional_reader_kwargs}
|
|
1
|
+
import enum
|
|
2
|
+
import pathlib
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from subsurface.core.utils.utils_core import get_extension
|
|
6
|
+
from pydantic import BaseModel, Field, model_validator
|
|
7
|
+
from typing import Union, List, Optional
|
|
8
|
+
|
|
9
|
+
if pd.__version__ < '1.4.0':
|
|
10
|
+
pass
|
|
11
|
+
elif pd.__version__ >= '1.4.0':
|
|
12
|
+
from pandas._typing import FilePath, ReadCsvBuffer
|
|
13
|
+
|
|
14
|
+
fb = Union[FilePath, ReadCsvBuffer[bytes], ReadCsvBuffer[str]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SupportedFormats(str, enum.Enum):
|
|
18
|
+
DXF = "dxf"
|
|
19
|
+
DXFStream = "dxfstream"
|
|
20
|
+
CSV = "csv"
|
|
21
|
+
JSON = "json"
|
|
22
|
+
XLXS = "xlsx"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GenericReaderFilesHelper(BaseModel):
|
|
26
|
+
file_or_buffer: Union[str, bytes, pathlib.Path, dict]
|
|
27
|
+
usecols: Optional[Union[List[str], List[int]]] = None
|
|
28
|
+
col_names: Optional[List[Union[str, int]]] = None
|
|
29
|
+
drop_cols: Optional[List[str]] = None
|
|
30
|
+
format: Optional[SupportedFormats] = None
|
|
31
|
+
separator: Optional[str] = None
|
|
32
|
+
index_map: Optional[dict] = None # Adjusted for serialization
|
|
33
|
+
columns_map: Optional[dict] = None # Adjusted for serialization
|
|
34
|
+
additional_reader_kwargs: dict = Field(default_factory=dict)
|
|
35
|
+
encoding: str = "ISO-8859-1"
|
|
36
|
+
index_col: Optional[Union[int, str, bool]] = False
|
|
37
|
+
header: Union[None, int, List[int]] = 0
|
|
38
|
+
|
|
39
|
+
# Computed fields
|
|
40
|
+
file_or_buffer_type: str = Field(init=False)
|
|
41
|
+
|
|
42
|
+
@model_validator(mode="before")
|
|
43
|
+
def set_format_and_file_type(cls, values):
|
|
44
|
+
file_or_buffer = values.get('file_or_buffer')
|
|
45
|
+
format = values.get('format')
|
|
46
|
+
|
|
47
|
+
# Determine format if not provided
|
|
48
|
+
if format is None and file_or_buffer is not None:
|
|
49
|
+
extension = get_extension(file_or_buffer)
|
|
50
|
+
format_map = {
|
|
51
|
+
".dxf" : SupportedFormats.DXF,
|
|
52
|
+
".csv" : SupportedFormats.CSV,
|
|
53
|
+
".json": SupportedFormats.JSON,
|
|
54
|
+
".xlsx": SupportedFormats.XLXS,
|
|
55
|
+
}
|
|
56
|
+
format = format_map.get(extension.lower())
|
|
57
|
+
values['format'] = format
|
|
58
|
+
|
|
59
|
+
# Set file_or_buffer_type as a string representation
|
|
60
|
+
if file_or_buffer is not None:
|
|
61
|
+
values['file_or_buffer_type'] = type(file_or_buffer).__name__
|
|
62
|
+
else:
|
|
63
|
+
values['file_or_buffer_type'] = None
|
|
64
|
+
|
|
65
|
+
return values
|
|
66
|
+
|
|
67
|
+
# Custom validation for index_col to explicitly handle None
|
|
68
|
+
|
|
69
|
+
@model_validator(mode="before")
|
|
70
|
+
def validate_additional_reader_kwargs(cls, values):
|
|
71
|
+
additional_reader_kwargs = values.get('additional_reader_kwargs')
|
|
72
|
+
# Make sure that if any of the values is a regex expression that it is properly parsed like "delimiter":"\\\\s{2,}" to delimiter="\s{2,}"
|
|
73
|
+
if additional_reader_kwargs is not None:
|
|
74
|
+
for key, value in additional_reader_kwargs.items():
|
|
75
|
+
if isinstance(value, str):
|
|
76
|
+
additional_reader_kwargs[key] = value.replace("\\\\", "\\")
|
|
77
|
+
|
|
78
|
+
return values
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@model_validator(mode="before")
|
|
82
|
+
def validate_index_col(cls, values):
|
|
83
|
+
index_col = values.get('index_col')
|
|
84
|
+
# Allow None explicitly
|
|
85
|
+
if index_col is None:
|
|
86
|
+
values['index_col'] = False
|
|
87
|
+
else:
|
|
88
|
+
# Ensure index_col is either int, str, or bool
|
|
89
|
+
if not isinstance(index_col, (int, str, bool)):
|
|
90
|
+
raise ValueError(f"Invalid value for index_col: {index_col}. Must be int, str, bool, or None.")
|
|
91
|
+
|
|
92
|
+
return values
|
|
93
|
+
|
|
94
|
+
# Validator to handle negative header values. If -1 is the same as null, other raise an error
|
|
95
|
+
@model_validator(mode="before")
|
|
96
|
+
def validate_header(cls, values):
|
|
97
|
+
header = values.get('header')
|
|
98
|
+
if header == -1:
|
|
99
|
+
values['header'] = None
|
|
100
|
+
header = None
|
|
101
|
+
if header is not None and header < 0:
|
|
102
|
+
raise ValueError(f"Invalid value for header: {header}. Must be None, 0, or positive integer.")
|
|
103
|
+
return values
|
|
104
|
+
|
|
105
|
+
# If col names is null or empy list it should be None
|
|
106
|
+
@model_validator(mode="before")
|
|
107
|
+
def validate_col_names(cls, values):
|
|
108
|
+
col_names = values.get('col_names')
|
|
109
|
+
if col_names is None or col_names == []:
|
|
110
|
+
values['col_names'] = None
|
|
111
|
+
return values
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def pandas_reader_kwargs(self):
|
|
115
|
+
attr_dict = {
|
|
116
|
+
"names" : self.col_names,
|
|
117
|
+
"header" : self.header,
|
|
118
|
+
"index_col": self.index_col,
|
|
119
|
+
"usecols" : self.usecols,
|
|
120
|
+
"encoding" : self.encoding
|
|
121
|
+
}
|
|
122
|
+
# Check if delimiter or separator is in additional_reader_kwargs if not add it here
|
|
123
|
+
if self.additional_reader_kwargs:
|
|
124
|
+
delimiter = self.additional_reader_kwargs.get("delimiter", None)
|
|
125
|
+
else:
|
|
126
|
+
delimiter = None
|
|
127
|
+
if self.separator is not None and delimiter is None:
|
|
128
|
+
attr_dict["sep"] = self.separator
|
|
129
|
+
|
|
130
|
+
return {**attr_dict, **self.additional_reader_kwargs}
|
|
131
131
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
from subsurface.core.reader_helpers.readers_data import GenericReaderFilesHelper
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass
|
|
9
|
-
class ReaderWellsHelper:
|
|
10
|
-
reader_collars_args: GenericReaderFilesHelper
|
|
11
|
-
reader_survey_args: GenericReaderFilesHelper
|
|
12
|
-
reader_lith_args: GenericReaderFilesHelper = None
|
|
13
|
-
reader_attr_args: List[GenericReaderFilesHelper] = None
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from subsurface.core.reader_helpers.readers_data import GenericReaderFilesHelper
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ReaderWellsHelper:
|
|
10
|
+
reader_collars_args: GenericReaderFilesHelper
|
|
11
|
+
reader_survey_args: GenericReaderFilesHelper
|
|
12
|
+
reader_lith_args: GenericReaderFilesHelper = None
|
|
13
|
+
reader_attr_args: List[GenericReaderFilesHelper] = None
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .base_structures import StructuredData, UnstructuredData
|
|
2
|
-
from .unstructured_elements import PointSet, TriSurf, LineSet
|
|
3
|
-
from .structured_elements import StructuredGrid
|
|
1
|
+
from .base_structures import StructuredData, UnstructuredData
|
|
2
|
+
from .unstructured_elements import PointSet, TriSurf, LineSet
|
|
3
|
+
from .structured_elements import StructuredGrid
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
from .unstructured_data import UnstructuredData
|
|
2
|
-
from .structured_data import StructuredData
|
|
1
|
+
from .unstructured_data import UnstructuredData
|
|
2
|
+
from .structured_data import StructuredData
|
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import numpy as np
|
|
3
|
-
import pandas as pd
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class LiquidEarthMesh:
|
|
7
|
-
def __init__(self, vertex=None, cells=None, attributes=None, points_attributes=None, data_attrs=None):
|
|
8
|
-
self.vertex = vertex # Expected to be a numpy array of shape (N, 3)
|
|
9
|
-
self.cells = cells # Expected to be a numpy array of shape (M, K)
|
|
10
|
-
self.attributes = attributes
|
|
11
|
-
self.points_attributes = points_attributes
|
|
12
|
-
self.data_attrs = data_attrs if data_attrs is not None else {}
|
|
13
|
-
|
|
14
|
-
def to_binary(self, order='F') -> bytes:
|
|
15
|
-
body_ = self._to_bytearray(order)
|
|
16
|
-
header_ = self._set_binary_header()
|
|
17
|
-
header_json = json.dumps(header_)
|
|
18
|
-
header_json_bytes = header_json.encode('utf-8')
|
|
19
|
-
header_json_length = len(header_json_bytes)
|
|
20
|
-
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
21
|
-
file = header_json_length_bytes + header_json_bytes + body_
|
|
22
|
-
return file
|
|
23
|
-
|
|
24
|
-
def _set_binary_header(self):
|
|
25
|
-
header = {
|
|
26
|
-
"vertex_shape" : self.vertex.shape if self.vertex is not None else [0, 0],
|
|
27
|
-
"cell_shape" : self.cells.shape if self.cells is not None else [0, 0],
|
|
28
|
-
"cell_attr_shape" : self.attributes.shape if not self.attributes.empty else [0, 0],
|
|
29
|
-
"vertex_attr_shape": self.points_attributes.shape if not self.points_attributes.empty else [0, 0],
|
|
30
|
-
"cell_attr_names" : self.attributes.columns.tolist() if not self.attributes.empty else [],
|
|
31
|
-
"cell_attr_types" : self.attributes.dtypes.astype(str).tolist() if not self.attributes.empty else [],
|
|
32
|
-
"vertex_attr_names": self.points_attributes.columns.tolist() if not self.points_attributes.empty else [],
|
|
33
|
-
"vertex_attr_types": self.points_attributes.dtypes.astype(str).tolist() if not self.points_attributes.empty else [],
|
|
34
|
-
"xarray_attrs" : self.data_attrs
|
|
35
|
-
}
|
|
36
|
-
return header
|
|
37
|
-
|
|
38
|
-
def _to_bytearray(self, order):
|
|
39
|
-
parts = []
|
|
40
|
-
if self.vertex is not None:
|
|
41
|
-
vertex_bytes = self.vertex.astype('float32').tobytes(order)
|
|
42
|
-
parts.append(vertex_bytes)
|
|
43
|
-
if self.cells is not None:
|
|
44
|
-
cells_bytes = self.cells.astype('int32').tobytes(order)
|
|
45
|
-
parts.append(cells_bytes)
|
|
46
|
-
if not self.attributes.empty:
|
|
47
|
-
cell_attr_bytes = self.attributes.values.astype('float32').tobytes(order)
|
|
48
|
-
parts.append(cell_attr_bytes)
|
|
49
|
-
if not self.points_attributes.empty:
|
|
50
|
-
vertex_attr_bytes = self.points_attributes.values.astype('float32').tobytes(order)
|
|
51
|
-
parts.append(vertex_attr_bytes)
|
|
52
|
-
bytearray_le = b''.join(parts)
|
|
53
|
-
return bytearray_le
|
|
54
|
-
|
|
55
|
-
@classmethod
|
|
56
|
-
def from_binary(cls, binary_data, order='F'):
|
|
57
|
-
# Read header length
|
|
58
|
-
header_length_bytes = binary_data[:4]
|
|
59
|
-
header_length = int.from_bytes(header_length_bytes, byteorder='little')
|
|
60
|
-
# Read header
|
|
61
|
-
header_json_bytes = binary_data[4:4 + header_length]
|
|
62
|
-
header_json = header_json_bytes.decode('utf-8')
|
|
63
|
-
header = json.loads(header_json)
|
|
64
|
-
# Read body
|
|
65
|
-
body = binary_data[4 + header_length:]
|
|
66
|
-
offset = 0
|
|
67
|
-
|
|
68
|
-
# Parse vertices
|
|
69
|
-
vertex_shape = header['vertex_shape']
|
|
70
|
-
if vertex_shape[0] > 0 and vertex_shape[1] > 0:
|
|
71
|
-
num_vertices = np.prod(vertex_shape)
|
|
72
|
-
num_bytes = num_vertices * 4 # float32
|
|
73
|
-
vertex = np.frombuffer(body[offset:offset + num_bytes], dtype=np.float32, count=num_vertices)
|
|
74
|
-
offset += num_bytes
|
|
75
|
-
vertex = vertex.reshape(vertex_shape, order=order)
|
|
76
|
-
else:
|
|
77
|
-
vertex = None
|
|
78
|
-
|
|
79
|
-
# Parse cells
|
|
80
|
-
cell_shape = header['cell_shape']
|
|
81
|
-
if cell_shape[0] > 0 and cell_shape[1] > 0:
|
|
82
|
-
num_cells = np.prod(cell_shape)
|
|
83
|
-
num_bytes = num_cells * 4 # int32
|
|
84
|
-
cells = np.frombuffer(body[offset:offset + num_bytes], dtype=np.int32, count=num_cells)
|
|
85
|
-
offset += num_bytes
|
|
86
|
-
cells = cells.reshape(cell_shape, order=order)
|
|
87
|
-
else:
|
|
88
|
-
cells = None
|
|
89
|
-
|
|
90
|
-
# Parse cell attributes
|
|
91
|
-
attributes = pd.DataFrame()
|
|
92
|
-
cell_attr_shape = header['cell_attr_shape']
|
|
93
|
-
if cell_attr_shape[0] > 0 and cell_attr_shape[1] > 0:
|
|
94
|
-
num_attrs = np.prod(cell_attr_shape)
|
|
95
|
-
num_bytes = num_attrs * 4 # float32
|
|
96
|
-
cell_attr_values = np.frombuffer(body[offset:offset + num_bytes], dtype=np.float32, count=num_attrs)
|
|
97
|
-
offset += num_bytes
|
|
98
|
-
cell_attr_values = cell_attr_values.reshape(cell_attr_shape, order=order)
|
|
99
|
-
attr_names = header['cell_attr_names']
|
|
100
|
-
attributes = pd.DataFrame(cell_attr_values, columns=attr_names)
|
|
101
|
-
else:
|
|
102
|
-
attributes = None
|
|
103
|
-
|
|
104
|
-
# Parse vertex attributes
|
|
105
|
-
points_attributes = pd.DataFrame()
|
|
106
|
-
vertex_attr_shape = header['vertex_attr_shape']
|
|
107
|
-
if vertex_attr_shape[0] > 0 and vertex_attr_shape[1] > 0:
|
|
108
|
-
num_attrs = np.prod(vertex_attr_shape)
|
|
109
|
-
num_bytes = num_attrs * 4 # float32
|
|
110
|
-
vertex_attr_values = np.frombuffer(body[offset:offset + num_bytes], dtype=np.float32, count=num_attrs)
|
|
111
|
-
offset += num_bytes
|
|
112
|
-
vertex_attr_values = vertex_attr_values.reshape(vertex_attr_shape, order=order)
|
|
113
|
-
attr_names = header['vertex_attr_names']
|
|
114
|
-
points_attributes = pd.DataFrame(vertex_attr_values, columns=attr_names)
|
|
115
|
-
else:
|
|
116
|
-
points_attributes = None
|
|
117
|
-
|
|
118
|
-
data_attrs = header.get('xarray_attrs', {})
|
|
119
|
-
|
|
120
|
-
return cls(vertex=vertex, cells=cells, attributes=attributes, points_attributes=points_attributes, data_attrs=data_attrs)
|
|
121
|
-
|
|
1
|
+
import json
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LiquidEarthMesh:
|
|
7
|
+
def __init__(self, vertex=None, cells=None, attributes=None, points_attributes=None, data_attrs=None):
|
|
8
|
+
self.vertex = vertex # Expected to be a numpy array of shape (N, 3)
|
|
9
|
+
self.cells = cells # Expected to be a numpy array of shape (M, K)
|
|
10
|
+
self.attributes = attributes
|
|
11
|
+
self.points_attributes = points_attributes
|
|
12
|
+
self.data_attrs = data_attrs if data_attrs is not None else {}
|
|
13
|
+
|
|
14
|
+
def to_binary(self, order='F') -> bytes:
|
|
15
|
+
body_ = self._to_bytearray(order)
|
|
16
|
+
header_ = self._set_binary_header()
|
|
17
|
+
header_json = json.dumps(header_)
|
|
18
|
+
header_json_bytes = header_json.encode('utf-8')
|
|
19
|
+
header_json_length = len(header_json_bytes)
|
|
20
|
+
header_json_length_bytes = header_json_length.to_bytes(4, byteorder='little')
|
|
21
|
+
file = header_json_length_bytes + header_json_bytes + body_
|
|
22
|
+
return file
|
|
23
|
+
|
|
24
|
+
def _set_binary_header(self):
|
|
25
|
+
header = {
|
|
26
|
+
"vertex_shape" : self.vertex.shape if self.vertex is not None else [0, 0],
|
|
27
|
+
"cell_shape" : self.cells.shape if self.cells is not None else [0, 0],
|
|
28
|
+
"cell_attr_shape" : self.attributes.shape if not self.attributes.empty else [0, 0],
|
|
29
|
+
"vertex_attr_shape": self.points_attributes.shape if not self.points_attributes.empty else [0, 0],
|
|
30
|
+
"cell_attr_names" : self.attributes.columns.tolist() if not self.attributes.empty else [],
|
|
31
|
+
"cell_attr_types" : self.attributes.dtypes.astype(str).tolist() if not self.attributes.empty else [],
|
|
32
|
+
"vertex_attr_names": self.points_attributes.columns.tolist() if not self.points_attributes.empty else [],
|
|
33
|
+
"vertex_attr_types": self.points_attributes.dtypes.astype(str).tolist() if not self.points_attributes.empty else [],
|
|
34
|
+
"xarray_attrs" : self.data_attrs
|
|
35
|
+
}
|
|
36
|
+
return header
|
|
37
|
+
|
|
38
|
+
def _to_bytearray(self, order):
|
|
39
|
+
parts = []
|
|
40
|
+
if self.vertex is not None:
|
|
41
|
+
vertex_bytes = self.vertex.astype('float32').tobytes(order)
|
|
42
|
+
parts.append(vertex_bytes)
|
|
43
|
+
if self.cells is not None:
|
|
44
|
+
cells_bytes = self.cells.astype('int32').tobytes(order)
|
|
45
|
+
parts.append(cells_bytes)
|
|
46
|
+
if not self.attributes.empty:
|
|
47
|
+
cell_attr_bytes = self.attributes.values.astype('float32').tobytes(order)
|
|
48
|
+
parts.append(cell_attr_bytes)
|
|
49
|
+
if not self.points_attributes.empty:
|
|
50
|
+
vertex_attr_bytes = self.points_attributes.values.astype('float32').tobytes(order)
|
|
51
|
+
parts.append(vertex_attr_bytes)
|
|
52
|
+
bytearray_le = b''.join(parts)
|
|
53
|
+
return bytearray_le
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_binary(cls, binary_data, order='F'):
|
|
57
|
+
# Read header length
|
|
58
|
+
header_length_bytes = binary_data[:4]
|
|
59
|
+
header_length = int.from_bytes(header_length_bytes, byteorder='little')
|
|
60
|
+
# Read header
|
|
61
|
+
header_json_bytes = binary_data[4:4 + header_length]
|
|
62
|
+
header_json = header_json_bytes.decode('utf-8')
|
|
63
|
+
header = json.loads(header_json)
|
|
64
|
+
# Read body
|
|
65
|
+
body = binary_data[4 + header_length:]
|
|
66
|
+
offset = 0
|
|
67
|
+
|
|
68
|
+
# Parse vertices
|
|
69
|
+
vertex_shape = header['vertex_shape']
|
|
70
|
+
if vertex_shape[0] > 0 and vertex_shape[1] > 0:
|
|
71
|
+
num_vertices = np.prod(vertex_shape)
|
|
72
|
+
num_bytes = num_vertices * 4 # float32
|
|
73
|
+
vertex = np.frombuffer(body[offset:offset + num_bytes], dtype=np.float32, count=num_vertices)
|
|
74
|
+
offset += num_bytes
|
|
75
|
+
vertex = vertex.reshape(vertex_shape, order=order)
|
|
76
|
+
else:
|
|
77
|
+
vertex = None
|
|
78
|
+
|
|
79
|
+
# Parse cells
|
|
80
|
+
cell_shape = header['cell_shape']
|
|
81
|
+
if cell_shape[0] > 0 and cell_shape[1] > 0:
|
|
82
|
+
num_cells = np.prod(cell_shape)
|
|
83
|
+
num_bytes = num_cells * 4 # int32
|
|
84
|
+
cells = np.frombuffer(body[offset:offset + num_bytes], dtype=np.int32, count=num_cells)
|
|
85
|
+
offset += num_bytes
|
|
86
|
+
cells = cells.reshape(cell_shape, order=order)
|
|
87
|
+
else:
|
|
88
|
+
cells = None
|
|
89
|
+
|
|
90
|
+
# Parse cell attributes
|
|
91
|
+
attributes = pd.DataFrame()
|
|
92
|
+
cell_attr_shape = header['cell_attr_shape']
|
|
93
|
+
if cell_attr_shape[0] > 0 and cell_attr_shape[1] > 0:
|
|
94
|
+
num_attrs = np.prod(cell_attr_shape)
|
|
95
|
+
num_bytes = num_attrs * 4 # float32
|
|
96
|
+
cell_attr_values = np.frombuffer(body[offset:offset + num_bytes], dtype=np.float32, count=num_attrs)
|
|
97
|
+
offset += num_bytes
|
|
98
|
+
cell_attr_values = cell_attr_values.reshape(cell_attr_shape, order=order)
|
|
99
|
+
attr_names = header['cell_attr_names']
|
|
100
|
+
attributes = pd.DataFrame(cell_attr_values, columns=attr_names)
|
|
101
|
+
else:
|
|
102
|
+
attributes = None
|
|
103
|
+
|
|
104
|
+
# Parse vertex attributes
|
|
105
|
+
points_attributes = pd.DataFrame()
|
|
106
|
+
vertex_attr_shape = header['vertex_attr_shape']
|
|
107
|
+
if vertex_attr_shape[0] > 0 and vertex_attr_shape[1] > 0:
|
|
108
|
+
num_attrs = np.prod(vertex_attr_shape)
|
|
109
|
+
num_bytes = num_attrs * 4 # float32
|
|
110
|
+
vertex_attr_values = np.frombuffer(body[offset:offset + num_bytes], dtype=np.float32, count=num_attrs)
|
|
111
|
+
offset += num_bytes
|
|
112
|
+
vertex_attr_values = vertex_attr_values.reshape(vertex_attr_shape, order=order)
|
|
113
|
+
attr_names = header['vertex_attr_names']
|
|
114
|
+
points_attributes = pd.DataFrame(vertex_attr_values, columns=attr_names)
|
|
115
|
+
else:
|
|
116
|
+
points_attributes = None
|
|
117
|
+
|
|
118
|
+
data_attrs = header.get('xarray_attrs', {})
|
|
119
|
+
|
|
120
|
+
return cls(vertex=vertex, cells=cells, attributes=attributes, points_attributes=points_attributes, data_attrs=data_attrs)
|
|
121
|
+
|