Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
095ad25
refactor: update to python3.9 & otb 8.0.1
Jun 7, 2024
cc7011e
refactor: add missing configuration file
Jun 7, 2024
243d1fb
wip : start json file as parameter.
Jun 7, 2024
7cedd9b
refactor: json files as input parameters
Jun 12, 2024
13c58f1
style: remove unreachable main() and hardcoded path
Jun 12, 2024
36e1660
refactor: in_shps only used as list
Jun 12, 2024
7576db7
bug : cast values
Jun 12, 2024
6bb62f0
refactor: json as parameter
Jun 12, 2024
48d6954
refactor: paths as parameters
Aug 1, 2024
6e56232
add tests
Aug 1, 2024
bd47e80
test: add asserts to tests
Oct 1, 2024
07d5086
test: add tests docstrings
Oct 4, 2024
2ef9f91
doc: start adding doc
Aug 2, 2024
d37f41d
bug + doc: add Montreux tutorial
Oct 10, 2024
7601d6f
test: add missing data
Oct 23, 2024
7f4e0a4
test: add missing data
Oct 23, 2024
7483712
test: bug add missing directory and remove hard coded path
Oct 28, 2024
16fe605
style: add headers
Oct 28, 2024
3e57c54
feat: add docker and singularity capabilities.
Oct 28, 2024
7e66b14
doc: update install documentation
Oct 28, 2024
fc99f67
test: add asserts
Oct 30, 2024
fac2120
CI: add ci
Dec 5, 2024
306c1ba
Update build_and_test_app.yml
ArthurVincentCS Dec 5, 2024
78aab07
update CI
Dec 5, 2024
416357a
Merge branch 'add_doc' of https://github.com/ArthurVincentCS/ALCD int…
Dec 5, 2024
4f672c9
CI: add ci
Dec 5, 2024
4ae5430
CI: add ci
Dec 5, 2024
f48e190
CI: update
Dec 5, 2024
c39fd2e
feat: start adding pydantic BaseModel
Oct 29, 2024
87772b5
feat: use pydantic models in alcd lib
Oct 30, 2024
43655f4
feat : added random forest with scikit
cadauxe Jan 8, 2025
5d3ef55
feat : added svm with scikit
cadauxe Jan 9, 2025
b1e93fa
feat : added classif algos
cadauxe Jan 10, 2025
6eef729
feat : added scikit test
cadauxe Jan 10, 2025
64c6683
feat : change for CI run
cadauxe Jan 13, 2025
8524096
feat : WIP user primitive
cadauxe Jan 14, 2025
6f150b4
feat : tests are working
cadauxe Jan 14, 2025
61ee000
feat : cleaning
cadauxe Jan 15, 2025
85f37e6
feat : WIP test user_module
cadauxe Jan 15, 2025
9034094
feat : test user_prim pass
cadauxe Jan 17, 2025
4b43a4e
docs : cleaning
cadauxe Jan 17, 2025
a761afe
feat : update CI
cadauxe Jan 17, 2025
be84f67
Merge branch 'new_add_doc' into user_prim
cadauxe Jan 17, 2025
fa0d3b5
feat : update for CI
cadauxe Jan 17, 2025
3cb7444
feat : add libs to requirements.txt
cadauxe Jan 17, 2025
453f47c
feat : add libs to requirements.txt
cadauxe Jan 17, 2025
157600b
feat : update CI
cadauxe Jan 17, 2025
188334e
feat : update CI
cadauxe Jan 17, 2025
512f196
feat : update CI
cadauxe Jan 17, 2025
5ee8853
feat : update CI
cadauxe Jan 17, 2025
c82fc48
feat : update CI
cadauxe Jan 17, 2025
9bc7f4f
doc : added doc
cadauxe Jan 17, 2025
d10e6f4
doc : added doc
cadauxe Jan 20, 2025
bc044eb
doc : updated doc
cadauxe Jan 23, 2025
8bdd5a8
feat : update requirement.txt
cadauxe Jan 23, 2025
b34e56d
feat : update for CI
cadauxe Jan 23, 2025
2c11a1f
feat : error prim does not exist
cadauxe Jan 23, 2025
f32ff70
feat : test if scikit model
cadauxe Jan 31, 2025
529bce4
feat : corrected typos in notebook
cadauxe Feb 6, 2025
5465c87
feat : merge with pydantic_models
cadauxe Feb 6, 2025
da51b2b
feat : added pydantic params and random for OTB
cadauxe Feb 7, 2025
864093c
feat : added pydantic in requirements.txt
cadauxe Feb 7, 2025
a37a616
feat : updated param files for montreux.ipynb
cadauxe Feb 7, 2025
0b4c2eb
doc : updated montreux.ipynb
cadauxe Feb 7, 2025
d0281e6
docs : updated montreux.ipynb
cadauxe Apr 3, 2025
6fde708
feat: refactored for cov-report in github ci
cadauxe Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/build_and_test_app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: ALCD

on:
push:
branches:
- "add_py_feat"

permissions:
contents: read
env:
OTB_RELEASE: 9.1.0
jobs:
quality:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: setup python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: install quality deps
run: |
pip install pylint mccabe
- name: Run Pylint
shell: bash -l {0}
run: |
pylint --recursive=y --disable=all --enable=too-many-statements,too-many-nested-blocks .
- name: Run McCabe Complexity Check
shell: bash -l {0}
run: |
./continuous_integration/scripts/check_mccabe_complexity.sh 25 .

test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: setup python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
sudo apt install -y --no-install-recommends g++ swig cmake make
pip install "numpy<2"
curl https://www.orfeo-toolbox.org/packages/archives/OTB/OTB-$OTB_RELEASE-Linux.tar.gz -o OTB-$OTB_RELEASE-Linux.tar.gz
tar xvf OTB-$OTB_RELEASE-Linux.tar.gz --one-top-level="/opt/otb"
source /opt/otb/otbenv.profile
sh /opt/otb/recompile_bindings.sh
python -m pip install --upgrade pip
if [ -f docker/requirements.txt ]; then pip install -r docker/requirements.txt; fi
- name: pytest
run: |
cd tests
export CMAKE_PREFIX_PATH=/opt/otb
export PYTHONPATH=/opt/otb/lib/python3/dist-packages:/opt/otb/lib/otb/python
export GDAL_DRIVER_PATH=disable
export GDAL_DATA=/opt/otb/share/gdal
export PROJ_LIB=/opt/otb/share/proj
export PATH=/opt/otb/bin:$PATH
export LD_LIBRARY_PATH=/opt/otb/lib:$LD_LIBRARY_PATH
export OTB_APPLICATION_PATH=/opt/otb/lib/otb/applications
export OTB_INSTALL_DIR=/opt/otb
export LC_NUMERIC=C

pytest --cov=. --cov-report=xml:.ci-reports-alcd/coverage_alcd.xml --cov-report html:cov_html_alcd --cov-report=term --junitxml=pytest-results-alcd.xml
- name: Upload Coverage Report
uses: actions/upload-artifact@v4
with:
name: coverage-report-alcd
path: |
.ci-reports-alcd/
cov_html_alcd/
pytest-results-alcd.xml
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.pyc
*.py~
.gitignore~
*.sh~
*.json~
147 changes: 131 additions & 16 deletions L1C_band_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,21 @@
"""
import os
import os.path as op
import sys

import otbApplication
import importlib.util
import string
import secrets
import find_directory_names
import glob
import json
import shutil
import subprocess
import tempfile
import argparse
import rasterio
import rioxarray

from alcd_params.params_reader import read_paths_parameters, read_global_parameters


def create_composit_band(bands_full_paths, out_tif, resolution=60, composit_type='ND'):
Expand Down Expand Up @@ -115,7 +122,7 @@ def create_specific_indices(in_bands_dir, out_tif, indice_name, resolution=60):
print('Please enter a valid indice name')


def create_time_difference_band(global_parameters, band_num, out_tif, resolution=60):
def create_time_difference_band(global_parameters, paths_parameters, band_num, out_tif, resolution=60):
'''
Create a TIF being the difference between the cloudy date and the clear date
The band_num is the number of the band of interest
Expand All @@ -125,9 +132,9 @@ def create_time_difference_band(global_parameters, band_num, out_tif, resolution
clear_date = global_parameters["user_choices"]["clear_date"]

current_dir, current_band_prefix, current_date = find_directory_names.get_L1C_dir(
location, current_date, display=False)
location, current_date,paths_parameters, display=False)
clear_dir, clear_band_prefix, clear_date = find_directory_names.get_L1C_dir(
location, clear_date, display=False)
location, clear_date,paths_parameters, display=False)

band_num_str = '{:02d}'.format(band_num)

Expand Down Expand Up @@ -260,7 +267,7 @@ def dtm_addition(location, out_band, resolution=60):
Create the adapted Digital Terrain Model
From the original one, change its resolution
'''
paths_configuration = json.load(open(op.join('..', 'paths_configuration.json')))
paths_configuration = read_paths_parameters(open(op.join('parameters_files', 'paths_configuration.json')))
tile = paths_configuration["tile_location"][location]

original_DTM_dir = paths_configuration["global_chains_paths"]["DTM_input"]
Expand Down Expand Up @@ -293,8 +300,105 @@ def resize_band(in_band, out_band, pixelresX, pixelresY):
os.system(build_warp)


def create_image_compositions(global_parameters, location, current_date, heavy=False, force=False):
#
def load_module(source, module_name = None):
"""
reads file source and loads it as a module

source : user's file to load
module_name : name of module to register in sys.modules

Return: loaded module
"""
if module_name is None:
alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits
symbol = "".join([secrets.choice(alphabet) for i in range(32)])
module_name = "gensym_" + symbol

spec = importlib.util.spec_from_file_location(module_name, source)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)

return module

def user_process(raw_img: str, main_dir: str, module_path : str, fct_name : str, location: str, user_path: str):
"""
Process an input raster image :
- Rename the bands knows how to apply its process
- Apply the user-defined function
- Save the result and update band description txt file.

Parameters:
----------
raw_img : str
Path to the input raster image in GeoTIFF format.

main_dir : str
The main directory containing project files.

fct_path : str
Path to the Python file containing the user's primitive.

location : str
A string representing the location identifier used to locate the band description file.

user_path : str
Path where the output raster image will be saved.

Returns:
-------
xarray.DataArray
The processed raster data as a xarray.DataArray.

Assumptions:
- The user's function accepts a xarray.DataArray and returns a modified xarray.DataArray.
"""
with rioxarray.open_rasterio(raw_img) as raw_arr:

# Read .txt file with band description
bands_dict = {}
band_descr = op.join(main_dir, 'In_data', 'Image', location + "_bands_bands.txt")

# Extract each band name
with open(band_descr, 'r') as f:
for line in f:
band, path = line.strip().split(" : ")
band_name = path.split(".tif")[0].split("Intermediate/")[-1]
if ("_B") in band_name:
band_name = band_name.split("_")[-1]
bands_dict[band] = band_name

# Rename xarray's bands according to the .txt file
bands_list = list(bands_dict.values())
out_arr = raw_arr.assign_coords(band=bands_list)

# Apply user's function
assert op.exists(module_path), 'The user function provided in the global_parameter\'s file does not exists'
user_module = load_module(module_path)

# Warning : user's function has to be named my_process
user_function = getattr(user_module, fct_name)
users_arr = user_function(out_arr)
new_bands_list = list(users_arr.coords['band'].values)

n_bands, height, width = users_arr.shape
print(n_bands)
# Save user's xarray on disk
with rasterio.open(user_path, 'w', driver='GTiff', height=height, width=width,
count=n_bands, dtype=out_arr.dtype, crs=out_arr.rio.crs,
transform=out_arr.rio.transform()) as dst:
for i in range(n_bands):
dst.write(users_arr[i], i + 1)

#Update the band description txt file
with open(band_descr, 'w') as f:
for b in range(len(new_bands_list)) :
print(f"B{b + 1} : {new_bands_list[b]}\n")
f.write(f"B{b + 1} : {new_bands_list[b]}\n")

return users_arr

def create_image_compositions(global_parameters, location, paths_parameters, current_date, heavy=False, force=False):
potential_final_tif = op.join(global_parameters["user_choices"]["main_dir"],
'In_data', 'Image', global_parameters["user_choices"]["raw_img"])

Expand All @@ -304,7 +408,7 @@ def create_image_compositions(global_parameters, location, current_date, heavy=F

# get the directory of the bands
bands_dir, band_prefix, date = find_directory_names.get_L1C_dir(
location, current_date, display=True)
location, current_date, paths_parameters, display=True)
# --------------------------------------------
# ------ Low resolution TIF with all the bands
# Preparation
Expand Down Expand Up @@ -356,7 +460,7 @@ def create_image_compositions(global_parameters, location, current_date, heavy=F
out_dir_bands = op.join(global_parameters["user_choices"]["main_dir"], 'Intermediate')
for band_num in bands_num:
out_tif = op.join(out_dir_bands, ('time_' + str(band_num) + '.tif'))
create_time_difference_band(global_parameters, band_num, out_tif, resolution=resolution)
create_time_difference_band(global_parameters, paths_parameters, band_num, out_tif, resolution=resolution)
additional_bands.append(str(out_tif))

# --- Create the main TIF with low resolution
Expand Down Expand Up @@ -426,10 +530,18 @@ def create_image_compositions(global_parameters, location, current_date, heavy=F
intermediate_sizes_paths = [str(i) for i in intermediate_sizes_paths]
compose_bands_heavy(intermediate_sizes_paths, str(out_heavy_tif))

if "user_function" in list(global_parameters["user_choices"].keys()) and global_parameters["user_choices"]["user_function"] != None:
print('ENTERED')
user_process(raw_img = out_all_bands_tif,
main_dir = global_parameters["user_choices"]["main_dir"],
module_path = global_parameters["user_choices"]["user_module"],
fct_name = global_parameters["user_choices"]["user_function"],
location = global_parameters["user_choices"]["location"],
user_path = out_all_bands_tif)
return


def create_no_data_tif(global_parameters, out_tif, dilation_radius=10):
def create_no_data_tif(global_parameters, paths_parameters, out_tif, dilation_radius=10):
'''
Create the no_data TIF using both the clear and cloudy date.
Used in the 'layers_creation.create_no_data_shp'
Expand All @@ -439,9 +551,9 @@ def create_no_data_tif(global_parameters, out_tif, dilation_radius=10):
clear_date = global_parameters["user_choices"]["clear_date"]

current_dir, current_band_prefix, current_date = find_directory_names.get_L1C_dir(
location, current_date, display=False)
location, current_date, paths_parameters, display=False)
clear_dir, clear_band_prefix, clear_date = find_directory_names.get_L1C_dir(
location, clear_date, display=False)
location, clear_date, paths_parameters, display=False)

# Band number, the 1 is 20m resolution, change it if
# other resolution is wanted
Expand All @@ -462,8 +574,9 @@ def create_no_data_tif(global_parameters, out_tif, dilation_radius=10):
Dilatation.SetParameterInputImage("in", BandMathX.GetParameterOutputImage("out"))
Dilatation.SetParameterString("out", str(out_tif))
Dilatation.SetParameterString("filter", "dilate")
Dilatation.SetParameterInt("structype.ball.xradius", dilation_radius)
Dilatation.SetParameterInt("structype.ball.yradius", dilation_radius)
Dilatation.SetParameterString("structype", "ball")
Dilatation.SetParameterInt("xradius", dilation_radius)
Dilatation.SetParameterInt("yradius", dilation_radius)
Dilatation.UpdateParameters()
Dilatation.ExecuteAndWriteOutput()

Expand All @@ -472,6 +585,8 @@ def str2bool(v):
'''
Converts a string to a boolean
'''
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
Expand All @@ -481,7 +596,7 @@ def str2bool(v):


def main():
global_parameters = json.load(open(op.join('parameters_files', 'global_parameters.json')))
global_parameters = read_global_parameters(op.join('parameters_files', 'global_parameters.json'))

out_tif = 'tmp/tmp_tif.tif'
create_no_data_tif(global_parameters, out_tif, resolution=60)
Expand Down
Loading
Loading