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.
Files changed (81) hide show
  1. subsurface/__init__.py +31 -31
  2. subsurface/_version.py +34 -21
  3. subsurface/api/__init__.py +13 -13
  4. subsurface/api/interfaces/__init__.py +3 -3
  5. subsurface/api/interfaces/stream.py +136 -136
  6. subsurface/api/reader/read_wells.py +78 -78
  7. subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
  8. subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -0
  9. subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -0
  10. subsurface/core/geological_formats/boreholes/boreholes.py +140 -116
  11. subsurface/core/geological_formats/boreholes/collars.py +26 -26
  12. subsurface/core/geological_formats/boreholes/survey.py +86 -380
  13. subsurface/core/geological_formats/fault.py +47 -47
  14. subsurface/core/reader_helpers/reader_unstruct.py +11 -11
  15. subsurface/core/reader_helpers/readers_data.py +130 -130
  16. subsurface/core/reader_helpers/readers_wells.py +13 -13
  17. subsurface/core/structs/__init__.py +3 -3
  18. subsurface/core/structs/base_structures/__init__.py +2 -2
  19. subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
  20. subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
  21. subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
  22. subsurface/core/structs/base_structures/structured_data.py +282 -282
  23. subsurface/core/structs/base_structures/unstructured_data.py +319 -319
  24. subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
  25. subsurface/core/structs/structured_elements/structured_grid.py +59 -59
  26. subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
  27. subsurface/core/structs/unstructured_elements/__init__.py +3 -3
  28. subsurface/core/structs/unstructured_elements/line_set.py +72 -72
  29. subsurface/core/structs/unstructured_elements/point_set.py +43 -43
  30. subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
  31. subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
  32. subsurface/core/utils/utils_core.py +38 -38
  33. subsurface/modules/reader/__init__.py +13 -13
  34. subsurface/modules/reader/faults/faults.py +80 -80
  35. subsurface/modules/reader/from_binary.py +46 -46
  36. subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
  37. subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
  38. subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
  39. subsurface/modules/reader/mesh/dxf_reader.py +177 -177
  40. subsurface/modules/reader/mesh/glb_reader.py +30 -30
  41. subsurface/modules/reader/mesh/mx_reader.py +232 -232
  42. subsurface/modules/reader/mesh/obj_reader.py +53 -53
  43. subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
  44. subsurface/modules/reader/mesh/surface_reader.py +56 -56
  45. subsurface/modules/reader/mesh/surfaces_api.py +41 -41
  46. subsurface/modules/reader/profiles/__init__.py +3 -3
  47. subsurface/modules/reader/profiles/profiles_core.py +197 -197
  48. subsurface/modules/reader/read_netcdf.py +38 -38
  49. subsurface/modules/reader/topography/__init__.py +7 -7
  50. subsurface/modules/reader/topography/topo_core.py +100 -100
  51. subsurface/modules/reader/volume/read_grav3d.py +478 -428
  52. subsurface/modules/reader/volume/read_volume.py +327 -230
  53. subsurface/modules/reader/volume/segy_reader.py +105 -105
  54. subsurface/modules/reader/volume/seismic.py +173 -173
  55. subsurface/modules/reader/volume/volume_utils.py +43 -43
  56. subsurface/modules/reader/wells/DEP/__init__.py +43 -43
  57. subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
  58. subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
  59. subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
  60. subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
  61. subsurface/modules/reader/wells/_read_to_df.py +57 -57
  62. subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
  63. subsurface/modules/reader/wells/wells_utils.py +68 -68
  64. subsurface/modules/tools/mocking_aux.py +104 -104
  65. subsurface/modules/visualization/__init__.py +2 -2
  66. subsurface/modules/visualization/to_pyvista.py +320 -320
  67. subsurface/modules/writer/to_binary.py +12 -12
  68. subsurface/modules/writer/to_rex/common.py +78 -78
  69. subsurface/modules/writer/to_rex/data_struct.py +74 -74
  70. subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
  71. subsurface/modules/writer/to_rex/material_encoder.py +44 -44
  72. subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
  73. subsurface/modules/writer/to_rex/to_rex.py +115 -115
  74. subsurface/modules/writer/to_rex/utils.py +15 -15
  75. subsurface/optional_requirements.py +116 -116
  76. {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/METADATA +194 -194
  77. subsurface_terra-2025.1.0rc16.dist-info/RECORD +98 -0
  78. {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/WHEEL +1 -1
  79. {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/licenses/LICENSE +203 -203
  80. subsurface_terra-2025.1.0rc14.dist-info/RECORD +0 -96
  81. {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
+