22Main entry point for creating a geological model
33"""
44
5- from ...utils import getLogger , log_to_file
5+ from ...utils import getLogger
66
77import numpy as np
88import pandas as pd
@@ -61,33 +61,24 @@ class GeologicalModel:
6161 the origin of the model box
6262 parameters : dict
6363 a dictionary tracking the parameters used to build the model
64- scale_factor : double
65- the scale factor used to rescale the model
66-
64+
6765
6866 """
6967
7068 def __init__ (
7169 self ,
72- origin : np .ndarray ,
73- maximum : np .ndarray ,
74- data = None ,
75- nsteps = (50 , 50 , 25 ),
76- reuse_supports = False ,
77- logfile = None ,
78- loglevel = "info" ,
70+ * args
7971 ):
8072 """
8173 Parameters
8274 ----------
83- origin : numpy array
84- specifying the origin of the model
85- maximum : numpy array
86- specifying the maximum extent of the model
87- rescale : bool
88- whether to rescale the model to between 0/1
89- epsion : float
90- a fudge factor for isosurfacing, used to make sure surfaces appear
75+ bounding_box : BoundingBox
76+ the bounding box of the model
77+ origin : np.array(3,dtype=doubles)
78+ the origin of the model
79+ maximum : np.array(3,dtype=doubles)
80+ the maximum of the model
81+
9182 Examples
9283 --------
9384 Demo data
@@ -111,38 +102,32 @@ def __init__(
111102
112103
113104 """
114- if logfile :
115- self .logfile = logfile
116- log_to_file (logfile , level = loglevel )
117-
105+ args = list (args )
106+ if len (args ) == 0 :
107+ raise ValueError ("Must provide either bounding_box or origin and maximum" )
108+ if len (args ) == 1 :
109+ bounding_box = args [0 ]
110+ if not isinstance (bounding_box , BoundingBox ):
111+ raise ValueError ("Must provide a bounding box" )
112+ self .bounding_box = bounding_box
113+ if len (args ) == 2 :
114+ origin = args [0 ]
115+ maximum = args [1 ]
116+ if not isinstance (origin , np .ndarray ) or not isinstance (maximum , np .ndarray ):
117+ raise ValueError ("Must provide origin and maximum as numpy arrays" )
118+ self .bounding_box = BoundingBox (
119+ dimensions = 3 ,
120+ origin = np .zeros (3 ),
121+ maximum = maximum - origin ,
122+ global_origin = origin ,
123+ )
118124 logger .info ("Initialising geological model" )
119125 self .features = []
120126 self .feature_name_index = {}
121127 self ._data = pd .DataFrame () # None
122- if data is not None :
123- self .data = data
124- self .nsteps = nsteps
125-
126- # we want to rescale the model area so that the maximum length is
127- # 1
128- self .origin = np .array (origin ).astype (float )
129- originstr = f"Model origin: { self .origin [0 ]} { self .origin [1 ]} { self .origin [2 ]} "
130- logger .info (originstr )
131- self .maximum = np .array (maximum ).astype (float )
132- maximumstr = "Model maximum: {} {} {}" .format (
133- self .maximum [0 ], self .maximum [1 ], self .maximum [2 ]
134- )
135- logger .info (maximumstr )
136-
137- self .scale_factor = 1.0
138-
139- self .bounding_box = BoundingBox (
140- dimensions = 3 ,
141- origin = np .zeros (3 ),
142- maximum = self .maximum - self .origin ,
143- global_origin = self .origin ,
144- )
128+
145129
130+
146131 self .stratigraphic_column = None
147132
148133 self .tol = 1e-10 * np .max (self .bounding_box .maximum - self .bounding_box .origin )
@@ -160,10 +145,7 @@ def to_dict(self):
160145 json = {}
161146 json ["model" ] = {}
162147 json ["model" ]["features" ] = [f .name for f in self .features ]
163- # json["model"]["data"] = self.data.to_json()
164- # json["model"]["origin"] = self.origin.tolist()
165- # json["model"]["maximum"] = self.maximum.tolist()
166- # json["model"]["nsteps"] = self.nsteps
148+ json ['model' ]['bounding_box' ] = self .bounding_box .to_dict ()
167149 json ["model" ]["stratigraphic_column" ] = self .stratigraphic_column
168150 # json["features"] = [f.to_json() for f in self.features]
169151 return json
@@ -192,86 +174,12 @@ def to_dict(self):
192174 # model.features.append(GeologicalFeature.from_json(feature,model))
193175 # return model
194176 def __str__ (self ):
195- lengths = self .maximum - self .origin
196- _str = "GeologicalModel - {} x {} x {}\n " .format (* lengths )
197- _str += "------------------------------------------ \n "
198- _str += "The model contains {} GeologicalFeatures \n " .format (len (self .features ))
199- _str += ""
200- _str += "------------------------------------------ \n "
201- _str += ""
202- _str += "Model origin: {} {} {}\n " .format (self .origin [0 ], self .origin [1 ], self .origin [2 ])
203- _str += "Model maximum: {} {} {}\n " .format (
204- self .maximum [0 ], self .maximum [1 ], self .maximum [2 ]
205- )
206- _str += "Model rescale factor: {} \n " .format (self .scale_factor )
207- _str += "------------------------------------------ \n "
208- _str += "Feature list: \n "
209- for feature in self .features :
210- _str += " {} \n " .format (feature .name )
211- return _str
177+ return f"GeologicalModel with { len (self .features )} features"
212178
213179 def _ipython_key_completions_ (self ):
214180 return self .feature_name_index .keys ()
215181
216- @classmethod
217- def from_map2loop_directory (
218- cls ,
219- m2l_directory ,
220- foliation_params = {},
221- fault_params = {},
222- use_thickness = True ,
223- vector_scale = 1 ,
224- gradient = False ,
225- ** kwargs ,
226- ):
227- """Alternate constructor for a geological model using m2l output
228-
229- Uses the information saved in the map2loop files to build a geological model.
230- You can specify kwargs for building foliation using foliation_params and for
231- faults using fault_params. faults is a flag that allows for the faults to be
232- skipped.
233-
234- Parameters
235- ----------
236- m2l_directory : string
237- path to map2loop directory
238-
239- Returns
240- -------
241- (GeologicalModel, dict)
242- the created geological model and a dictionary of the map2loop data
243-
244- Notes
245- ------
246- For additional information see :class:`LoopStructural.modelling.input.Map2LoopProcessor`
247- and :meth:`LoopStructural.GeologicalModel.from_processor`
248- """
249- from LoopStructural .modelling .input .map2loop_processor import Map2LoopProcessor
250-
251- log_to_file (f"{ m2l_directory } /loopstructural_log.txt" )
252- logger .info ("Creating model from m2l directory" )
253- processor = Map2LoopProcessor (m2l_directory , use_thickness )
254- processor ._gradient = gradient
255- processor .vector_scale = vector_scale
256- for foliation_name in processor .stratigraphic_column .keys ():
257- if foliation_name != "faults" :
258- if foliation_name in foliation_params .keys ():
259- processor .foliation_properties [foliation_name ] = foliation_params [
260- foliation_name
261- ]
262- else :
263- processor .foliation_properties [foliation_name ] = foliation_params
264-
265- for fault_name in processor .fault_names :
266- if fault_name in fault_params .keys ():
267- for param_name , value in fault_params [fault_name ].items ():
268- processor .fault_properties .loc [fault_name , param_name ] = value
269- else :
270- for param_name , value in fault_params .items ():
271- processor .fault_properties .loc [fault_name , param_name ] = value
272-
273- model = GeologicalModel .from_processor (processor )
274- return model , processor
182+
275183
276184 @classmethod
277185 def from_processor (cls , processor ):
@@ -565,13 +473,9 @@ def data(self, data: pd.DataFrame):
565473 raise BaseException ("Cannot load data" )
566474 logger .info (f"Adding data to GeologicalModel with { len (data )} data points" )
567475 self ._data = data .copy ()
568-
569- self ._data ["X" ] -= self .origin [0 ]
570- self ._data ["Y" ] -= self .origin [1 ]
571- self ._data ["Z" ] -= self .origin [2 ]
572- self ._data ["X" ] /= self .scale_factor
573- self ._data ["Y" ] /= self .scale_factor
574- self ._data ["Z" ] /= self .scale_factor
476+ self ._data [['X' ,'Y' ,'Z' ]] = self .bounding_box .project (self ._data [['X' ,'Y' ,'Z' ]].to_numpy ())
477+
478+
575479 if "type" in self ._data :
576480 logger .warning ("'type' is deprecated replace with 'feature_name' \n " )
577481 self ._data .rename (columns = {"type" : "feature_name" }, inplace = True )
@@ -1413,7 +1317,7 @@ def create_and_add_fault(
14131317 if "data_region" in kwargs :
14141318 kwargs .pop ("data_region" )
14151319 logger .error ("kwarg data_region currently not supported, disabling" )
1416- displacement_scaled = displacement / self . scale_factor
1320+ displacement_scaled = displacement
14171321 fault_frame_builder = FaultBuilder (
14181322 interpolatortype ,
14191323 bounding_box = self .bounding_box ,
@@ -1434,11 +1338,11 @@ def create_and_add_fault(
14341338 if fault_center is not None and ~ np .isnan (fault_center ).any ():
14351339 fault_center = self .scale (fault_center , inplace = False )
14361340 if minor_axis :
1437- minor_axis = minor_axis / self . scale_factor
1341+ minor_axis = minor_axis
14381342 if major_axis :
1439- major_axis = major_axis / self . scale_factor
1343+ major_axis = major_axis
14401344 if intermediate_axis :
1441- intermediate_axis = intermediate_axis / self . scale_factor
1345+ intermediate_axis = intermediate_axis
14421346 fault_frame_builder .create_data_from_geometry (
14431347 fault_frame_data = fault_data ,
14441348 fault_center = fault_center ,
@@ -1493,11 +1397,9 @@ def rescale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
14931397 points : np.array((N,3),dtype=double)
14941398
14951399 """
1496- if not inplace :
1497- points = points .copy ()
1498- points *= self .scale_factor
1499- points += self .origin
1500- return points
1400+
1401+ return self .bounding_box .reproject (points ,inplace = inplace )
1402+
15011403
15021404 # TODO move scale to bounding box/transformer
15031405 def scale (self , points : np .ndarray , * , inplace : bool = False ) -> np .ndarray :
@@ -1515,16 +1417,8 @@ def scale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
15151417 points : np.a::rray((N,3),dtype=double)
15161418
15171419 """
1518- points = np .array (points ).astype (float )
1519- if not inplace :
1520- points = points .copy ()
1521- # if len(points.shape) == 1:
1522- # points = points[None,:]
1523- # if len(points.shape) != 2:
1524- # logger.error("cannot scale array of dimensions".format(len(points.shape)))
1525- points -= self .origin
1526- points /= self .scale_factor
1527- return points
1420+ return self .bounding_box .project (np .array (points ).astype (float ),inplace = inplace )
1421+
15281422
15291423 def regular_grid (self , * , nsteps = None , shuffle = True , rescale = False , order = "C" ):
15301424 """
@@ -1673,7 +1567,7 @@ def evaluate_fault_displacements(self, points, scale=True):
16731567 if f .type == FeatureType .FAULT :
16741568 disp = f .displacementfeature .evaluate_value (points )
16751569 vals [~ np .isnan (disp )] += disp [~ np .isnan (disp )]
1676- return vals * - self . scale_factor # convert from restoration magnutude to displacement
1570+ return vals # convert from restoration magnutude to displacement
16771571
16781572 def get_feature_by_name (self , feature_name ) -> GeologicalFeature :
16791573 """Returns a feature from the mode given a name
0 commit comments