From c1b35b9253d917145363cf3f47dedcddfa2d6aab Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Wed, 16 Jul 2025 13:06:07 +0930 Subject: [PATCH 01/10] feat: sample storage supervisor class --- map2loop/sample_storage.py | 226 +++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 map2loop/sample_storage.py diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py new file mode 100644 index 00000000..3094c225 --- /dev/null +++ b/map2loop/sample_storage.py @@ -0,0 +1,226 @@ + + + + + +class SampleSupervisor: + """ + The SampleSupervisor class is responsible for managing the samples and samplers in the project. + It extends the AccessStorage abstract base class. + + Attributes: + storage_label (str): The label of the storage. + samples (list): A list of samples. + samplers (list): A list of samplers. + sampler_dirtyflags (list): A list of flags indicating if the sampler has changed. + dirtyflags (list): A list of flags indicating the state of the data, sample or sampler. + project (Project): The project associated with the SampleSupervisor. + map_data (MapData): The map data associated with the project. + """ + + def __init__(self, project: "Project"): + """ + The constructor for the SampleSupervisor class. + + Args: + project (Project): The Project class associated with the SampleSupervisor. + """ + + self.storage_label = "SampleSupervisor" + self.samples = [None] * len(SampleType) + self.samplers = [None] * len(SampleType) + self.sampler_dirtyflags = [True] * len(SampleType) + self.dirtyflags = [True] * len(StateType) + self.set_default_samplers() + self.project = project + self.map_data = project.map_data + + def type(self): + return self.storage_label + + def set_default_samplers(self): + """ + Initialisation function to set or reset the point samplers + """ + self.samplers[SampleType.STRUCTURE] = SamplerDecimator(1) + self.samplers[SampleType.FAULT_ORIENTATION] = SamplerDecimator(1) + self.samplers[SampleType.GEOLOGY] = SamplerSpacing(50.0) + self.samplers[SampleType.FAULT] = SamplerSpacing(50.0) + self.samplers[SampleType.FOLD] = SamplerSpacing(50.0) + self.samplers[SampleType.CONTACT] = SamplerSpacing(50.0) + self.samplers[SampleType.DTM] = SamplerSpacing(50.0) + # dirty flags to false after initialisation + self.sampler_dirtyflags = [False] * len(SampleType) + + @beartype.beartype + def set_sampler(self, sampletype: SampleType, sampler: Sampler): + """ + Set the point sampler for a specific datatype + + Args: + sampletype (Datatype): + The sample type (SampleType) to use this sampler on + sampler (Sampler): + The sampler to use + """ + self.samplers[sampletype] = sampler + # set the dirty flag to True to indicate that the sampler has changed + self.sampler_dirtyflags[sampletype] = True + + @beartype.beartype + def get_sampler(self, sampletype: SampleType): + """ + Get the sampler name being used for a datatype + + Args: + sampletype: The sample type of the sampler + + Returns: + str: The name of the sampler being used on the specified datatype + """ + return self.samplers[sampletype].sampler_label + + @beartype.beartype + def store(self, sampletype: SampleType, data): + """ + Stores the sample data. + + Args: + sampletype (SampleType): The type of the sample. + data: The sample data to store. + """ + + # store the sample data + self.samples[sampletype] = data + self.sampler_dirtyflags[sampletype] = False + + @beartype.beartype + def check_state(self, sampletype: SampleType): + """ + Checks the state of the data, sample and sampler. + + Args: + sampletype (SampleType): The type of the sample. + """ + + self.dirtyflags[StateType.DATA] = self.map_data.dirtyflags[sampletype] + self.dirtyflags[StateType.SAMPLER] = self.sampler_dirtyflags[sampletype] + + @beartype.beartype + def load(self, sampletype: SampleType): + """ + Loads the map data or raster map data based on the sample type. + + Args: + sampletype (SampleType): The type of the sample. + """ + datatype = Datatype(sampletype) + + if datatype == Datatype.DTM: + self.map_data.load_raster_map_data(datatype) + + else: + # load map data + self.map_data.load_map_data(datatype) + + @beartype.beartype + def process(self, sampletype: SampleType): + """ + Processes the sample based on the sample type. + + Args: + sampletype (SampleType): The type of the sample. + """ + + if sampletype == SampleType.CONTACT: + self.store( + SampleType.CONTACT, + self.samplers[SampleType.CONTACT].sample( + self.map_data.basal_contacts, self.map_data + ), + ) + + else: + datatype = Datatype(sampletype) + self.store( + sampletype, + self.samplers[sampletype].sample( + self.map_data.get_map_data(datatype), self.map_data + ), + ) + + @beartype.beartype + def reprocess(self, sampletype: SampleType): + """ + Reprocesses the data based on the sample type. + + Args: + sampletype (SampleType): The type of the sample. + """ + + if sampletype == SampleType.GEOLOGY or sampletype == SampleType.CONTACT: + self.map_data.extract_all_contacts() + + if self.project.stratigraphic_column.column is None: + self.project.calculate_stratigraphic_order() + + else: + self.project.sort_stratigraphic_column() + + self.project.extract_geology_contacts() + self.process(SampleType.GEOLOGY) + + elif sampletype == SampleType.STRUCTURE: + self.process(SampleType.STRUCTURE) + + elif sampletype == SampleType.FAULT: + self.project.calculate_fault_orientations() + self.project.summarise_fault_data() + self.process(SampleType.FAULT) + + elif sampletype == SampleType.FOLD: + self.process(SampleType.FOLD) + + @beartype.beartype + def __call__(self, sampletype: SampleType): + """ + Checks the state of the data, sample and sampler, and returns + the requested sample after reprocessing if necessary. + + Args: + sampletype (SampleType): The type of the sample. + + Returns: + The requested sample. + """ + + # check the state of the data, sample and sampler + self.check_state(sampletype) + + # run the sampler only if no sample was generated before + if self.samples[sampletype] is None: + # if the data is changed, load and reprocess the data and generate a new sample + if self.dirtyflags[StateType.DATA] is True: + self.load(sampletype) + self.reprocess(sampletype) + return self.samples[sampletype] + + if self.dirtyflags[StateType.DATA] is False: + self.process(sampletype) + return self.samples[sampletype] + + # return the requested sample after reprocessing if the data is changed + elif self.samples[sampletype] is not None: + if self.dirtyflags[StateType.DATA] is False: + if self.dirtyflags[StateType.SAMPLER] is True: + self.reprocess(sampletype) + return self.samples[sampletype] + + if self.dirtyflags[StateType.SAMPLER] is False: + return self.samples[sampletype] + + if self.dirtyflags[StateType.DATA] is True: + + self.load(sampletype) + self.reprocess(sampletype) + return self.samples[sampletype] \ No newline at end of file From b2ce6ff95c16db3f4818fb9976b9a3dbc6a5d380 Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Wed, 16 Jul 2025 13:57:06 +0930 Subject: [PATCH 02/10] refactor: simplify SampleSupervisor class --- map2loop/sample_storage.py | 129 ++++--------------------------------- 1 file changed, 12 insertions(+), 117 deletions(-) diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 3094c225..67582a31 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -1,6 +1,7 @@ - - +from .m2l_enums import Datatype, SampleType, StateType +from .sampler import SamplerDecimator, SamplerSpacing, Sampler +import beartype class SampleSupervisor: @@ -79,54 +80,24 @@ def get_sampler(self, sampletype: SampleType): str: The name of the sampler being used on the specified datatype """ return self.samplers[sampletype].sampler_label - - @beartype.beartype - def store(self, sampletype: SampleType, data): - """ - Stores the sample data. - - Args: - sampletype (SampleType): The type of the sample. - data: The sample data to store. - """ - - # store the sample data - self.samples[sampletype] = data - self.sampler_dirtyflags[sampletype] = False - + @beartype.beartype - def check_state(self, sampletype: SampleType): + def get_sample(self, sampletype: SampleType): """ - Checks the state of the data, sample and sampler. + Get a sample given a sample type Args: - sampletype (SampleType): The type of the sample. - """ - - self.dirtyflags[StateType.DATA] = self.map_data.dirtyflags[sampletype] - self.dirtyflags[StateType.SAMPLER] = self.sampler_dirtyflags[sampletype] - - @beartype.beartype - def load(self, sampletype: SampleType): - """ - Loads the map data or raster map data based on the sample type. + sampletype: The sample type of the sampler - Args: - sampletype (SampleType): The type of the sample. + Returns: + str: The name of the sampler being used on the specified datatype """ - datatype = Datatype(sampletype) - - if datatype == Datatype.DTM: - self.map_data.load_raster_map_data(datatype) - - else: - # load map data - self.map_data.load_map_data(datatype) + return self.samples[sampletype] @beartype.beartype - def process(self, sampletype: SampleType): + def sample(self, sampletype: SampleType, ): """ - Processes the sample based on the sample type. + sample sample based on the sample type. Args: sampletype (SampleType): The type of the sample. @@ -148,79 +119,3 @@ def process(self, sampletype: SampleType): self.map_data.get_map_data(datatype), self.map_data ), ) - - @beartype.beartype - def reprocess(self, sampletype: SampleType): - """ - Reprocesses the data based on the sample type. - - Args: - sampletype (SampleType): The type of the sample. - """ - - if sampletype == SampleType.GEOLOGY or sampletype == SampleType.CONTACT: - self.map_data.extract_all_contacts() - - if self.project.stratigraphic_column.column is None: - self.project.calculate_stratigraphic_order() - - else: - self.project.sort_stratigraphic_column() - - self.project.extract_geology_contacts() - self.process(SampleType.GEOLOGY) - - elif sampletype == SampleType.STRUCTURE: - self.process(SampleType.STRUCTURE) - - elif sampletype == SampleType.FAULT: - self.project.calculate_fault_orientations() - self.project.summarise_fault_data() - self.process(SampleType.FAULT) - - elif sampletype == SampleType.FOLD: - self.process(SampleType.FOLD) - - @beartype.beartype - def __call__(self, sampletype: SampleType): - """ - Checks the state of the data, sample and sampler, and returns - the requested sample after reprocessing if necessary. - - Args: - sampletype (SampleType): The type of the sample. - - Returns: - The requested sample. - """ - - # check the state of the data, sample and sampler - self.check_state(sampletype) - - # run the sampler only if no sample was generated before - if self.samples[sampletype] is None: - # if the data is changed, load and reprocess the data and generate a new sample - if self.dirtyflags[StateType.DATA] is True: - self.load(sampletype) - self.reprocess(sampletype) - return self.samples[sampletype] - - if self.dirtyflags[StateType.DATA] is False: - self.process(sampletype) - return self.samples[sampletype] - - # return the requested sample after reprocessing if the data is changed - elif self.samples[sampletype] is not None: - if self.dirtyflags[StateType.DATA] is False: - if self.dirtyflags[StateType.SAMPLER] is True: - self.reprocess(sampletype) - return self.samples[sampletype] - - if self.dirtyflags[StateType.SAMPLER] is False: - return self.samples[sampletype] - - if self.dirtyflags[StateType.DATA] is True: - - self.load(sampletype) - self.reprocess(sampletype) - return self.samples[sampletype] \ No newline at end of file From 32c1dbb3f8be41cbe5549cdcd3db505eb850cd4d Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Wed, 16 Jul 2025 15:46:49 +0930 Subject: [PATCH 03/10] refactor: update sample retrieval method name --- map2loop/sample_storage.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 67582a31..52f1220a 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -32,9 +32,6 @@ def __init__(self, project: "Project"): self.samplers = [None] * len(SampleType) self.sampler_dirtyflags = [True] * len(SampleType) self.dirtyflags = [True] * len(StateType) - self.set_default_samplers() - self.project = project - self.map_data = project.map_data def type(self): return self.storage_label @@ -82,7 +79,7 @@ def get_sampler(self, sampletype: SampleType): return self.samplers[sampletype].sampler_label @beartype.beartype - def get_sample(self, sampletype: SampleType): + def get_samples(self, sampletype: SampleType): """ Get a sample given a sample type @@ -95,7 +92,7 @@ def get_sample(self, sampletype: SampleType): return self.samples[sampletype] @beartype.beartype - def sample(self, sampletype: SampleType, ): + def sample(self, sampletype: SampleType): """ sample sample based on the sample type. From 52b51337dfddf1d8322ae46ae112911bcb71466a Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Fri, 25 Jul 2025 13:07:30 +0800 Subject: [PATCH 04/10] SampleSupervisor --- map2loop/contact_sampler.py | 53 +++++++++++++++++++ map2loop/fault_orientation_sampler.py | 23 +++++++++ map2loop/m2l_enums.py | 10 ++++ map2loop/sample_storage.py | 73 +++++++++++++++++---------- map2loop/structure_sampler.py | 36 +++++++++++++ 5 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 map2loop/contact_sampler.py create mode 100644 map2loop/fault_orientation_sampler.py create mode 100644 map2loop/structure_sampler.py diff --git a/map2loop/contact_sampler.py b/map2loop/contact_sampler.py new file mode 100644 index 00000000..0b2cee85 --- /dev/null +++ b/map2loop/contact_sampler.py @@ -0,0 +1,53 @@ +from .sampler import SamplerSpacing +from .m2l_enums import Datatype, SampleType +from .contact_extractor import ContactExtractor +from .utils import hex_to_rgb, set_z_values_from_raster_df + +class ContactSampler(SamplerSpacing): + def __init__(self, spacing=50.0, + dtm_data=None, + geology_data=None, + fault_data=None, + stratigraphic_column=None, + ): + super().__init__(spacing, dtm_data, geology_data) + self.sampler_label = "ContactSampler" + self.stratigraphic_column = stratigraphic_column + self.fault_data = fault_data + self.contact_extractor = None + + def get_contact_extractor(self): + if self.contact_extractor is None: + self.contact_extractor = ContactExtractor( + self.geology_data, + self.fault_data + ) + return self.contact_extractor + + + def extract_all_contacts(self): + extractor = self.get_contact_extractor() + contacts = extractor.extract_all_contacts() + return contacts + + + def extract_basal_contacts(self): + extractor = self.get_contact_extractor() + if extractor.contacts is None: + self.extract_all_contacts() + + basal_contacts = extractor.extract_basal_contacts(self.stratigraphic_column) + + return basal_contacts + + def sample(self, spatial_data): + if spatial_data is None: + basal_contacts = self.extract_basal_contacts() + else: + basal_contacts = spatial_data + + sampled_contacts = super().sample(basal_contacts) + + set_z_values_from_raster_df(self.dtm_data, sampled_contacts) + + return sampled_contacts \ No newline at end of file diff --git a/map2loop/fault_orientation_sampler.py b/map2loop/fault_orientation_sampler.py new file mode 100644 index 00000000..e4c216ae --- /dev/null +++ b/map2loop/fault_orientation_sampler.py @@ -0,0 +1,23 @@ +from map2loop.fault_orientation import FaultOrientationNearest +from .m2l_enums import Datatype, SampleType +from .sampler import Sampler +from .utils import set_z_values_from_raster_df + +class FaultOrientationSampler(Sampler): + def __init__(self, dtm_data=None, geology_data=None, fault_data=None,map_data=None): + super().__init__(dtm_data,geology_data) + self.sampler_label = "FaultOrientationSampler" + self.fault_data = fault_data + self.fault_orientation = FaultOrientationNearest() + + def sample(self, spatial_data): + + fault_orientations = self.fault_orientation.calculate( + self.fault_data, + spatial_data, + self.map_data + ) + + set_z_values_from_raster_df(self.dtm_data, fault_orientations) + + return fault_orientations \ No newline at end of file diff --git a/map2loop/m2l_enums.py b/map2loop/m2l_enums.py index f390361a..626ae9e1 100644 --- a/map2loop/m2l_enums.py +++ b/map2loop/m2l_enums.py @@ -30,3 +30,13 @@ class VerboseLevel(IntEnum): NONE = 0 TEXTONLY = 1 ALL = 2 + +class SampleType(IntEnum): + GEOLOGY = 0 + STRUCTURE = 1 + FAULT = 2 + FOLD = 3 + DTM = 4 + FAULT_ORIENTATION = 5 + CONTACT = 6 + BASAL_CONTACT = 7 \ No newline at end of file diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 52f1220a..c02f13e9 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -1,7 +1,16 @@ -from .m2l_enums import Datatype, SampleType, StateType +from .m2l_enums import Datatype, SampleType from .sampler import SamplerDecimator, SamplerSpacing, Sampler import beartype +from .mapdata import MapData +from .stratigraphic_column import StratigraphicColumn +from .fault_orientation_sampler import FaultOrientationSampler +from .contact_sampler import ContactSampler +from .structure_sampler import StructureSampler + +from .logging import getLogger + +logger = getLogger(__name__) class SampleSupervisor: @@ -19,7 +28,7 @@ class SampleSupervisor: map_data (MapData): The map data associated with the project. """ - def __init__(self, project: "Project"): + def __init__(self, project: "Project", map_data: MapData, stratigraphic_column: StratigraphicColumn ): """ The constructor for the SampleSupervisor class. @@ -28,10 +37,12 @@ def __init__(self, project: "Project"): """ self.storage_label = "SampleSupervisor" + self.map_data = map_data + self.stratigraphic_column = stratigraphic_column self.samples = [None] * len(SampleType) self.samplers = [None] * len(SampleType) self.sampler_dirtyflags = [True] * len(SampleType) - self.dirtyflags = [True] * len(StateType) + self.set_default_samplers() def type(self): return self.storage_label @@ -40,13 +51,19 @@ def set_default_samplers(self): """ Initialisation function to set or reset the point samplers """ - self.samplers[SampleType.STRUCTURE] = SamplerDecimator(1) - self.samplers[SampleType.FAULT_ORIENTATION] = SamplerDecimator(1) - self.samplers[SampleType.GEOLOGY] = SamplerSpacing(50.0) - self.samplers[SampleType.FAULT] = SamplerSpacing(50.0) - self.samplers[SampleType.FOLD] = SamplerSpacing(50.0) - self.samplers[SampleType.CONTACT] = SamplerSpacing(50.0) - self.samplers[SampleType.DTM] = SamplerSpacing(50.0) + + geology_data = self.map_data.get_map_data(Datatype.GEOLOGY) + dtm_data = self.map_data.get_map_data(Datatype.DTM) + fault_data = self.map_data.get_map_data(Datatype.FAULT) + + self.samplers[SampleType.STRUCTURE] = StructureSampler(decimation=1,dtm_data=dtm_data,geology_data=geology_data) + self.samplers[SampleType.GEOLOGY] = SamplerSpacing(spacing=50.0) + self.samplers[SampleType.FAULT] = SamplerSpacing(spacing=50.0) + self.samplers[SampleType.FOLD] = SamplerSpacing(spacing=50.0) + self.samplers[SampleType.DTM] = SamplerSpacing(spacing=50.0) + self.samplers[SampleType.CONTACT] = ContactSampler(spacing=50.0,geology_data=geology_data,fault_data=fault_data, stratigraphic_column=self.stratigraphic_column.column) + self.samplers[SampleType.FAULT_ORIENTATION] = FaultOrientationSampler(dtm_data=dtm_data, geology_data=geology_data, + fault_data=fault_data, map_data=self.map_data) # dirty flags to false after initialisation self.sampler_dirtyflags = [False] * len(SampleType) @@ -56,7 +73,7 @@ def set_sampler(self, sampletype: SampleType, sampler: Sampler): Set the point sampler for a specific datatype Args: - sampletype (Datatype): + sampletype (SampleType): The sample type (SampleType) to use this sampler on sampler (Sampler): The sampler to use @@ -89,7 +106,21 @@ def get_samples(self, sampletype: SampleType): Returns: str: The name of the sampler being used on the specified datatype """ + if sampletype == SampleType.BASAL_CONTACT: + if self.samples[sampletype] is None: + contact_sampler = self.samplers[SampleType.CONTACT] + basal_contacts = contact_sampler.extract_basal_contacts() + self.store(SampleType.BASAL_CONTACT, basal_contacts) + return self.samples[sampletype] + + if self.samples[sampletype] is None or self.sampler_dirtyflags[sampletype]: + self.sample(sampletype) return self.samples[sampletype] + + @beartype.beartype + def store(self, sampletype: SampleType, sample_data): + self.samples[sampletype] = sample_data + self.sampler_dirtyflags[sampletype] = False @beartype.beartype def sample(self, sampletype: SampleType): @@ -100,19 +131,7 @@ def sample(self, sampletype: SampleType): sampletype (SampleType): The type of the sample. """ - if sampletype == SampleType.CONTACT: - self.store( - SampleType.CONTACT, - self.samplers[SampleType.CONTACT].sample( - self.map_data.basal_contacts, self.map_data - ), - ) - - else: - datatype = Datatype(sampletype) - self.store( - sampletype, - self.samplers[sampletype].sample( - self.map_data.get_map_data(datatype), self.map_data - ), - ) + datatype = Datatype(sampletype) + spatial_data = self.map_data.get_map_data(datatype) + sampled_data = self.samplers[sampletype].sample(spatial_data) + self.store(sampletype, sampled_data) diff --git a/map2loop/structure_sampler.py b/map2loop/structure_sampler.py new file mode 100644 index 00000000..feaa82ef --- /dev/null +++ b/map2loop/structure_sampler.py @@ -0,0 +1,36 @@ +from .sampler import SamplerDecimator +from .m2l_enums import Datatype +from .utils import set_z_values_from_raster_df +import geopandas +import pandas + +class StructureSampler(SamplerDecimator): + def __init__(self, decimation: int = 1,dtm_data=None, geology_data=None): + super().__init__(decimation,dtm_data,geology_data) + self.sampler_label = "StructureSampler" + + def sample(self, spatial_data): + + data = spatial_data.copy() + data["X"] = data.geometry.x + data["Y"] = data.geometry.y + + if self.dtm_data is not None: + result = set_z_values_from_raster_df(self.dtm_data, data) + if result is not None: + data["Z"] = result["Z"] + else: + data["Z"] = None + else: + data["Z"] = None + + if self.geology_data is not None: + data["layerID"] = geopandas.sjoin( + data, self.geology_data, how='left' + )['index_right'] + else: + data["layerID"] = None + + data.reset_index(drop=True, inplace=True) + + return pandas.DataFrame(data[:: self.decimation].drop(columns="geometry")) \ No newline at end of file From aaa825dd0548b8f0e86cf33602d731e7fa42f2a6 Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Fri, 25 Jul 2025 14:04:44 +0800 Subject: [PATCH 05/10] delete unnecessary StructureSampler --- map2loop/sample_storage.py | 3 +-- map2loop/sampler.py | 2 +- map2loop/structure_sampler.py | 36 ----------------------------------- 3 files changed, 2 insertions(+), 39 deletions(-) delete mode 100644 map2loop/structure_sampler.py diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index c02f13e9..0ee820a7 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -6,7 +6,6 @@ from .stratigraphic_column import StratigraphicColumn from .fault_orientation_sampler import FaultOrientationSampler from .contact_sampler import ContactSampler -from .structure_sampler import StructureSampler from .logging import getLogger @@ -56,7 +55,7 @@ def set_default_samplers(self): dtm_data = self.map_data.get_map_data(Datatype.DTM) fault_data = self.map_data.get_map_data(Datatype.FAULT) - self.samplers[SampleType.STRUCTURE] = StructureSampler(decimation=1,dtm_data=dtm_data,geology_data=geology_data) + self.samplers[SampleType.STRUCTURE] = SamplerDecimator(decimation=1, dtm_data=dtm_data, geology_data=geology_data) self.samplers[SampleType.GEOLOGY] = SamplerSpacing(spacing=50.0) self.samplers[SampleType.FAULT] = SamplerSpacing(spacing=50.0) self.samplers[SampleType.FOLD] = SamplerSpacing(spacing=50.0) diff --git a/map2loop/sampler.py b/map2loop/sampler.py index b4c7835c..4e31db28 100644 --- a/map2loop/sampler.py +++ b/map2loop/sampler.py @@ -132,7 +132,7 @@ def __init__(self, spacing: float = 50.0, dtm_data: Optional[gdal.Dataset] = Non """ super().__init__(dtm_data, geology_data) self.sampler_label = "SamplerSpacing" - spacing = max(spacing, 1.0) + spacing = max(spacing, 50.0) self.spacing = spacing diff --git a/map2loop/structure_sampler.py b/map2loop/structure_sampler.py deleted file mode 100644 index feaa82ef..00000000 --- a/map2loop/structure_sampler.py +++ /dev/null @@ -1,36 +0,0 @@ -from .sampler import SamplerDecimator -from .m2l_enums import Datatype -from .utils import set_z_values_from_raster_df -import geopandas -import pandas - -class StructureSampler(SamplerDecimator): - def __init__(self, decimation: int = 1,dtm_data=None, geology_data=None): - super().__init__(decimation,dtm_data,geology_data) - self.sampler_label = "StructureSampler" - - def sample(self, spatial_data): - - data = spatial_data.copy() - data["X"] = data.geometry.x - data["Y"] = data.geometry.y - - if self.dtm_data is not None: - result = set_z_values_from_raster_df(self.dtm_data, data) - if result is not None: - data["Z"] = result["Z"] - else: - data["Z"] = None - else: - data["Z"] = None - - if self.geology_data is not None: - data["layerID"] = geopandas.sjoin( - data, self.geology_data, how='left' - )['index_right'] - else: - data["layerID"] = None - - data.reset_index(drop=True, inplace=True) - - return pandas.DataFrame(data[:: self.decimation].drop(columns="geometry")) \ No newline at end of file From 6e768da8fd6423e9b13f0d869108dbf33111e2a0 Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Fri, 25 Jul 2025 14:43:57 +0800 Subject: [PATCH 06/10] fix contact sample logic --- map2loop/contact_sampler.py | 8 ++------ map2loop/sample_storage.py | 7 +++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/map2loop/contact_sampler.py b/map2loop/contact_sampler.py index 0b2cee85..76a13d1d 100644 --- a/map2loop/contact_sampler.py +++ b/map2loop/contact_sampler.py @@ -1,7 +1,7 @@ from .sampler import SamplerSpacing from .m2l_enums import Datatype, SampleType from .contact_extractor import ContactExtractor -from .utils import hex_to_rgb, set_z_values_from_raster_df +from .utils import set_z_values_from_raster_df class ContactSampler(SamplerSpacing): def __init__(self, spacing=50.0, @@ -41,11 +41,7 @@ def extract_basal_contacts(self): return basal_contacts def sample(self, spatial_data): - if spatial_data is None: - basal_contacts = self.extract_basal_contacts() - else: - basal_contacts = spatial_data - + basal_contacts = self.extract_basal_contacts() sampled_contacts = super().sample(basal_contacts) set_z_values_from_raster_df(self.dtm_data, sampled_contacts) diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 0ee820a7..98079a80 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -112,6 +112,13 @@ def get_samples(self, sampletype: SampleType): self.store(SampleType.BASAL_CONTACT, basal_contacts) return self.samples[sampletype] + if sampletype == SampleType.CONTACT: + if self.samples[sampletype] is None or self.sampler_dirtyflags[sampletype]: + contact_sampler = self.samplers[SampleType.CONTACT] + sampled_contacts = contact_sampler.sample(None) + self.store(SampleType.CONTACT, sampled_contacts) + return self.samples[sampletype] + if self.samples[sampletype] is None or self.sampler_dirtyflags[sampletype]: self.sample(sampletype) return self.samples[sampletype] From 05e035e4167c51398b604a328aa5e7895201986f Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Mon, 28 Jul 2025 13:02:32 +0800 Subject: [PATCH 07/10] fix sample function in SampleSupervisor --- map2loop/contact_sampler.py | 2 +- map2loop/sample_storage.py | 45 +++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/map2loop/contact_sampler.py b/map2loop/contact_sampler.py index 76a13d1d..e14a17eb 100644 --- a/map2loop/contact_sampler.py +++ b/map2loop/contact_sampler.py @@ -40,7 +40,7 @@ def extract_basal_contacts(self): return basal_contacts - def sample(self, spatial_data): + def sample(self, spatial_data=None): basal_contacts = self.extract_basal_contacts() sampled_contacts = super().sample(basal_contacts) diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 98079a80..3d6f04cc 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -103,24 +103,8 @@ def get_samples(self, sampletype: SampleType): sampletype: The sample type of the sampler Returns: - str: The name of the sampler being used on the specified datatype + The sample data associated with the specified sample type """ - if sampletype == SampleType.BASAL_CONTACT: - if self.samples[sampletype] is None: - contact_sampler = self.samplers[SampleType.CONTACT] - basal_contacts = contact_sampler.extract_basal_contacts() - self.store(SampleType.BASAL_CONTACT, basal_contacts) - return self.samples[sampletype] - - if sampletype == SampleType.CONTACT: - if self.samples[sampletype] is None or self.sampler_dirtyflags[sampletype]: - contact_sampler = self.samplers[SampleType.CONTACT] - sampled_contacts = contact_sampler.sample(None) - self.store(SampleType.CONTACT, sampled_contacts) - return self.samples[sampletype] - - if self.samples[sampletype] is None or self.sampler_dirtyflags[sampletype]: - self.sample(sampletype) return self.samples[sampletype] @beartype.beartype @@ -135,8 +119,35 @@ def sample(self, sampletype: SampleType): Args: sampletype (SampleType): The type of the sample. + + Returns: + The sample data for the specified sample type """ + if self.samples[sampletype] is not None and not self.sampler_dirtyflags[sampletype]: + return self.samples[sampletype] + if sampletype == SampleType.BASAL_CONTACT: + self._sample_basal_contact() + elif sampletype == SampleType.CONTACT: + self._sample_contact() + else: + self._sample_other_types(sampletype) + return self.samples[sampletype] + + @beartype.beartype + def _sample_basal_contact(self): + contact_sampler = self.samplers[SampleType.CONTACT] + basal_contacts = contact_sampler.extract_basal_contacts() + self.store(SampleType.BASAL_CONTACT, basal_contacts) + + @beartype.beartype + def _sample_contact(self): + contact_sampler = self.samplers[SampleType.CONTACT] + sampled_contacts = contact_sampler.sample() + self.store(SampleType.CONTACT, sampled_contacts) + + @beartype.beartype + def _sample_other_types(self, sampletype: SampleType): datatype = Datatype(sampletype) spatial_data = self.map_data.get_map_data(datatype) sampled_data = self.samplers[sampletype].sample(spatial_data) From ead72ed3db4a8e6da8f27e908931f0dc87aa02b5 Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Mon, 28 Jul 2025 13:43:33 +0800 Subject: [PATCH 08/10] fix set_sampler in SampleSupervisor --- map2loop/sample_storage.py | 55 ++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 3d6f04cc..5298a586 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -55,32 +55,65 @@ def set_default_samplers(self): dtm_data = self.map_data.get_map_data(Datatype.DTM) fault_data = self.map_data.get_map_data(Datatype.FAULT) - self.samplers[SampleType.STRUCTURE] = SamplerDecimator(decimation=1, dtm_data=dtm_data, geology_data=geology_data) - self.samplers[SampleType.GEOLOGY] = SamplerSpacing(spacing=50.0) - self.samplers[SampleType.FAULT] = SamplerSpacing(spacing=50.0) - self.samplers[SampleType.FOLD] = SamplerSpacing(spacing=50.0) - self.samplers[SampleType.DTM] = SamplerSpacing(spacing=50.0) - self.samplers[SampleType.CONTACT] = ContactSampler(spacing=50.0,geology_data=geology_data,fault_data=fault_data, stratigraphic_column=self.stratigraphic_column.column) - self.samplers[SampleType.FAULT_ORIENTATION] = FaultOrientationSampler(dtm_data=dtm_data, geology_data=geology_data, - fault_data=fault_data, map_data=self.map_data) + self._set_decimator_sampler(SampleType.STRUCTURE, decimation=1) + self._set_spacing_sampler(SampleType.GEOLOGY, spacing=50.0) + self._set_spacing_sampler(SampleType.FAULT, spacing=50.0) + self._set_spacing_sampler(SampleType.FOLD, spacing=50.0) + self._set_spacing_sampler(SampleType.DTM, spacing=50.0) + self._set_contact_sampler(SampleType.CONTACT, spacing=50.0) + self._set_fault_orientation_sampler(SampleType.FAULT_ORIENTATION) + # dirty flags to false after initialisation self.sampler_dirtyflags = [False] * len(SampleType) @beartype.beartype - def set_sampler(self, sampletype: SampleType, sampler: Sampler): + def set_sampler(self, sampletype: SampleType, sampler_type: str, **kwargs): """ Set the point sampler for a specific datatype Args: sampletype (SampleType): The sample type (SampleType) to use this sampler on - sampler (Sampler): + samplertype (str): The sampler to use """ - self.samplers[sampletype] = sampler + if sampler_type == "SamplerDecimator": + self._set_decimator_sampler(sampletype, **kwargs) + elif sampler_type == "SamplerSpacing": + self._set_spacing_sampler(sampletype, **kwargs) + elif sampler_type == "ContactSampler": + self._set_contact_sampler(sampletype, **kwargs) + elif sampler_type == "FaultOrientationSampler": + self._set_fault_orientation_sampler(sampletype, **kwargs) + else: + raise ValueError('incorrect sampler type') + # set the dirty flag to True to indicate that the sampler has changed self.sampler_dirtyflags[sampletype] = True + @beartype.beartype + def _set_decimator_sampler(self, sampletype, decimation=1): + geology_data = self.map_data.get_map_data(Datatype.GEOLOGY) + dtm_data = self.map_data.get_map_data(Datatype.DTM) + self.samplers[sampletype] = SamplerDecimator(decimation=decimation, dtm_data=dtm_data, geology_data=geology_data) + + @beartype.beartype + def _set_spacing_sampler(self, sampletype, spacing=50.0): + self.samplers[sampletype] = SamplerSpacing(spacing=spacing) + + @beartype.beartype + def _set_contact_sampler(self, sampletype, spacing=50.0): + geology_data = self.map_data.get_map_data(Datatype.GEOLOGY) + fault_data = self.map_data.get_map_data(Datatype.FAULT) + self.samplers[sampletype] = ContactSampler(spacing=spacing,geology_data=geology_data,fault_data=fault_data, stratigraphic_column=self.stratigraphic_column.column) + + @beartype.beartype + def _set_fault_orientation_sampler(self, sampletype): + geology_data = self.map_data.get_map_data(Datatype.GEOLOGY) + dtm_data = self.map_data.get_map_data(Datatype.DTM) + fault_data = self.map_data.get_map_data(Datatype.FAULT) + self.samplers[sampletype] = FaultOrientationSampler(dtm_data=dtm_data, geology_data=geology_data, fault_data=fault_data, map_data=self.map_data) + @beartype.beartype def get_sampler(self, sampletype: SampleType): """ From d37b0631e820bc853a02749ee39073197b0dff6c Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Mon, 28 Jul 2025 14:36:05 +0800 Subject: [PATCH 09/10] add verification in set_sampler in SampleSupervisor --- map2loop/sample_storage.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/map2loop/sample_storage.py b/map2loop/sample_storage.py index 5298a586..1d2b8de4 100644 --- a/map2loop/sample_storage.py +++ b/map2loop/sample_storage.py @@ -66,6 +66,21 @@ def set_default_samplers(self): # dirty flags to false after initialisation self.sampler_dirtyflags = [False] * len(SampleType) + def _verify_sampler_type(self, sampletype: SampleType, sampler_type: str): + allowed_samplers = { + SampleType.STRUCTURE: ["SamplerDecimator"], + SampleType.GEOLOGY: ["SamplerSpacing"], + SampleType.FAULT: ["SamplerSpacing"], + SampleType.FOLD: ["SamplerSpacing"], + SampleType.DTM: ["SamplerSpacing"], + SampleType.CONTACT: ["ContactSampler"], + SampleType.FAULT_ORIENTATION: ["FaultOrientationSampler"] + } + + if sampletype in allowed_samplers and sampler_type not in allowed_samplers[sampletype]: + allowed = ", ".join(allowed_samplers[sampletype]) + raise ValueError(f"Invalid sampler type '{sampler_type}' for sample '{sampletype}', please use {allowed}") + @beartype.beartype def set_sampler(self, sampletype: SampleType, sampler_type: str, **kwargs): """ @@ -77,6 +92,8 @@ def set_sampler(self, sampletype: SampleType, sampler_type: str, **kwargs): samplertype (str): The sampler to use """ + self._verify_sampler_type(sampletype, sampler_type) + if sampler_type == "SamplerDecimator": self._set_decimator_sampler(sampletype, **kwargs) elif sampler_type == "SamplerSpacing": From f6bceae7a237bf516e999e8d4ea18dd9a35cfe77 Mon Sep 17 00:00:00 2001 From: Noelle Cheng Date: Mon, 28 Jul 2025 15:13:43 +0800 Subject: [PATCH 10/10] fix SamplerSpacing spacing --- map2loop/sampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map2loop/sampler.py b/map2loop/sampler.py index 4e31db28..b4c7835c 100644 --- a/map2loop/sampler.py +++ b/map2loop/sampler.py @@ -132,7 +132,7 @@ def __init__(self, spacing: float = 50.0, dtm_data: Optional[gdal.Dataset] = Non """ super().__init__(dtm_data, geology_data) self.sampler_label = "SamplerSpacing" - spacing = max(spacing, 50.0) + spacing = max(spacing, 1.0) self.spacing = spacing