Repository: theroggy/cropclassification (language: python)

Issue:
ENH: maybe merge config of [calc_periodic_mosaic_params] and [marker] regarding data used?
Since https://github.com/theroggy/cropclassification/pull/68, parameters like `start_date`, `end_date`,... exist in two sections, which gives double maintenance of parameter + increased risk on configuration errors.

Additionally, some parameters that exist in [calc_periodic_mosaic_params] would also be useful to have in [marker] (e.g. `end_date_subtract_days`), so to avoid more duplication... they would better be "merged" before duplicating them.

Should be looked into a bit further what is the best way forward before implementation.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cffd18d4..32bc4e67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,28 @@
 # CHANGELOG
 
-## 0.3 (yyyy-mm-dd)
+## 0.3.0 (yyyy-mm-dd)
 
 ### Deprecations and compatibility notes
 
 - Consolidated some landcover pre-processing ignore codes (#120)
+- Restructure + cleanup of configuration file, mainly to avoid duplicate keys and to
+  improve parameter names:
+    - Consolidate and rename `start_date_str`, `end_date_str` and
+      `end_date_subtract_days` from sections `marker` and `calc_periodic_mosaic_params`
+      to respectively `start_date`, `end_date` and `images_available_delay` in a new
+      section `period`.
+    - Consolidate `roi_bounds`, `roi_crs` and `roi_name` from
+      sections `marker` and `calc_periodic_mosaic_params` to a new section `roi`.
+    - Consolidate `marker.sensordata_to_use` and
+      `calc_periodic_mosaic_params.imageprofiles_to_get` to `images.images`.
+    - Move `marker.buffer` to section `timeseries`.
+    - Move `marker.image_profiles_config_filepath` to
+      `paths.image_profiles_config_filepath`
+    - Move `marker.year` to `period.year`.
+    - Drop `calc_periodic_mosaic_params.dest_image_data_dir` and use
+      `dirs.images_periodic_dir` instead.
+    - Rename section `dirs` to `paths`.
+    - Remove `general.run_id`.
 
 ### Improvements
 
@@ -21,7 +39,7 @@
 - Fix `calc_periodic_mosaic_task` to read `roi_bounds` parameter as list of floats from
   config (#130)
 
-## 0.2 (2024-06-17)
+## 0.2.0 (2024-06-17)
 
 ### Deprecations and compatibility notes
 
diff --git a/bin_util/calc_dias_weekly.py b/bin_util/calc_dias_weekly.py
index 2adac630..e8cf2a6f 100644
--- a/bin_util/calc_dias_weekly.py
+++ b/bin_util/calc_dias_weekly.py
@@ -19,7 +19,7 @@ def main():
     conf.read_config(config_paths)
 
     # Init logging
-    base_log_dir = conf.dirs.getpath("log_dir")
+    base_log_dir = conf.paths.getpath("log_dir")
     log_dir = (
         base_log_dir / f"{os.sep}calc_dias_weekly{datetime.now():%Y-%m-%d_%H-%M-%S}"
     )
@@ -29,8 +29,8 @@ def main():
     logger.info(f"Config used: \n{conf.pformat_config()}")
 
     # Get the config needed
-    timeseries_per_image_dir = conf.dirs.getpath("timeseries_per_image_dir")
-    timeseries_periodic_dir = conf.dirs.getpath("timeseries_periodic_dir")
+    timeseries_per_image_dir = conf.paths.getpath("timeseries_per_image_dir")
+    timeseries_periodic_dir = conf.paths.getpath("timeseries_periodic_dir")
 
     # Input features file depends on the year
     if year == 2017:
@@ -44,14 +44,14 @@ def main():
         raise Exception(f"Not a valid year: {year}")
 
     # Calculate!
-    input_parcel_path = conf.dirs.getpath("input_dir") / input_features_filename
+    input_parcel_path = conf.paths.getpath("input_dir") / input_features_filename
     ts_helper.calculate_periodic_timeseries(
         parcel_path=input_parcel_path,
         timeseries_per_image_dir=timeseries_per_image_dir,
         start_date=datetime.fromisoformat(f"{year}-03-15"),
         end_date=datetime.fromisoformat(f"{year}-08-15"),
         sensordata_to_get=["SENSORDATA_S1_COHERENCE"],
-        dest_data_dir=timeseries_periodic_dir,
+        timeseries_periodic_dir=timeseries_periodic_dir,
         force=False,
     )
 
diff --git a/bin_util/calc_overlaps.py b/bin_util/calc_overlaps.py
index ba8fa5a3..a2c984b3 100644
--- a/bin_util/calc_overlaps.py
+++ b/bin_util/calc_overlaps.py
@@ -22,14 +22,16 @@ def main():
 
     # Main initialisation of the logging
     log_level = conf.general.get("log_level")
-    logger = log_helper.main_log_init(conf.dirs.getpath("log_dir"), __name__, log_level)
+    logger = log_helper.main_log_init(
+        conf.paths.getpath("log_dir"), __name__, log_level
+    )
     logger.info("Start")
     logger.info(f"Config used: \n{conf.pformat_config()}")
 
     logger.info(pprint.pformat(dict(os.environ)))
 
     # Init variables
-    input_preprocessed_dir = conf.dirs.getpath("input_preprocessed_dir")
+    input_preprocessed_dir = conf.paths.getpath("input_preprocessed_dir")
     overlap_path = input_preprocessed_dir / "Prc_BEFL_2019_2019-07-02_bufm5_32632.gpkg"
 
     # Read parcels file to memory (isn't that large...)
diff --git a/bin_util/calc_periodic_mosaic.py b/bin_util/calc_periodic_mosaic.py
index ad1c0f82..9a9b6526 100644
--- a/bin_util/calc_periodic_mosaic.py
+++ b/bin_util/calc_periodic_mosaic.py
@@ -15,13 +15,13 @@ def main():
     start_date = datetime(2023, 2, 6)
     end_date = datetime(2023, 7, 30)
     roi_bounds = [20_000, 150_000, 260_000, 245_000]
-    dest_image_data_dir = Path("//dg3.be/alp/Datagis/satellite_periodic/BEFL")
+    images_periodic_dir = Path("//dg3.be/alp/Datagis/satellite_periodic/BEFL")
 
     # roi_test
     start_date = datetime(2023, 3, 6)
     end_date = datetime(2023, 3, 13)
     roi_bounds = [161_400, 188_000, 161_900, 188_500]
-    dest_image_data_dir = Path("c:/temp/periodic_mosaic/roi_test")
+    images_periodic_dir = Path("c:/temp/periodic_mosaic/roi_test")
 
     imageprofiles_to_get = ["s1-dprvi-asc-weekly", "s1-dprvi-desc-weekly"]
     imageprofiles_to_get = ["s2-agri-weekly"]
@@ -37,7 +37,7 @@ def main():
         roi_crs=roi_crs,
         start_date=start_date,
         end_date=end_date,
-        output_base_dir=dest_image_data_dir,
+        output_base_dir=images_periodic_dir,
         imageprofiles_to_get=imageprofiles_to_get,
         imageprofiles=imageprofiles,
         force=False,
diff --git a/bin_util/recalc_reporting.py b/bin_util/recalc_reporting.py
index 490e149f..7cfcff56 100644
--- a/bin_util/recalc_reporting.py
+++ b/bin_util/recalc_reporting.py
@@ -11,24 +11,26 @@ def main():
     logging.basicConfig(level=logging.INFO)
 
     # Some variables that need to be choosen
+    year = 2023
+    if year == 2022:
+        input_groundtruth_filename = "Prc_BEFL_2022_2024_05_13_groundtruth.tsv"
+    elif year == 2023:
+        input_groundtruth_filename = "Prc_BEFL_2023_2024_05_13_groundtruth.tsv"
+    else:
+        raise ValueError(f"invalid year: {year}")
 
-    input_groundtruth_filename = "Prc_BEFL_2022_2024_05_13_groundtruth.tsv"
     run_dir = Path(
-        "X:/Monitoring/Markers/dev/2022_CROPGROUP/Run_2022-08-02_001_91.63_groundtruth"
+        "X:/Monitoring/Markers/dev/2023_CROPGROUP/Run_2023-08-08_001_prd_groundtruth"
     )
     basedir = run_dir.parent.parent
-    overrules = [
-        "columns.class_declared2=NOT_AVAILABLE",
-        "marker.roi_name=BEFL",
-        # "calc_marker_params.country_code=BEFL",
-    ]
+    overrules = ["columns.class_declared2=NOT_AVAILABLE", "roi.roi_name=BEFL"]
     """
     input_groundtruth_filename = "Prc_BEFL_2022_2024_05_13_groundtruth.tsv"
     run_dir = Path(
         "X:/Monitoring/Markers/dev/2022_CROPGROUP/Run_2024-05-17_008_96.13_rf_min80pct"
     )
     basedir = run_dir.parent.parent
-    overrules = []  # ["marker.roi_name=BEFL"]
+    overrules = []  # ["roi.roi_name=BEFL"]
     """
     # Init
     if not run_dir.exists():
@@ -49,10 +51,10 @@ def main():
 
     conf.read_config(configfiles_used, default_basedir=basedir, overrules=overrules)
 
-    input_dir = conf.dirs.getpath("input_dir")
+    input_dir = conf.paths.getpath("input_dir")
     input_groundtruth_path = input_dir / input_groundtruth_filename
     input_parcel_filetype = conf.calc_marker_params["input_parcel_filetype"]
-    input_preprocessed_dir = conf.dirs.getpath("input_preprocessed_dir")
+    input_preprocessed_dir = conf.paths.getpath("input_preprocessed_dir")
 
     data_ext = conf.general["data_ext"]
     geofile_ext = conf.general["geofile_ext"]
@@ -65,23 +67,23 @@ def main():
         input_preprocessed_dir / f"{input_parcel_filename.stem}{data_ext}"
     )
 
-    buffer = conf.marker.getint("buffer")
+    buffer = conf.timeseries.getfloat("buffer")
     imagedata_input_parcel_filename = (
-        f"{input_parcel_filename.stem}_bufm{buffer}{geofile_ext}"
+        f"{input_parcel_filename.stem}_bufm{buffer:g}{geofile_ext}"
     )
     imagedata_input_parcel_path = (
         input_preprocessed_dir / imagedata_input_parcel_filename
     )
 
     # TODO: periode_name shouldn't be here!
-    base_filename = f"{input_parcel_filename.stem}_bufm{buffer}_weekly"
+    base_filename = f"{input_parcel_filename.stem}_bufm{buffer:g}_weekly"
     period_name = conf.marker.get("period_name", "weekly")
-    timeseries_periodic_dir = conf.dirs.getpath("timeseries_periodic_dir")
+    timeseries_periodic_dir = conf.paths.getpath("timeseries_periodic_dir")
     timeseries_periodic_dir = (
         timeseries_periodic_dir / f"{imagedata_input_parcel_path.stem}_{period_name}"
     )
 
-    refe_dir = conf.dirs.getpath("refe_dir")
+    refe_dir = conf.paths.getpath("refe_dir")
     classes_refe_filename = conf.calc_marker_params.getpath("classes_refe_filename")
     classes_refe_path = refe_dir / classes_refe_filename
 
diff --git a/cropclassification/calc_marker.py b/cropclassification/calc_marker.py
index d2484825..2ea50b31 100644
--- a/cropclassification/calc_marker.py
+++ b/cropclassification/calc_marker.py
@@ -59,7 +59,7 @@ def calc_marker_task(
         "reuse_last_run_dir_config"
     )
     run_dir = dir_helper.create_run_dir(
-        conf.dirs.getpath("marker_dir"), reuse_last_run_dir
+        conf.paths.getpath("marker_dir"), reuse_last_run_dir
     )
     print(run_dir)
     if not run_dir.exists():
@@ -134,7 +134,7 @@ def calc_marker_task(
     # Prepare input paths
     if input_model_to_use_relativepath is not None:
         input_model_to_use_path = (
-            conf.dirs.getpath("model_dir") / input_model_to_use_relativepath
+            conf.paths.getpath("model_dir") / input_model_to_use_relativepath
         )
         if not input_model_to_use_path.exists():
             raise Exception(
@@ -144,14 +144,14 @@ def calc_marker_task(
     else:
         input_model_to_use_path = None
 
-    input_dir = conf.dirs.getpath("input_dir")
+    input_dir = conf.paths.getpath("input_dir")
     input_parcel_path = input_dir / input_parcel_filename
     if input_groundtruth_filename is not None:
         input_groundtruth_path = input_dir / input_groundtruth_filename
     else:
         input_groundtruth_path = None
 
-    refe_dir = conf.dirs.getpath("refe_dir")
+    refe_dir = conf.paths.getpath("refe_dir")
     classes_refe_path = refe_dir / classes_refe_filename
 
     # Check if the necessary input files exist...
@@ -175,13 +175,13 @@ def calc_marker_task(
     # Prepare the input data for optimal image data extraction:
     #    1) apply a negative buffer on the parcel to evade mixels
     #    2) remove features that became null because of buffer
-    input_preprocessed_dir = conf.dirs.getpath("input_preprocessed_dir")
-    buffer = conf.marker.getint("buffer")
+    input_preprocessed_dir = conf.paths.getpath("input_preprocessed_dir")
+    buffer = conf.timeseries.getfloat("buffer")
     input_parcel_nogeo_path = (
         input_preprocessed_dir / f"{input_parcel_filename.stem}{data_ext}"
     )
     imagedata_input_parcel_filename = (
-        f"{input_parcel_filename.stem}_bufm{buffer}{geofile_ext}"
+        f"{input_parcel_filename.stem}_bufm{buffer:g}{geofile_ext}"
     )
     imagedata_input_parcel_path = (
         input_preprocessed_dir / imagedata_input_parcel_filename
@@ -197,25 +197,22 @@ def calc_marker_task(
     # Get the time series data (eg. S1, S2,...) to be used for the classification
     # Result: data is put in files in timeseries_periodic_dir, in one file per
     #         date/period
-    period_name = conf.marker.get("period_name", "weekly")
-    timeseries_periodic_dir = conf.dirs.getpath("timeseries_periodic_dir")
-    timeseries_periodic_dir = (
-        timeseries_periodic_dir / f"{imagedata_input_parcel_path.stem}_{period_name}"
-    )
-    start_date = datetime.fromisoformat(conf.marker["start_date_str"])
-    end_date = datetime.fromisoformat(conf.marker["end_date_str"])
-    sensordata_to_use = conf.parse_sensordata_to_use(conf.marker["sensordata_to_use"])
+    timeseries_periodic_dir = conf.paths.getpath("timeseries_periodic_dir")
+    timeseries_periodic_dir /= f"{imagedata_input_parcel_path.stem}"
+    start_date = datetime.fromisoformat(conf.period["start_date"])
+    end_date = datetime.fromisoformat(conf.period["end_date"])
+    images_to_use = conf.parse_image_config(conf.images["images"])
     parceldata_aggregations_to_use = conf.marker.getlist(
         "parceldata_aggregations_to_use"
     )
     ts.calc_timeseries_data(
         input_parcel_path=imagedata_input_parcel_path,
-        roi_bounds=tuple(conf.marker.getlistfloat("roi_bounds")),
-        roi_crs=pyproj.CRS.from_user_input(conf.marker.get("roi_crs")),
+        roi_bounds=tuple(conf.roi.getlistfloat("roi_bounds")),
+        roi_crs=pyproj.CRS.from_user_input(conf.roi.get("roi_crs")),
         start_date=start_date,
         end_date=end_date,
-        sensordata_to_get=sensordata_to_use,
-        dest_data_dir=timeseries_periodic_dir,
+        images_to_use=images_to_use,
+        timeseries_periodic_dir=timeseries_periodic_dir,
     )
 
     # STEP 3: Preprocess all data needed for the classification
@@ -235,7 +232,7 @@ def calc_marker_task(
     classtype_to_prepare = conf.preprocess["classtype_to_prepare"]
     min_parcels_in_class = conf.preprocess.getint("min_parcels_in_class")
     parcel_path = run_dir / f"{input_parcel_filename.stem}_parcel{data_ext}"
-    base_filename = f"{input_parcel_filename.stem}_bufm{buffer}_weekly"
+    base_filename = f"{input_parcel_filename.stem}_bufm{buffer:g}_weekly"
     class_pre.prepare_input(
         input_parcel_path=input_parcel_nogeo_path,
         input_parcel_filetype=input_parcel_filetype,
@@ -259,7 +256,7 @@ def calc_marker_task(
         output_path=parcel_classification_data_path,
         start_date=start_date,
         end_date=end_date,
-        sensordata_to_use=sensordata_to_use,
+        images_to_use=images_to_use,
         parceldata_aggregations_to_use=parceldata_aggregations_to_use,
     )
 
diff --git a/cropclassification/calc_periodic_mosaic.py b/cropclassification/calc_periodic_mosaic.py
index 9eaa15aa..bb0afe3c 100644
--- a/cropclassification/calc_periodic_mosaic.py
+++ b/cropclassification/calc_periodic_mosaic.py
@@ -24,39 +24,36 @@ def calc_periodic_mosaic_task(config_paths: list[Path], default_basedir: Path):
 
     # Initialisation of the logging
     log_level = conf.general.get("log_level")
-    log_dir = conf.dirs.getpath("log_dir")
+    log_dir = conf.paths.getpath("log_dir")
     logger = log_helper.main_log_init(log_dir, __name__, log_level)
     logger.info(f"Config used: \n{conf.pformat_config()}")
 
     # Init some variables
-    start_date = datetime.fromisoformat(
-        conf.calc_periodic_mosaic_params["start_date_str"]
-    )
-    end_date_str = conf.calc_periodic_mosaic_params["end_date_str"]
+    start_date = datetime.fromisoformat(conf.period["start_date"])
+    end_date_str = conf.period["end_date"]
     if end_date_str == "{now}":
-        end_date = datetime.now()
+        now = datetime.now()
+        end_date = datetime(now.year, now.month, now.day)
     else:
-        end_date = datetime.fromisoformat(
-            conf.calc_periodic_mosaic_params["end_date_str"]
-        )
-    end_date_subtract_days = conf.calc_periodic_mosaic_params["end_date_subtract_days"]
-    if end_date_subtract_days is not None:
-        end_date = end_date - timedelta(int(end_date_subtract_days))
-
-    imageprofiles_to_get = conf.calc_periodic_mosaic_params.getlist(
-        "imageprofiles_to_get"
-    )
+        end_date = datetime.fromisoformat(conf.period["end_date"])
+    images_available_delay = conf.period["images_available_delay"]
+    if images_available_delay is not None:
+        now = datetime.now()
+        today = datetime(now.year, now.month, now.day)
+        end_date = today - timedelta(int(images_available_delay))
+
+    imageprofiles_to_get = list(conf.parse_image_config(conf.images["images"]))
     imageprofiles = conf._get_image_profiles(
-        Path(conf.marker["image_profiles_config_filepath"])
+        Path(conf.paths["image_profiles_config_filepath"])
     )
 
     if not conf.calc_periodic_mosaic_params.getboolean("simulate"):
         _ = mosaic_util.calc_periodic_mosaic(
-            roi_bounds=tuple(conf.marker.getlistfloat("roi_bounds")),
-            roi_crs=conf.calc_periodic_mosaic_params.getint("roi_crs"),
+            roi_bounds=tuple(conf.roi.getlistfloat("roi_bounds")),
+            roi_crs=conf.roi.getint("roi_crs"),
             start_date=start_date,
             end_date=end_date,
-            output_base_dir=Path(conf.dirs["images_periodic_dir"]),
+            output_base_dir=conf.paths.getpath("images_periodic_dir"),
             imageprofiles_to_get=imageprofiles_to_get,
             imageprofiles=imageprofiles,
             force=False,
diff --git a/cropclassification/cropclassification.py b/cropclassification/cropclassification.py
index d252cf3f..0c655939 100644
--- a/cropclassification/cropclassification.py
+++ b/cropclassification/cropclassification.py
@@ -74,8 +74,8 @@ def cropclassification(tasksdir: Path, config_overrules: list[str] = []):
         # Now get the info we want from the task config
         action = task_config["task"].get("action")
         default_basedir = None
-        if "dirs" in task_config:
-            default_basedir = Path(task_config["dirs"].get("marker_basedir"))
+        if "paths" in task_config:
+            default_basedir = Path(task_config["paths"].get("marker_basedir"))
         if default_basedir is None:
             default_basedir = task_path.parent.parent
 
diff --git a/cropclassification/general.ini b/cropclassification/general.ini
index 065d3591..9f28abf0 100644
--- a/cropclassification/general.ini
+++ b/cropclassification/general.ini
@@ -1,17 +1,24 @@
 # This is a config file with the shared/default settings for all markers. 
 
-# The job section contains information about the (cropclassification) task you want to run 
+# The task section contains information about the task you want to run 
 [task]
 
 # The action you want to run:
-#    - calc_timeseries: calculate a Sentinel timeseries for a file with polygons
-#    - calc_maker: calculate a marker
+#   - calc_timeseries: calculate a Sentinel timeseries for a file with polygons
+#   - calc_maker: calculate a marker
+#   - calc_periodic_mosaic: calculate periodic mosai
 action = MUST_OVERRIDE
 
-# Extra config files to load, in addition to general defaults
+# Extra config files to load, in addition to general defaults.
+# Possibilities to specify them:
+#   - Absolute paths
+#   - Relative paths: will be resolved towards the `default_basedir` or to the parent of
+#     the tasks directory
+#   - The {task_path} placeholder will be replaced by the current task file's path
+#   - The {tasks_dir} placeholder will be replaced by the current task file's directory
 extra_config_files_to_load = MUST_OVERRIDE
 
-# The calc_marker_params section contains information to run a calc_marker task
+# The calc_marker_params section contains information to run a calc_marker task.
 [calc_timeseries_params]
 
 # input files
@@ -19,15 +26,14 @@ input_parcel_filename = MUST_OVERRIDE
 # if you wandt to do a test run, set to True
 test = False
 
-# The calc_marker_params section contains information to run a calc_marker task
+# The `calc_marker_params` section contains information for this specific `calc_marker`
+# task. Permanent configuration of the marker can be found in e.g. the `marker` section.
 [calc_marker_params]
 
-# The type of the input file: typically country code
-country_code = MUST_OVERRIDE
 # The filename of the vector (polygon) geofile
 input_parcel_filename = MUST_OVERRIDE
 # The type of the input file (= which preprocessing is needed/can be done)  
-input_parcel_filetype = ${country_code}
+input_parcel_filetype = MUST_OVERRIDE
 # The lookup table (LUT) file where classes to classify to are specified
 classes_refe_filename = MUST_OVERRIDE
 # A groundtruth file for extra reporting, optional
@@ -42,31 +48,10 @@ reuse_last_run_dir_config = False
 # The calc_periodic_mosaic_params section contains information to run a calc_marker task
 [calc_periodic_mosaic_params]
 
-# Depending on the period_name specified in the ImageProfiles, the `start_date` and
-# `end_date` will be adjusted:
-#   - "weekly": image periods will start on a monday and end on a sunday. If the
-#     `start_date` is no monday, the first monday following it will be used. If the
-#     `end_date` (exclusive) is no monday, the first monday before it will be used.
-#   - "biweekly": image periods will start on even mondays of the year. If the
-#      `start_date` is no monday, the first even monday following it will be used.
-#      If the `end_date` (exclusive) is no monday, the first even monday before it
-#      will be used.
-# The start date of the period we want to download the mosaic
-start_date_str = MUST_OVERRIDE
-# The end date of the period we want to download the mosaic
-# Also supports placeholder {now}, to use the current date
-end_date_str = MUST_OVERRIDE
-# The days before the end_date
-# If not None, subtract end_date_subtract_days from end_date
-end_date_subtract_days = 3
-# The image profiles to get
-imageprofiles_to_get = s2-agri-weekly
-# The CRS of the roi
-roi_crs = 31370
 # Simulate, nothing is being downloaded
 simulate = False
 
-# The general section contains some general confiuration ;-)
+# The general section contains some general configuration ;-).
 [general]
 
 # File format (extension) to use for structured (table) data
@@ -83,20 +68,8 @@ log_level = INFO
 # number of available CPUs.
 nb_parallel = -1
 
-[images]
-
-# What to do if an image is missing:
-#   - ignore: ignore that the image, don't try to download it
-#   - calculate_raise: calculate the image and raise an error if it fails
-#   - calculate_ignore: calculate the image and ignore the error if it fails
-on_missing_image = calculate_raise
-
-[marker]
-# markertype, must be overriden in child ini files
-markertype = MUST_OVERRIDE
-
-# Year to use, should be overridden when running
-year = MUST_OVERRIDE
+# Configuration regarding the region of interest (roi) we want to use.
+[roi]
 
 # The bounds of the roi to use to prepare images,...
 roi_bounds = MUST_OVERRIDE
@@ -105,21 +78,85 @@ roi_crs = MUST_OVERRIDE
 # The name of the roi
 roi_name = MUST_OVERRIDE
 
+# Configuration of the period we want to use.
+[period]
+
+# Year to use. Should be overridden when used.
+year = MUST_OVERRIDE
+
+# Depending on the `period_name` specified in the ImageProfiles for the images to be
+# generated/used, the `start_date` and `end_date` will be adjusted:
+#   - "weekly": image periods will start on a monday and end on a sunday. If the
+#     `start_date` is no monday, the first monday following it will be used. If the
+#     `end_date` (exclusive) is no monday, the first monday before it will be used.
+#   - "biweekly": image periods will start on even mondays of the year. If the
+#      `start_date` is no monday, the first even monday following it will be used.
+#      If the `end_date` (exclusive) is no monday, the first even monday before it
+#      will be used.
+
+# The start date of the period we want to use. The period starts at 00h of this day.
+start_date = MUST_OVERRIDE
+# The end date of the period we want to use.
+# The period ends at 23h59 of the PREVIOUS day, so the end_date is exclusive. Supports
+# placeholder {now}, to use the current date.
+# Note that `end_date` will be overridden by (date.today() - `images_available_delay`)
+# if that date is sooner than `end_date`.
+end_date = MUST_OVERRIDE
+
+# The number of days it can take for images to become available.
+# If specified, the end date will be calculated as the minimum of `end_date` and
+# (date.today() - `images_available_delay`). This avoids mosaics being generated before
+# all images needed for them are available.
+images_available_delay = 3
+
+# Configuration of the images we want to use.
+[images]
+
+# Images to get/use. This should basically be a list of existing image profile names as
+# configured in imageprofile.ini.
+# These are some examples of possible image profile names:
+#   - s2-landcover: Sentinel 2 landcover, based on SCL mask
+#   - s2-ndvi: Sentinel 2 ndvi
+#   - s2-agri: Sentinel 2 bands relevant for agriculture (B02, B03, B04, B08, B11, B12)
+#   - s1-grd-sigma0-asc: Sentinel 1 GRD, ascending orbit (VV, VH)
+#   - s1-grd-sigma0-desc: Sentinel 1 GRD, descending orbit (VV, VH)
+#   - s1-coh: Sentinel 1, 6 day or 12 day coherence, depending on availability (VV, VH)
+#
+# Optionally, it is possible to specify a subset of bands available in ImageProfile to
+# be used e.g. in a marker. When a subset of bands is specified here this will be
+# ignored for the purpose of loading image mosaics,... Image mosaics will always contain
+# all bands defined in the image profile.
+#   eg. [..., {"s2-grd-sigma0-asc": ["VV", "VH"]}]
+images = MUST_OVERRIDE
+
+# What to do if an image is missing:
+#   - calculate_raise: calculate the image and raise an error if it fails. This is the default value.
+#   - ignore: ignore that image, don't try to download it.
+#   - calculate_ignore: calculate the image and ignore the error if it fails.
+on_missing_image = calculate_raise
 
-# For more information about how the start and end dates map to image periodes, check
-# the calc_periodic_mosaic_params section.
-# Remark: year will be replace in run-time
-# start date of timeseries data to use
-start_date_str = ${year}-03-27 
-# end date of timeseries data to use, NOT inclusive
-end_date_str = ${year}-08-10
+# Configuration on how/which periodic images/timeseries statistics should be generated.
+[timeseries]
 
-# negative buffer to apply to input parcels
+# Negative buffer to apply to input parcels to account for mixels.
 buffer = 5
-# minimum number of pixels that should be inside the buffered input parcels
+
+# The maximum percentage cloudcover an (S2) image can have to be used.
+max_cloudcover_pct = 15
+
+# The min percentage of parcels that need to have valid data for a time+sensor to use it 
+min_parcels_with_data_pct = 80
+
+# Configuration specific to the marker being calculated.
+[marker]
+
+# markertype, must be overriden in child ini files.
+markertype = MUST_OVERRIDE
+
+# minimum number of pixels that should be inside the (buffered) input parcels.
 min_nb_pixels = -2 
-# minimum number of pixels that should be inside the buffered input parcels used when
-# training
+# minimum number of pixels that should be inside the (buffered) input parcels used when
+# training.
 min_nb_pixels_train = ${marker:min_nb_pixels}
 
 # classes that will be ignored for training + won't receive a prediction
@@ -148,24 +185,6 @@ classes_to_ignore_unimportant_default = IGNORE_UNIMPORTANT, IGNORE_LATE_MAINCROP
 #   * BALANCING_STRATEGY_EQUAL: for each input class, the same amount of samples is used as training. For classes with few samples, (samples will be duplicated if needed)
 balancing_strategy = BALANCING_STRATEGY_MEDIUM2
 
-# Sensordata to use for the markers. This is a list of ImageProfile names:
-#   - S1dB: Sentinel 1 data, in dB
-#   - S1ascDesc: Sentinel 1 data, divided in Ascending and Descending passes
-#   - S1dBAscDesc: Sentinel 1 data, in dB, divided in Ascending and Descending passes
-#   - S1Coh: Sentinel 1 coherence
-#   - S2gt95: Sentinel 2 data (B2,B3,B4,B8) IF available for 95% or area
-#
-#   - s2-landcover: Sentinel 2 landcover, based on SCL mask
-#   - s2-ndvi: Sentinel 2 ndvi
-#   - s2-agri: Sentinel 2 bands relevant for agriculture (B02, B03, B04, B08, B11, B12)
-#   - s1-grd-sigma0-asc: Sentinel 1 GRD, ascending orbit (VV, VH)
-#   - s1-grd-sigma0-desc: Sentinel 1 GRD, descending orbit (VV, VH)
-#   - s1-coh: Sentinel 1, 6 day or 12 day coherence, depending on availability (VV, VH)
-#
-# Optionally, it is possible to specify the bands of the ImageProfile to be used like this:
-#   eg. [..., {"s2-grd-sigma0-asc": ["VV", "VH"]}]
-sensordata_to_use = ["s2-agri", {"s1-grd-sigma0-asc": ["VV", "VH"]}, {"s1-grd-sigma0-desc": ["VV", "VH"]}]
-
 # The aggregation type to use on parcel level. Following options are avalable:
 #    - mean: the mean of the pixel values in a parcel
 #    - median: the median of the pixel values in a parcel
@@ -175,17 +194,9 @@ parceldata_aggregations_to_use = median
 # Postprocess...
 postprocess_to_groups = 
 
-# Path to the image profiles
-image_profiles_config_filepath = ${dirs:marker_basedir}/_config/image_profiles.ini
-
-[timeseries]
-# The maximum percentage cloudcover an (S2) image can have to be used
-max_cloudcover_pct = 15
-
-# The min percentage of parcels that need to have valid data for a time+sensor to use it 
-min_parcels_with_data_pct = 80
-
+# Configuration of marker parameters regarding the preprocessing of input files.
 [preprocess]
+
 # 
 dedicated_data_columns = ${columns:id}, ${columns:class}, ${columns:class}_orig, ${columns:class_declared}, ${columns:class_declared}_orig, ${columns:pixcount_s1s2}
 extra_export_columns = CODE_OBJ, LAYER_ID, PRC_ID, VERSIENR, GWSCOD_H
@@ -197,7 +208,9 @@ classtype_to_prepare_groundtruth = ${classtype_to_prepare}-GROUNDTRUTH
 # min amount of parcels that need to be in a specific class or it will get moved into IGNORE_NOT_ENOUGH_SAMPLES
 min_parcels_in_class = 50
 
+# Configuration of marker classifier parameters.
 [classifier]
+
 # The classifier type to use. Currently supported types:
 #     * histgradientboostingclassifier (sklearn)
 #     * keras_multilayer_perceptron (tensorflow)
@@ -236,7 +249,9 @@ multilayer_perceptron_learning_rate_init = 0.001
 #     training loss
 best_model_strategy = VAL_LOSS
 
+# Configuration of marker postprocessing parameters.
 [postprocess]
+
 # Doubt: if highest probability < 2*second probability  
 doubt_proba1_st_2_x_proba2 = MUST_OVERRIDE
 # Doubt: if prediction == input class and highest probability < thresshold
@@ -246,9 +261,11 @@ doubt_pred_eq_input_proba1_st_thresshold = MUST_OVERRIDE
 # Remark: should be floating point number from 0 till 1
 doubt_pred_ne_input_proba1_st_thresshold = MUST_OVERRIDE
 
+# Configuration of columns names to use from the input files for marker calculation.
 [columns]
+
 # The columns to include in the final output file
-output_columns = LAYER_ID, PRC_ID, VERSIENR, markercode, run_id, ${class}, ${prediction_full_alpha}, ${prediction_cons_status}, cons_date, pred1, pred1_prob, pred2, pred2_prob, pred3, pred3_prob, modify_date
+output_columns = LAYER_ID, PRC_ID, VERSIENR, markercode, ${class}, ${prediction_full_alpha}, ${prediction_cons_status}, cons_date, pred1, pred1_prob, pred2, pred2_prob, pred3, pred3_prob, modify_date
 
 # Column name of the id column
 id = UID
@@ -307,8 +324,9 @@ prediction_conclusion_detail_full_alpha = pred_conclusion_detail_full_alpha
 # Column name of the conclusion based on consolidated prediction
 prediction_conclusion_cons = pred_conclusion_cons
 
-[dirs]
-# Directories to use
+# Configuration of paths to use to find/save input/output files.
+[paths]
+
 # data_dir can be specified as a relative path which will be resolved to
 # the parent dir of the tasks dir 
 data_dir = .
@@ -317,13 +335,16 @@ temp_dir = {tmp_dir}/cropclassification
 # marker_basedir can be specified as a relative path which will be resolved to
 # the parent dir of the tasks dir
 marker_basedir = .
-marker_dir = ${dirs:marker_basedir}/${marker:year}_${marker:markertype}
+marker_dir = ${paths:marker_basedir}/${period:year}_${marker:markertype}
 images_periodic_dir = MUST_OVERRIDE
-timeseries_periodic_dir = ${dirs:data_dir}/_ts_periodic
-timeseries_per_image_dir = ${dirs:data_dir}/_ts_per_image
-input_dir = ${dirs:data_dir}/_inputdata
-input_preprocessed_dir = ${dirs:data_dir}/_inputdata_preprocessed
-model_dir = ${dirs:data_dir}/_models
-refe_dir = ${dirs:data_dir}/_refe
-log_dir = ${dirs:marker_basedir}/log
+timeseries_periodic_dir = ${paths:data_dir}/_ts_periodic
+timeseries_per_image_dir = ${paths:data_dir}/_ts_per_image
+input_dir = ${paths:data_dir}/_inputdata
+input_preprocessed_dir = ${paths:data_dir}/_inputdata_preprocessed
+model_dir = ${paths:data_dir}/_models
+refe_dir = ${paths:data_dir}/_refe
+log_dir = ${paths:marker_basedir}/log
 gee = users/pieter_roggemans/
+
+# Path to the image profiles
+image_profiles_config_filepath = ${paths:marker_basedir}/_config/image_profiles.ini
\ No newline at end of file
diff --git a/cropclassification/helpers/config_helper.py b/cropclassification/helpers/config_helper.py
index 25e31695..1de641f3 100644
--- a/cropclassification/helpers/config_helper.py
+++ b/cropclassification/helpers/config_helper.py
@@ -20,6 +20,8 @@
 calc_timeseries_params: Any
 calc_marker_params: Any
 calc_periodic_mosaic_params: Any
+roi: Any
+period: Any
 images: Any
 marker: Any
 timeseries: Any
@@ -27,11 +29,11 @@
 classifier: Any
 postprocess: Any
 columns: Any
-dirs: Any
+paths: Any
 image_profiles: dict[str, ImageProfile]
 
 
-class SensorData:
+class ImageConfig:
     def __init__(
         self,
         imageprofile_name: str,
@@ -141,51 +143,50 @@ def read_config(
     )
     config.read(config_paths)
 
+    # Basic check if the config file is of an old version.
+    if "dirs" in config:
+        raise ValueError(
+            "Old version of config file detected. Please update your config file to "
+            "the new format. More info in changelog of version 0.3.0."
+        )
+
     # If the data_dir parameter is a relative path, try to resolve it towards
     # the default basedir of, if specfied.
-    data_dir = config["dirs"].getpath("data_dir")
+    data_dir = config["paths"].getpath("data_dir")
     if not data_dir.is_absolute():
         if default_basedir is None:
             raise ValueError(
-                "Config parameter dirs.data_dir is relative, but no default_basedir "
+                "Config parameter paths.data_dir is relative, but no default_basedir "
                 "supplied!"
             )
         data_dir_absolute = (default_basedir / data_dir).resolve()
         print(
-            "Config parameter dirs.data_dir was relative, so is now resolved to "
+            "Config parameter paths.data_dir was relative, so is now resolved to "
             f"{data_dir_absolute}"
         )
-        config["dirs"]["data_dir"] = data_dir_absolute.as_posix()
+        config["paths"]["data_dir"] = data_dir_absolute.as_posix()
 
     # If the marker_basedir parameter is a relative path, try to resolve it towards
     # the default basedir of, if specfied.
-    marker_basedir = config["dirs"].getpath("marker_basedir")
+    marker_basedir = config["paths"].getpath("marker_basedir")
     if not marker_basedir.is_absolute():
         if default_basedir is None:
             raise ValueError(
-                "Config parameter dirs.marker_basedir is relative, but no "
+                "Config parameter paths.marker_basedir is relative, but no "
                 "default_basedir supplied!"
             )
         marker_basedir_absolute = (default_basedir / marker_basedir).resolve()
         print(
-            "Config parameter dirs.marker_basedir was relative, so is now resolved to "
+            "Config parameter paths.marker_basedir was relative, so is now resolved to "
             f"{marker_basedir_absolute}"
         )
-        config["dirs"]["marker_basedir"] = marker_basedir_absolute.as_posix()
+        config["paths"]["marker_basedir"] = marker_basedir_absolute.as_posix()
 
     # Fill out placeholder in the temp_dir (if it is there)
     tmp_dir_str = tempfile.gettempdir()
-    config["dirs"]["temp_dir"] = config["dirs"]["temp_dir"].format(tmp_dir=tmp_dir_str)
-
-    # Check some parameters that should be overriden to have a valid config
-    if config["calc_marker_params"].get("country_code") == "MUST_OVERRIDE":
-        raise ValueError("calc_marker_params.country_code must be overridden")
-
-    if config["marker"].get("roi_name") == "MUST_OVERRIDE":
-        raise ValueError("marker.roi_name must be overridden")
-
-    if config["dirs"].get("images_periodic_dir") == "MUST_OVERRIDE":
-        raise ValueError("dirs.images_periodic_dir must be overridden")
+    config["paths"]["temp_dir"] = config["paths"]["temp_dir"].format(
+        tmp_dir=tmp_dir_str
+    )
 
     global config_paths_used
     config_paths_used = config_paths
@@ -202,6 +203,10 @@ def read_config(
         calc_periodic_mosaic_params = config["calc_periodic_mosaic_params"]
     else:
         calc_periodic_mosaic_params = None
+    global roi
+    roi = config["roi"]
+    global period
+    period = config["period"]
     global images
     images = config["images"]
     global marker
@@ -216,52 +221,59 @@ def read_config(
     postprocess = config["postprocess"]
     global columns
     columns = config["columns"]
-    global dirs
-    dirs = config["dirs"]
+    global paths
+    paths = config["paths"]
+
+    # Check some parameters that should be overriden to have a valid config
+    if config["roi"].get("roi_name") == "MUST_OVERRIDE":
+        raise ValueError("roi.roi_name must be overridden")
+
+    if config["paths"].get("images_periodic_dir") == "MUST_OVERRIDE":
+        raise ValueError("paths.images_periodic_dir must be overridden")
 
     # Load image profiles
     global image_profiles
-    image_profiles_config_filepath = marker.getpath("image_profiles_config_filepath")
+    image_profiles_config_filepath = paths.getpath("image_profiles_config_filepath")
     if image_profiles_config_filepath is not None:
         image_profiles = _get_image_profiles(
-            marker.getpath("image_profiles_config_filepath")
+            paths.getpath("image_profiles_config_filepath")
         )
     else:
         # For backwards compatibility: old runs didn't have image profile configuration.
         image_profiles = {}
 
 
-def parse_sensordata_to_use(input) -> dict[str, SensorData]:
+def parse_image_config(input) -> dict[str, ImageConfig]:
     result = None
-    sensordata_parsed = None
+    imageconfig_parsed = None
     try:
-        sensordata_parsed = json.loads(input)
+        imageconfig_parsed = json.loads(input)
     except Exception:
         pass
 
-    if sensordata_parsed is not None:
+    if imageconfig_parsed is not None:
         # It was a json object, so parse as such
         result = {}
-        for imageprofile in sensordata_parsed:
-            if isinstance(imageprofile, str):
-                result[imageprofile] = SensorData(imageprofile)
-            elif isinstance(imageprofile, dict):
-                if len(imageprofile) != 1:
+        for imageconfig in imageconfig_parsed:
+            if isinstance(imageconfig, str):
+                result[imageconfig] = ImageConfig(imageconfig)
+            elif isinstance(imageconfig, dict):
+                if len(imageconfig) != 1:
                     raise ValueError(
-                        "invalid sensordata_to_use: this should be a single key dict: "
-                        f"{imageprofile}"
+                        "Invalid element in json list images.images: this should be a "
+                        f"single key dict, not: {imageconfig}"
                     )
-                imageprofile_name = next(iter(imageprofile.keys()))
-                bands = next(iter(imageprofile.values()))
-                result[imageprofile_name] = SensorData(imageprofile_name, bands=bands)
+                imageprofile_name = next(iter(imageconfig.keys()))
+                bands = next(iter(imageconfig.values()))
+                result[imageprofile_name] = ImageConfig(imageprofile_name, bands=bands)
             else:
                 raise ValueError(
-                    "invalid sensordata_to_use: only str or dict elements allowed, "
-                    f"not: {imageprofile}"
+                    "Invalid element in json list images.images: only str or dict "
+                    f"elements allowed, not: {imageconfig}"
                 )
     else:
         # It was no json object, so it must be a list
-        result = {i.strip(): SensorData(i.strip()) for i in input.split(",")}
+        result = {i.strip(): ImageConfig(i.strip()) for i in input.split(",")}
 
     return result
 
@@ -320,9 +332,11 @@ def _validate_image_profiles(profiles: dict[str, ImageProfile]):
 
 
 def pformat_config():
-    message = f"Config files used: {pprint.pformat(config_paths_used)} \n"
-    message += "Config info listing:\n"
-    message += pprint.pformat(as_dict())
+    message = (
+        f"Config files used: {pprint.pformat(config_paths_used)} \n"
+        "Config info listing:\n"
+        f"{pprint.pformat(as_dict())}"
+    )
 
     return message
 
diff --git a/cropclassification/postprocess/classification_postprocess.py b/cropclassification/postprocess/classification_postprocess.py
index 9d69c65c..ab29944b 100644
--- a/cropclassification/postprocess/classification_postprocess.py
+++ b/cropclassification/postprocess/classification_postprocess.py
@@ -116,7 +116,6 @@ def calc_top3_and_consolidation(
         # First add some aditional columns specific for the export
         pred_output_df = pred_df.copy()
         pred_output_df["markercode"] = conf.marker["markertype"]
-        pred_output_df["run_id"] = conf.general["run_id"]
         today = datetime.date.today()
         pred_output_df["cons_date"] = today
         pred_output_df["modify_date"] = today
@@ -131,7 +130,7 @@ def calc_top3_and_consolidation(
         if conf.marker["markertype"] in ["LANDCOVER", "LANDCOVER-EARLY"]:
             table_name = "mon_marker_landcover"
             table_columns = (
-                "layer_id, prc_id, versienummer, markercode, run_id, cons_input, "
+                "layer_id, prc_id, versienummer, markercode, cons_input, "
                 "cons_landcover, cons_status, cons_date date 'yyyy-mm-dd', landcover1, "
                 "probability1, landcover2, probability2, landcover3, probability3, "
                 "modify_date date 'yyyy-mm-dd'"
@@ -139,7 +138,7 @@ def calc_top3_and_consolidation(
         elif conf.marker["markertype"] in ["CROPGROUP", "CROPGROUP-EARLY"]:
             table_name = "mon_marker_cropgroup"
             table_columns = (
-                "layer_id, prc_id, versienummer, markercode, run_id, cons_input, "
+                "layer_id, prc_id, versienummer, markercode, cons_input, "
                 "cons_cropgroup, cons_status, cons_date date 'yyyy-mm-dd', cropgroup1, "
                 "probability1, cropgroup2, probability2, cropgroup3, probability3, "
                 "modify_date date 'yyyy-mm-dd'"
diff --git a/cropclassification/postprocess/classification_reporting.py b/cropclassification/postprocess/classification_reporting.py
index 3f6abc0c..2c3a9dd2 100644
--- a/cropclassification/postprocess/classification_reporting.py
+++ b/cropclassification/postprocess/classification_reporting.py
@@ -143,25 +143,22 @@ def write_full_report(
         html_data["PARAMETERS_USED_TEXT"] = message
 
         logger.info(f"{dict(conf.marker)}")
-        parameter_list = [["marker", key, value] for key, value in conf.marker.items()]
-        parameter_list += [
+        params = [["marker", key, value] for key, value in conf.marker.items()]
+        params += [
             ["calc_marker_params", key, value] for key, value in conf.timeseries.items()
         ]
-        parameter_list += [
-            ["timeseries", key, value] for key, value in conf.timeseries.items()
-        ]
-        parameter_list += [
-            ["preprocess", key, value] for key, value in conf.preprocess.items()
-        ]
-        parameter_list += [
-            ["classifier", key, value] for key, value in conf.classifier.items()
-        ]
-        parameter_list += [
+        params += [["period", key, value] for key, value in conf.period.items()]
+        params += [["roi", key, value] for key, value in conf.roi.items()]
+        params += [["images", key, value] for key, value in conf.images.items()]
+        params += [["timeseries", key, value] for key, value in conf.timeseries.items()]
+        params += [["preprocess", key, value] for key, value in conf.preprocess.items()]
+        params += [["classifier", key, value] for key, value in conf.classifier.items()]
+        params += [
             ["postprocess", key, value] for key, value in conf.postprocess.items()
         ]
 
         parameters_used_df = pd.DataFrame(
-            parameter_list, columns=["parameter_type", "parameter", "value"]
+            params, columns=["parameter_type", "parameter", "value"]
         )
         with pd.option_context(*pandas_option_context_list):  # type: ignore[arg-type]
             outputfile.write(f"\n{parameters_used_df}\n")
diff --git a/cropclassification/preprocess/_timeseries_calc_openeo.py b/cropclassification/preprocess/_timeseries_calc_openeo.py
index 0e39e37a..2623dc7f 100644
--- a/cropclassification/preprocess/_timeseries_calc_openeo.py
+++ b/cropclassification/preprocess/_timeseries_calc_openeo.py
@@ -24,8 +24,8 @@ def calculate_periodic_timeseries(
     end_date: datetime,
     imageprofiles_to_get: list[str],
     imageprofiles: dict[str, ImageProfile],
-    dest_image_data_dir: Path,
-    dest_data_dir: Path,
+    images_periodic_dir: Path,
+    timeseries_periodic_dir: Path,
     nb_parallel: int,
     on_missing_image: str,
 ):
@@ -33,7 +33,7 @@ def calculate_periodic_timeseries(
     Calculate timeseries data for the input parcels.
 
     args
-        imageprofiles_to_get: an array with data you want to be calculated.
+        imageprofiles_to_get: an array with images you want to be calculated.
         on_missing_image: what to do when an image is missing. Options are:
             - ignore: ignore that the image, don't try to download it
             - calculate_raise: calculate the image and raise an error if it fails
@@ -53,7 +53,7 @@ def calculate_periodic_timeseries(
         roi_crs=roi_crs,
         start_date=start_date,
         end_date=end_date,
-        output_base_dir=dest_image_data_dir,
+        output_base_dir=images_periodic_dir,
         imageprofiles_to_get=imageprofiles_to_get,
         imageprofiles=imageprofiles,
         on_missing_image=on_missing_image,
@@ -71,14 +71,14 @@ def calculate_periodic_timeseries(
                 raise RuntimeError(f"Image {image_info['path']} is missing")
         images_bands.append((image_info["path"], image_info["bands"]))
 
-    temp_dir = conf.dirs.getpath("temp_dir")
+    temp_dir = conf.paths.getpath("temp_dir")
     if temp_dir == "None":
         temp_dir = Path(tempfile.gettempdir())
     zonal_stats_bulk.zonal_stats(
         vector_path=input_parcel_path,
         id_column=conf.columns["id"],
         rasters_bands=images_bands,
-        output_dir=dest_data_dir,
+        output_dir=timeseries_periodic_dir,
         stats=["count", "mean", "median", "std", "min", "max"],  # type: ignore[arg-type]
         engine="pyqgis",
         nb_parallel=nb_parallel,
diff --git a/cropclassification/preprocess/_timeseries_helper.py b/cropclassification/preprocess/_timeseries_helper.py
index 9c0a7cbc..e74bc951 100644
--- a/cropclassification/preprocess/_timeseries_helper.py
+++ b/cropclassification/preprocess/_timeseries_helper.py
@@ -109,7 +109,7 @@ def prepare_input(
     # Apply buffer
     parceldata_buf_gdf = parceldata_gdf.copy()
     # resolution = number of segments per circle
-    buffer_size = -conf.marker.getint("buffer")
+    buffer_size = -conf.timeseries.getfloat("buffer")
     logger.info(f"Apply buffer of {buffer_size} on parcel")
     parceldata_buf_gdf[conf.columns["geom"]] = parceldata_buf_gdf[
         conf.columns["geom"]
@@ -173,7 +173,7 @@ def calculate_periodic_timeseries(
     start_date: datetime,
     end_date: datetime,
     sensordata_to_get: list[str],
-    dest_data_dir: Path,
+    timeseries_periodic_dir: Path,
     force: bool = False,
 ):
     """
@@ -189,7 +189,7 @@ def calculate_periodic_timeseries(
         end_date (datetime): End date. Needs to be aligned already on
             the periods wanted + data on this date is excluded.
         sensordata_to_get ([]):
-        dest_data_dir (Path): [description]
+        timeseries_periodic_dir (Path): Directory the timeseries will be written to.
         force (bool, optional): [description]. Defaults to False.
     """
     logger.info("calculate_periodic_data")
@@ -202,7 +202,7 @@ def calculate_periodic_timeseries(
     year = start_date.year
 
     # Prepare output dir
-    dest_data_dir.mkdir(parents=True, exist_ok=True)
+    timeseries_periodic_dir.mkdir(parents=True, exist_ok=True)
 
     # Create Dataframe with all files with their info
     logger.debug("Create Dataframe with all files and their properties")
@@ -281,7 +281,7 @@ def calculate_periodic_timeseries(
 
         # There should also be one pixcount file
         pixcount_filename = f"{parcel_path.stem}_weekly_pixcount{output_ext}"
-        pixcount_path = dest_data_dir / pixcount_filename
+        pixcount_path = timeseries_periodic_dir / pixcount_filename
 
         # For each week
         start_week = int(datetime.strftime(start_date, "%W"))
@@ -299,7 +299,7 @@ def calculate_periodic_timeseries(
                 f"{parcel_path.stem}_weekly_{period_date_str_long}_{sensordata_type}"
                 f"{output_ext}"
             )
-            period_data_path = dest_data_dir / period_data_filename
+            period_data_path = timeseries_periodic_dir / period_data_filename
 
             # Check if output file exists already
             if period_data_path.exists() and pixcount_path.exists():
diff --git a/cropclassification/preprocess/timeseries.py b/cropclassification/preprocess/timeseries.py
index 26c7ef11..b432bbe6 100644
--- a/cropclassification/preprocess/timeseries.py
+++ b/cropclassification/preprocess/timeseries.py
@@ -24,31 +24,31 @@ def calc_timeseries_data(
     roi_crs: Optional[pyproj.CRS],
     start_date: datetime,
     end_date: datetime,
-    sensordata_to_get: dict[str, conf.SensorData],
-    dest_data_dir: Path,
+    images_to_use: dict[str, conf.ImageConfig],
+    timeseries_periodic_dir: Path,
 ):
     """
     Calculate timeseries data for the input parcels
 
     Args:
         input_parcel_path (str): [description]
-        start_date_str (datetime): [description]
-        end_date_str (datetime): [description]
-        sensordata_to_get (List[str]): an array with data you want to be calculated:
+        start_date (datetime): the start date for the timeseries to generate, inclusive.
+        end_date (datetime): the end date for the timeseries to generate, exclusive.
+        images_to_use (List[str]): an array with data you want to be calculated:
             check out the constants starting with DATA_TO_GET... for the options.
-        dest_data_dir (str): [description]
+        timeseries_periodic_dir (Path): Directory the timeseries will be written to.
     """
     # Check some variables...
-    if sensordata_to_get is None:
+    if images_to_use is None:
         raise Exception("sensordata_to_get cannot be None")
-    if not dest_data_dir.exists():
-        dest_data_dir.mkdir(parents=True, exist_ok=True)
+    if not timeseries_periodic_dir.exists():
+        timeseries_periodic_dir.mkdir(parents=True, exist_ok=True)
 
     sensordata_to_get_onda = [
-        sensor for sensor in sensordata_to_get if sensor not in conf.image_profiles
+        sensor for sensor in images_to_use if sensor not in conf.image_profiles
     ]
     sensordata_to_get_openeo = [
-        sensor for sensor in sensordata_to_get if sensor in conf.image_profiles
+        sensor for sensor in images_to_use if sensor in conf.image_profiles
     ]
 
     if len(sensordata_to_get_onda) > 0:
@@ -60,11 +60,11 @@ def calc_timeseries_data(
         # Now all image data is available per image, calculate periodic data
         ts_helper.calculate_periodic_timeseries(
             parcel_path=input_parcel_path,
-            timeseries_per_image_dir=conf.dirs.getpath("timeseries_per_image_dir"),
+            timeseries_per_image_dir=conf.paths.getpath("timeseries_per_image_dir"),
             start_date=start_date,
             end_date=end_date,
             sensordata_to_get=sensordata_to_get_onda,
-            dest_data_dir=dest_data_dir,
+            timeseries_periodic_dir=timeseries_periodic_dir,
         )
 
     if len(sensordata_to_get_openeo) > 0:
@@ -79,8 +79,8 @@ def calc_timeseries_data(
             end_date=end_date,
             imageprofiles_to_get=sensordata_to_get_openeo,
             imageprofiles=conf.image_profiles,
-            dest_image_data_dir=conf.dirs.getpath("images_periodic_dir"),
-            dest_data_dir=dest_data_dir,
+            images_periodic_dir=conf.paths.getpath("images_periodic_dir"),
+            timeseries_periodic_dir=timeseries_periodic_dir,
             nb_parallel=conf.general.getint("nb_parallel", -1),
             on_missing_image=conf.images.get("on_missing_image", "calculate_raise"),
         )
@@ -93,7 +93,7 @@ def collect_and_prepare_timeseries_data(
     output_path: Path,
     start_date: datetime,
     end_date: datetime,
-    sensordata_to_use: dict[str, conf.SensorData],
+    images_to_use: dict[str, conf.ImageConfig],
     parceldata_aggregations_to_use: list[str],
     force: bool = False,
 ):
@@ -131,20 +131,18 @@ def collect_and_prepare_timeseries_data(
         # Only process data that is of the right sensor types
         fileinfo = ts_helper.get_fileinfo_timeseries_periods(curr_path)
         image_profile = fileinfo["image_profile"].lower()
-        if image_profile not in sensordata_to_use:
-            logger.debug(
-                f"SKIP: file not needed (only {sensordata_to_use}): {curr_path}"
-            )
+        if image_profile not in images_to_use:
+            logger.debug(f"SKIP: file not needed (only {images_to_use}): {curr_path}")
             continue
         # The only data we want to process is the data in the range of dates
         if fileinfo["start_date"] < start_date or fileinfo["end_date"] >= end_date:
             logger.debug(f"SKIP: file doesn't match the period asked: {curr_path}")
             continue
         band = fileinfo["band"]
-        if band not in sensordata_to_use[image_profile].bands:
+        if band not in images_to_use[image_profile].bands:
             logger.debug(f"SKIP: file doesn't match the bands asked: {curr_path}")
             continue
-        time_reducer_asked = sensordata_to_use[image_profile].imageprofile.time_reducer
+        time_reducer_asked = images_to_use[image_profile].imageprofile.time_reducer
         if time_reducer_asked is not None:
             time_reducer = fileinfo.get("time_reducer")
             if time_reducer is None:
diff --git a/sample_marker_basedir/_config/cropgroup.ini b/sample_marker_basedir/_config/cropgroup.ini
index 8c3b6f31..76a05ca4 100644
--- a/sample_marker_basedir/_config/cropgroup.ini
+++ b/sample_marker_basedir/_config/cropgroup.ini
@@ -1,7 +1,28 @@
 # Specific settings for this marker.
 
-[general]
-run_id = 4
+# Configuration of the period we want to use.
+[period]
+
+# Depending on the `period_name` specified in the ImageProfiles for the images to be
+# generated/used, the `start_date` and `end_date` will be adjusted:
+#   - "weekly": image periods will start on a monday and end on a sunday. If the
+#     `start_date` is no monday, the first monday following it will be used. If the
+#     `end_date` (exclusive) is no monday, the first monday before it will be used.
+#   - "biweekly": image periods will start on even mondays of the year. If the
+#      `start_date` is no monday, the first even monday following it will be used.
+#      If the `end_date` (exclusive) is no monday, the first even monday before it
+#      will be used.
+
+# The start date of the period we want to use. The period starts at 00h of this day.
+start_date = ${year}-03-03 
+# end date of timeseries data to use
+# remarks: end date is NOT inclusive + year will be replaced in run-time
+end_date = ${year}-03-20
+
+[images]
+# Images to get/use. This should basically be a list of existing image profile names as
+# configured in imageprofile.ini.
+images = ["s2-agri-weekly", {"s1-grd-sigma0-asc-weekly": ["VV", "VH"]}, {"s1-grd-sigma0-desc-weekly": ["VV", "VH"]}]
 
 [marker]
 markertype = CROPGROUP
@@ -9,13 +30,6 @@ markertype = CROPGROUP
 # strategy to balance the training dataset for the marker.
 balancing_strategy = BALANCING_STRATEGY_MEDIUM2
 
-# start date of timeseries data to use
-# remarks: nearest monday will be used + year will be replaced in run-time
-start_date_str = ${year}-03-03 
-# end date of timeseries data to use
-# remarks: end date is NOT inclusive + year will be replaced in run-time
-end_date_str = ${year}-03-20
-
 # add classes that should be ignored for training, but have to get a prediction
 classes_to_ignore_for_train = ${classes_to_ignore_for_train_default}, MON_ONBEKEND_MET_KLASSIFICATIE
 # add classes that will be ignored for training and won't receive a prediction 
@@ -26,9 +40,6 @@ classes_to_ignore = ${classes_to_ignore_default}, MON_SUBSIDIABEL_NIET, MON_MOEI
 # the other paremeters!
 classes_to_ignore_unimportant = ${classes_to_ignore_unimportant_default}, MON_STAL_GEB, MON_SUBSIDIABEL_NIET, MON_GRASSEN_BRAAK_NIETSUB, MON_MOEILIJK_ZONDER_KLASSIFICATIE_NS
 
-# The sensor data to be used for this marker
-sensordata_to_use = ["s2-agri-weekly", {"s1-grd-sigma0-asc-weekly": ["VV", "VH"]}, {"s1-grd-sigma0-desc-weekly": ["VV", "VH"]}]
-
 [columns]
 # Column name of the classes to prepropress to in the refe file
 class_refe = MON_CROPGROUP
@@ -52,5 +63,3 @@ doubt_proba1_st_2_x_proba2 = False
 doubt_pred_eq_input_proba1_st_pct = 0
 # Doubt: if prediction != input class and highest probability < pct
 doubt_pred_ne_input_proba1_st_pct = 94.5
-
-
diff --git a/sample_marker_basedir/_tasks/local_overrule.ini b/sample_marker_basedir/_tasks/local_overrule.ini
index deb23fc7..b202eaf4 100644
--- a/sample_marker_basedir/_tasks/local_overrule.ini
+++ b/sample_marker_basedir/_tasks/local_overrule.ini
@@ -1,8 +1,15 @@
+# Configuration file with base local overrules for reuse in other configuration files.
+
+# Configuration of marker parameters regarding the preprocessing of input files.
 [preprocess]
-# min amount of parcels that need to be in a specific class or it will get moved into IGNORE_NOT_ENOUGH_SAMPLES
+
+# Minimum amount of parcels that need to be in a specific class or it will get moved
+# into IGNORE_NOT_ENOUGH_SAMPLES.
 min_parcels_in_class = 1
 
-[marker]
+# Configuration regarding the region of interest (roi) we want to use.
+[roi]
+
 # The bounds of the roi to use to prepare images,...
 roi_bounds = 161400, 188000, 161900, 188500
 # The crs the roi bounds are specified in
@@ -10,8 +17,6 @@ roi_crs = 31370
 # Roi name
 roi_name = roi_test
 
-[calc_marker_params]
-country_code = BEFL
-
-[dirs]
-images_periodic_dir = ${dirs:data_dir}//_images_periodic/${marker:roi_name}
\ No newline at end of file
+# Configuration of paths to use to find/save input/output files.
+[paths]
+images_periodic_dir = ${paths:data_dir}//_images_periodic/${roi:roi_name}
\ No newline at end of file


Test patch:
diff --git a/sample_marker_basedir/_tasks/ignore/task_test_calc_marker.ini b/sample_marker_basedir/_tasks/ignore/task_test_calc_marker.ini
index f4d8dbc8..529847f0 100644
--- a/sample_marker_basedir/_tasks/ignore/task_test_calc_marker.ini
+++ b/sample_marker_basedir/_tasks/ignore/task_test_calc_marker.ini
@@ -1,5 +1,6 @@
-# This file contains the info for the job to run
+# This file contains the info for the job to run.
 
+# The task section contains information about the task you want to run.
 [task]
 
 # The action you want to run:
@@ -8,23 +9,23 @@
 #    - calc_periodic_mosaic: calculate periodic mosaic
 action = calc_marker
 
-# Extra config files to load, in addition to general defaults
+# Extra config files to load, in addition to general defaults.
 # Possibilities to specify them:
 #   - Absolute paths
 #   - Relative paths: they will be resolved towards the dir this task file is located in
-#   - The {task_path} placeholder can be used to specify that this file should be used as extra config file
+#   - The {task_path} placeholder can be used to specify that this file should be used
+#     as extra config file
 extra_config_files_to_load = 
         ./_config/cropgroup.ini,
         ./_tasks/local_overrule.ini,
         {task_filepath}
 
+# The calc_marker_params section contains information to run a calc_marker task.
 [calc_marker_params]
-# The type of the input file: typically country code
-country_code = BEFL
 
 # input files
 input_parcel_filename = Prc_BEFL_2023_2023-07-24.gpkg
-input_parcel_filetype = ${country_code}
+input_parcel_filetype = BEFL
 input_groundtruth_filename = Prc_BEFL_2023_2023_07_24_groundtruth.tsv
 classes_refe_filename = BEFL_2023_mon_refe_2023-07-24.tsv
 
@@ -33,8 +34,8 @@ input_model_to_use_relativepath
 
 reuse_last_run_dir = False
 
-[marker]
-# year to run
-year = 2024
-# Roi name
-roi_name = roi_test
\ No newline at end of file
+# Configuration of the period we want to use.
+[period]
+
+# Year to use.
+year = 2024
\ No newline at end of file
diff --git a/sample_marker_basedir/_tasks/ignore/task_test_calc_periodic_mosaic.ini b/sample_marker_basedir/_tasks/ignore/task_test_calc_periodic_mosaic.ini
index 0dde655f..6b690aaa 100644
--- a/sample_marker_basedir/_tasks/ignore/task_test_calc_periodic_mosaic.ini
+++ b/sample_marker_basedir/_tasks/ignore/task_test_calc_periodic_mosaic.ini
@@ -1,4 +1,4 @@
-# This file contains the info for the job to run
+# This file contains the info for the job to run.
 
 [task]
 
@@ -18,14 +18,25 @@ extra_config_files_to_load =
         {task_filepath}
 
 [calc_periodic_mosaic_params]
-# The start date of the period we want to download the mosaic
-start_date_str = 2024-03-05
-# The end date of the period we want to download the mosaic
-# Also supports placeholder {now}, to use the current date
-end_date_str = 2024-03-15
-# The days before the end_date, if not None, end_date_subtract_days are subtracted from end_date
-end_date_subtract_days = 3
+
 # Simulate
 simulate = True
 # Reducer to use to aggregate pixels in the time dimension
-time_reducer = mean
\ No newline at end of file
+time_reducer = mean
+
+# Configuration of the period we want to use.
+[period]
+
+# The start date of the period we want to use. The period starts at 00h of this day.
+start_date = 2024-03-05
+# The end date of the period we want to use. The period ends at 23h59 of the PREVIOUS
+# day, so the end_date is exclusive. Also supports placeholder {now}, to use the current
+# date.
+end_date = 2024-03-15
+
+# Configuration of the images we want to use.
+[images]
+
+# Images to get/use. This should basically be a list of existing image profile names as
+# configured in imageprofile.ini.
+images = s2-agri-weekly
\ No newline at end of file
diff --git a/tests/test_config_helper.py b/tests/test_config_helper.py
index 3eb20016..166c5cb1 100644
--- a/tests/test_config_helper.py
+++ b/tests/test_config_helper.py
@@ -41,13 +41,12 @@ def test_read_config():
         config_paths=config_paths,
         default_basedir=SampleData.marker_basedir,
         overrules=[
-            "dirs.images_periodic_dir=_satellite_periodic/BEFL",
+            "paths.images_periodic_dir=_satellite_periodic/BEFL",
         ],
     )
 
     assert conf.marker["markertype"] == "CROPGROUP"
-    assert conf.calc_marker_params["country_code"] == "BEFL"
-    assert conf.marker["roi_name"] == "roi_test"
+    assert conf.roi["roi_name"] == "roi_test"
 
 
 @pytest.mark.parametrize(
@@ -57,19 +56,12 @@ def test_read_config():
         ("Config file doesn't exist", Path("not_existing_config.ini"), None, False, []),
         ("default_basedir does not exist", None, Path("not_existing_dir"), True, []),
         (
-            "dirs.data_dir is relative, but no default_basedir supplied",
+            "paths.data_dir is relative, but no default_basedir supplied",
             None,
             None,
             True,
             [],
         ),
-        (
-            "calc_marker_params.country_code must be overridden",
-            None,
-            SampleData.marker_basedir,
-            True,
-            [],
-        ),
     ],
 )
 def test_read_config_invalid(
@@ -90,15 +82,13 @@ def test_read_config_overrule():
         config_paths=config_paths,
         default_basedir=SampleData.marker_basedir,
         overrules=[
-            "calc_marker_params.country_code=COUNTRY_TEST",
-            "marker.roi_name=ROI_NAME_TEST",
-            "dirs.images_periodic_dir=_satellite_periodic/BEFL",
+            "roi.roi_name=ROI_NAME_TEST",
+            "paths.images_periodic_dir=_satellite_periodic/BEFL",
         ],
     )
 
     assert conf.marker["markertype"] == "CROPGROUP"
-    assert conf.calc_marker_params["country_code"] == "COUNTRY_TEST"
-    assert conf.marker["roi_name"] == "ROI_NAME_TEST"
+    assert conf.roi["roi_name"] == "ROI_NAME_TEST"
 
 
 def test_validate_image_profiles():