├── .gitignore
├── LICENSE
├── README.md
├── básico
├── 00_Acceso.ipynb
├── 01_Cargar_datos.ipynb
├── 02_Limpieza.ipynb
├── 03_Análisis_básico.ipynb
├── 04_Caso_de_estudio_1.ipynb
└── 05_Caso_de_estudio_2.ipynb
├── extra
├── capturas
│ ├── 01.png
│ ├── 02.png
│ ├── 03.png
│ ├── 04.png
│ ├── 05.png
│ ├── 06.png
│ ├── 07.png
│ ├── 08.png
│ ├── Server1_2023-05-23.png
│ ├── Server2_2023-05-23.png
│ ├── c1.png
│ ├── c1_2023_05_23.png
│ ├── git.PNG
│ ├── intro1.png
│ ├── login1.PNG
│ ├── login2.PNG
│ ├── primer_login.PNG
│ ├── s1.png
│ └── server_options.PNG
├── functions
│ ├── __init__.py
│ └── holoplots.py
└── logos
│ └── logos.png
├── intermedio
└── 00_Computo_distribuido.ipynb
├── material_complementario
├── 02a_Limpieza_s2l2a.ipynb
├── 02b_Limpieza_fasat_charlie_ms.ipynb
└── dosafio_fach2024
│ ├── dataton_zonas.gpkg
│ ├── imagenes_no_indexadas.ipynb
│ └── s3_imagenes_disponibles.csv
└── samsara
├── 00_SAMSARA_intro.ipynb
├── 01_SAMSARA_app.ipynb
├── LC_mix_matybosquenat2013-2014_cog.tif
├── LC_union_4y7_cog.tif
├── RF_v03_all-trained_negative_of_first.joblib
└── denuncias.gpkg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | .vscode
132 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DataCube Entrenamiento 🌎
2 |
3 | [](http://creativecommons.org/publicdomain/zero/1.0/)
4 | 
5 | 
6 | [](https://jupyter.org/try)
7 |
8 | Este repositorio contiene una serie de notebooks para funcionar con el DataCube Chile. Está dividido en niveles de dificultad y por ahora, está disponible el entrenamiento:
9 |
10 | - [básico](/básico), que sienta los primeros lineamientos para comenzar a trabajar en el cubo de datos.
11 | - [intermedio](/intermedio), que permite comprender el uso de computo distribuido en el trabajar del cubo de datos.
12 | - [samsara](/samsara), que muestra la utilización de [lib-samsara](https://github.com/Data-Observatory/lib-samsara) en el cubo de datos, como ejemplo del proyecto [Samsara](https://www.dataobservatory.net/projects/samsara).
13 |
14 | En un futuro cercano, se seguirá agregando material en diferentes niveles.
15 |
--------------------------------------------------------------------------------
/básico/00_Acceso.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "48a5ff75-9377-4a46-a9bf-d9f3cefce38c",
6 | "metadata": {
7 | "tags": []
8 | },
9 | "source": [
10 | "
"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "c7a9d69a-af16-4bdf-b191-8c8557498969",
16 | "metadata": {
17 | "tags": []
18 | },
19 | "source": [
20 | "# Bienvenida 👋\n",
21 | "\n",
22 | "¡Bienvenido al tutorial básico de DataCube Chile! En este tutorial vas a aprender a:\n",
23 | " \n",
24 | "- Acceder y salir del entorno virtual de DataCubeChile;\n",
25 | "- Clonar repositorios dentro de DataCubeChile;\n",
26 | "- Extraer información de DataCubeChile, y procesar imágenes;\n",
27 | "- Usar xarray y las herramientas del DataCubeChile.\n",
28 | "\n",
29 | "Este tutorial puede leerse tanto desde una página estática de GitHub, como desde la página del DataCubeChile. Recomendamos hacerlo desde la página de DataCubeChile, pues podrás correr sin problema los scripts de este tutorial.\n",
30 | "\n",
31 | ">**Consejo:** No olvides leer detalladamente todas las etapas, y dedicar el tiempo necesario en cada paso. Esto es particularmente importante en este Notebook, ya que incorpora las secciones de acceso e importación de repositorios. Notarás que son pocos pasos y debiese ser rápido de terminar, en cuyo caso aprovecha el tiempo para experimentar las posibilidades que trae DataCubeChile. \n",
32 | "\n",
33 | "***"
34 | ]
35 | },
36 | {
37 | "cell_type": "markdown",
38 | "id": "de8d573a-a819-4c86-be2c-64559a3ded27",
39 | "metadata": {
40 | "tags": []
41 | },
42 | "source": [
43 | "# DataCubeChile 🌎\n",
44 | "\n",
45 | "DataCubeChile es una plataforma colaborativa que busca revolucionar el uso de datos de Observación de la Tierra y el Mar en Chile, para entregar valor a los sectores de investigación, industria y gobierno.\n",
46 | "\n",
47 | "El cubo de datos de DataCubeChile está montado sobre la infraestructura de Amazon Web Services (AWS), la cual permite una alta flexibilidad y disponibilidad en la nube.\n",
48 | "\n",
49 | "DataCubeChile viene con un **entorno virtual** que tiene todas las funcionalidades necesarias para acceder, procesar y visualizar las imágenes disponibles en el cubo.\n",
50 | "\n",
51 | "El usuario visualiza un Jupyter-Notebook, el que está corriendo en la nube (en AWS) y no en el computador del usuario. Se parece bastante a Google Colabs (¿te suena algo?). Al conectarnos a DataCube, AWS nos presta un servidor en la nube (una instancia), el cual viene configurado con todas las librerías necesarias.\n",
52 | "\n",
53 | "Los archivos que se creen y guarden en la sesión, quedan disponible permanentemente, por lo que cada vez que se reinicie la sesión, estos estarán accesibles. Sin embargo, las instancias tienen costo. Por esta razón, es importante cerrar bien todo cuando uno deja de usar DataCube.\n",
54 | "\n",
55 | ">**Nota:** Parte de este notebook es una adaptación realizada las guías de [DataCube Australia](https://github.com/GeoscienceAustralia/dea-notebooks).\n",
56 | "\n",
57 | "***"
58 | ]
59 | },
60 | {
61 | "cell_type": "markdown",
62 | "id": "57d72987-f36c-474a-8c8a-2337e14e22d5",
63 | "metadata": {
64 | "tags": []
65 | },
66 | "source": [
67 | "# 1. Configuración de la cuenta 🔧\n",
68 | "\n",
69 | "Antes que todo, deberías haber recibido un correo con tu login al DataCube. Es un correo de Jonathan Hodge con tu usuario y clave. Se ve así:\n",
70 | "\n",
71 | "
\n",
72 | "\n",
73 | "\n",
74 | "***Guárdalos***, los necesitarás pronto.\n",
75 | "\n",
76 | "Una vez que los tenga, puedes proseguir.\n",
77 | "\n",
78 | "***"
79 | ]
80 | },
81 | {
82 | "cell_type": "markdown",
83 | "id": "85f50f83-b227-4e7b-a87a-8cf688fca44b",
84 | "metadata": {
85 | "tags": []
86 | },
87 | "source": [
88 | "# 2. Acceso al sistema \t🔑\n",
89 | "\n",
90 | "Para acceder al cubo, tal como se menciona en el correo de Jonathan Hodge, se requiere acceder al siguiente enlace: [**https://hub.datacubechile.cl/**](https://hub.datacubechile.cl/)\n",
91 | "\n",
92 | "Debiese aparecer la siguiente página:\n",
93 | "\n",
94 | "
\n",
95 | "\n",
96 | "***"
97 | ]
98 | },
99 | {
100 | "cell_type": "markdown",
101 | "id": "efda9a47-14d4-4d3c-9396-e81f3adb2f19",
102 | "metadata": {},
103 | "source": [
104 | "Luego, haga click en el botón \"Sign Up with **AWSCognito**\" para acceder a la página de login. \n",
105 | "\n",
106 | "Ingrese las credenciales proporcionadas por correo (usuario, contraseña), y haga click en \"Sign in\".\n",
107 | "\n",
108 | "
\n",
109 | "\n",
110 | "***"
111 | ]
112 | },
113 | {
114 | "cell_type": "markdown",
115 | "id": "36cbf43b-ab60-496a-830c-314162d93501",
116 | "metadata": {},
117 | "source": [
118 | "Aparecerá la página de configuración del entorno virtual \"**Server Options**\" con 3 categorías \"**Resource Usage Allocation**\", \"**Resource request**\" y \"**Jupyter profile**\".\n",
119 | "\n",
120 | "
\n",
121 | "
\n",
122 | "\n",
123 | "* En \"**Resource Usage Allocation**\", de existir opciones, escojer la que corresponda según el proyecto en que está trabajando o las instrucciones del administrador.\n",
124 | "* En \"**Resource request**\" escojer \"**DEFAULT**\" que consiste en la asignación de 8 CPU (cores), 32 GB de memoria RAM, sin GPU.\n",
125 | "* En \"**Jupyter profile**\" escojer \"**Default Jupyter Python enviroment (master.lastest)**\".\n",
126 | "* Finalmente, deslizar hasta abajo y presionar **Start**.\n",
127 | "\n",
128 | "Existen varias opciones (R, GPU, etc...), que pueden o no estar activas dependiendo de la configuración que tenga su cuenta. Estas opciones permiten lanzar instancias con imágenes distintas (es decir pre-configuraciones diferentes) y en caso de necesitar alguna en particular, deberá ponerse en contacto con el administrador del sistema.\n",
129 | "\n",
130 | "\n",
131 | "\n",
132 | "***"
133 | ]
134 | },
135 | {
136 | "cell_type": "markdown",
137 | "id": "49ce78b4-a84d-46d5-8702-f2794b29a72c",
138 | "metadata": {},
139 | "source": [
140 | "¡Perfecto! Ahora se va a lanzar la instancia. Paciencia, se demora un par de minutos... 🤞\n",
141 | "\n",
142 | "
\n",
143 | "\n",
144 | "***"
145 | ]
146 | },
147 | {
148 | "cell_type": "markdown",
149 | "id": "5cd5756c-629d-4354-8cae-d443c355ed77",
150 | "metadata": {
151 | "tags": []
152 | },
153 | "source": [
154 | "Una vez que la instancia se haya lanzado, deberías llegar a esto:\n",
155 | "\n",
156 | "
\n",
157 | "\n",
158 | "\n",
159 | "***"
160 | ]
161 | },
162 | {
163 | "cell_type": "markdown",
164 | "id": "ced600ef-37c5-4d85-b442-c4ed32149b20",
165 | "metadata": {
166 | "tags": []
167 | },
168 | "source": [
169 | "\n",
170 | "\n",
171 | "¡***Buen trabajo***, ahora puedes usar DataCubeChile! Respira hondo y descansa, que ya continúa el proceso\n",
172 | "\n",
173 | "***"
174 | ]
175 | },
176 | {
177 | "cell_type": "markdown",
178 | "id": "1d5d05e1-c140-4a4c-a878-2f9c3ed8bc52",
179 | "metadata": {
180 | "tags": []
181 | },
182 | "source": [
183 | "# 3. Clonar repositorios 📚\n",
184 | "\n",
185 | "Al leer este Notebook desde nuestro navegador, al ser estático, no podemos correr los códigos en DataCube. Por eso, el siguiente paso consiste en importar los tutoriales desde un repositorio hacia DataCube. Para esto, vamos clonar el repositorio en DataCubeChile. Es muy simple, primero copia esta url:\n",
186 | "\n",
187 | "`\n",
188 | "https://github.com/Data-Observatory/DataCubeTraining.git\n",
189 | "`\n",
190 | "\n",
191 | "¿Listo? \n",
192 | "\n",
193 | "***\n",
194 | "Los siguientes pasos son:\n",
195 | "1. Asegúrate que estás en tu \"home directory\". Para hacer esto, debes apretar el icono de carpeta;\n",
196 | "2. Haz clic en el icono GitHub;\n",
197 | "3. Pega la URL que acabas de copiar;\n",
198 | "4. Haz clic en \"clone\";\n",
199 | "\n",
200 | "Estos pasos están representados visualmente a continuación!\n",
201 | "\n",
202 | "
\n",
203 | "\n",
204 | "\n",
205 | "***"
206 | ]
207 | },
208 | {
209 | "cell_type": "markdown",
210 | "id": "70e6dc8d-a670-4526-b850-9143eb3ba45b",
211 | "metadata": {},
212 | "source": [
213 | "Ahora debiese aparecer la carpeta `DataCubeTraining` en tu \"home directory\". Puedes hacer clic en la carpeta, y abrir los tutoriales desde DataCubeChile.\n",
214 | "\n",
215 | "Este procedimiento puede ser repetido por cada repositorio que desee clonar. Una vez clonado, al estar en la carpeta de un repositorio se desplegarán todas las funcionalidades de Git. Si bien estas escapan al objetivo de este tutorial, se puede obtener más información [aquí](https://product.hubspot.com/blog/git-and-github-tutorial-for-beginners).\n",
216 | "\n",
217 | "> **Otros repositorios de interés:** \n",
218 | "\n",
219 | ">> `https://github.com/GeoscienceAustralia/dea-notebooks.git`\n",
220 | "\n",
221 | ">> `https://github.com/digitalearthafrica/deafrica-sandbox-notebooks.git`\n",
222 | "\n",
223 | ">**Nota**: Si se van a realizar ediciones, es recomendable copiar el notebook en otra ubicación y realizar los cambios. De esa forma, si se actualiza el código en la fuente (por ejemplo, este mismo notebook se corrije y actualiza en el repositorio), no se generan conflictos entre los cambios que se están generando y los de la fuente.\n",
224 | "\n",
225 | "***"
226 | ]
227 | },
228 | {
229 | "cell_type": "markdown",
230 | "id": "cddc44d5-b30e-4658-aeeb-29a05f9e9aaa",
231 | "metadata": {},
232 | "source": [
233 | "# 4. Cerrar DataCubeChile 🔒\n",
234 | "\n",
235 | "Como dijimos con anterioridad, es importante que cierres bien tu instancia cuando dejes de usar DataCubeChile, para evitar costos inútiles. Aquí están detallados los pasos para cerrar con seguridad tu instancia cuando termines de trabajar, acuérdate de que los pasos aparecen acá:\n",
236 | "\n",
237 | "
\n",
238 | "\n",
239 | "***"
240 | ]
241 | },
242 | {
243 | "cell_type": "markdown",
244 | "id": "e68da446-7417-43e4-afe6-17ea48fa0ea3",
245 | "metadata": {},
246 | "source": [
247 | "# *Siguientes pasos* 🐾\n",
248 | "\n",
249 | "Para continuar con el tutorial pueden acceder a los notebooks del siguiente listado.\n",
250 | "\n",
251 | "1. **Acceso**\n",
252 | "2. [Cargar datos](01_Cargar_datos.ipynb)\n",
253 | "3. [Limpieza](02_Limpieza.ipynb)\n",
254 | "4. [Análisis básico](03_Análisis_básico.ipynb)\n",
255 | "5. [Caso de estudio 1](04_Caso_de_estudio_1.ipynb)\n",
256 | "6. [Caso de estudio 2](05_Caso_de_estudio_2.ipynb)\n",
257 | "\n",
258 | "***"
259 | ]
260 | },
261 | {
262 | "cell_type": "code",
263 | "execution_count": null,
264 | "id": "53533a40-963f-4bce-9e81-3ebef965f25d",
265 | "metadata": {},
266 | "outputs": [],
267 | "source": []
268 | }
269 | ],
270 | "metadata": {
271 | "kernelspec": {
272 | "display_name": "Python 3 (ipykernel)",
273 | "language": "python",
274 | "name": "python3"
275 | },
276 | "language_info": {
277 | "codemirror_mode": {
278 | "name": "ipython",
279 | "version": 3
280 | },
281 | "file_extension": ".py",
282 | "mimetype": "text/x-python",
283 | "name": "python",
284 | "nbconvert_exporter": "python",
285 | "pygments_lexer": "ipython3",
286 | "version": "3.10.12"
287 | }
288 | },
289 | "nbformat": 4,
290 | "nbformat_minor": 5
291 | }
292 |
--------------------------------------------------------------------------------
/básico/01_Cargar_datos.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "46c89f70-ecfd-44c7-b980-cd9e9594eacb",
6 | "metadata": {
7 | "tags": []
8 | },
9 | "source": [
10 | "
"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "9ab88058-1fbf-4022-a203-f1e1e3c3c575",
16 | "metadata": {
17 | "tags": []
18 | },
19 | "source": [
20 | "# Carga de datos en el DataCube Chile 🌎\n",
21 | "\n",
22 | "¡Sigamos con este tutorial! En [00_Acceso.ipynb](00_Acceso.ipynb) aprendimos a conectarnos a la instancia DataCubeChile, e importar repositorios. \n",
23 | "\n",
24 | "En este apartado, se verán las operaciones básicas que se pueden realizar con el DataCube:\n",
25 | "* Definir una área de estudio\n",
26 | "* Buscar productos y mediciones\n",
27 | "* Solicitar la información\n",
28 | "* Visualizar la información\n",
29 | "\n",
30 | "Aunque, hasta ahora no hemos explicado qué son los Jupyter Notebooks, así que partiremos por ello. Verás que son muy útiles...\n",
31 | "\n",
32 | "***\n",
33 | "\n",
34 | ">**Nota**: Este notebook contiene elementos extraídos desde [DataCube Australia](https://github.com/GeoscienceAustralia/dea-notebooks).\n",
35 | "\n",
36 | ">**Nota**: Si tiene problemas para desplegar este notebook, abra una terminal de Linux (File -> New -> Terminal), navegue hasta la carpeta donde está este notebook y escriba `jupyter trust 01_Carga_datos.ipynb`. Luego, vuelva a abrir el notebook.\n",
37 | "\n",
38 | "***"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "id": "ff5cb6e1-1a3d-47e0-beec-2d6239140a40",
44 | "metadata": {
45 | "tags": []
46 | },
47 | "source": [
48 | "# 1. Jupyter Notebook 📓\n",
49 | "\n",
50 | "Nacido del acrónimno **Ju**lia + **Pyt**hon + **R**, esta aplicación permite interactuar nativamente con estos tres lenguajes de programación. De momento sólo está instalado Python (**versión 3.10**), aunque se puede habilitar R también. La ventaja de Jupyter es que permite una interacción directa entre código, texto y resultados.\n",
51 | "\n",
52 | "## 1.1 ¿Cómo funciona?\n",
53 | "\n",
54 | "Los notebooks de Jupyter permiten que el código se separe en secciones que se pueden ejecutar de forma independiente entre sí. Estas secciones se denominan \"celdas\". \n",
55 | "\n",
56 | "Puedes viajar entre las celdas con las flechas arriba/abajo de tu teclado. \n",
57 | "\n",
58 | "Existen 3 tipos de celdas: \"Code\", \"Markdown\", \"Raw\". Puedes elejir el tipo de celda a la derecha del botón ⯈⯈.\n",
59 | "\n",
60 | "El código Python se escribe en celdas individuales de tipo \"Code\".\n",
61 | "\n",
62 | "Al ejecutar una celda se ejecuta todo el contenido de esta. El resultado de la celda aparacererá directamente debajo de ella. Solo la última salida producida por la celda aparecerá como resultado, por lo que si desea ver el resultado de pasos intermedios, debe separar el código entre multiples celdas o utilizar el comando `print` en las partes de interés.\n",
63 | "\n",
64 | "- a. Para ejecutar una celda en particular, debe seleccionarla y presionar Shift-Enter en el teclado o seleccionar el botón ⯈ \"Run the selected cells and advance\" en la parte superior del notebook.\n",
65 | "- b. Para ejecutar todas las celdas en un notebook, navegue a la pestaña \"Run\" y luego seleccione \"Restart Kernel and Run All Cells...\". \n",
66 | "\n",
67 | "Mira, ¡abajo te mostramos donde tienes que hacer clic para ejecutar celdas!\n",
68 | "\n",
69 | "
\n",
70 | "\n",
71 | "El símbolo `[ ]:` ubicado a la izquierda de cada celda de código, describe el estado de la celda:\n",
72 | "\n",
73 | "* `[ ]:` significa que la celda aún no se ha ejecutado.\n",
74 | "* `[*]:` significa que la celda se está ejecutado.\n",
75 | "* `[1]:` significa que la celda ha terminado de ejecutarse. Este número va variando e indica el orden en que las celdas han sido ejecutadas\n",
76 | "\n",
77 | "> **Nota:** Para comprobar si una celda se está ejecutando actualmente, inspeccione el círculo pequeño en la parte superior a la derecha (donde dice Python 3 ⭘).\n",
78 | "El círculo se volverá gris (\"Kernel ocupado\") cuando la celda se esté ejecutando y volverá a estar vacío (\"Kernel inactivo\") cuando se complete el proceso.\n",
79 | "\n",
80 | "***\n",
81 | "## 1.2 Tipos de celdas\n",
82 | "\n",
83 | "### 1.2.1 Celdas de código (Code)\n",
84 | "\n",
85 | "Todas las operaciones de código se realizan en celdas de código.\n",
86 | "Las celdas de código se pueden usar para editar y escribir código nuevo, y realizar cualquier tipo de tarea, como también ejecutar análisis. \n",
87 | "Estas celdas tienen la misma funcionalidad que la consola que provee iPython y algunos de sus comandos [\"mágicos\"](https://ipython.readthedocs.io/en/stable/interactive/magics.html) se pueden utilizar también.\n",
88 | "\n",
89 | "### 1.2.2 Celdas de escritura (Markdown)\n",
90 | "\n",
91 | "Las celdas de Markdown proporcionan el contexto al notebook y permiten que darle una orientación al usuario, ya que permiten describir de manera más extendida y detallada lo que sucede en el código.\n",
92 | "Para ver algunas de las opciones de formato para el texto en una celda de Markdown, navegue a la pestaña \"Ayuda\" de la barra de menú en la parte superior de JupyterLab y seleccione \"Markdown Reference\".\n",
93 | "Existe una amplia gama de opciones de formato de texto, incluidos títulos, puntos, cursiva, hipervínculos, creación de tablas e inclusión de imágenes/figuras. \n",
94 | "\n",
95 | "### 1.2.3 Celdas de escritura (Raw)\n",
96 | "\n",
97 | "Las celdas Raw permiten escribir texto que no será formateado, similar a escribir en un bloc de notas. \n",
98 | "\n",
99 | "***\n",
100 | "## 1.3 Ejecutar/Detener procesos\n",
101 | "\n",
102 | "Si por algún motivo se requiere detener la ejecución de una celda antes de que finalice (por ejemplo, si un proceso tarda demasiado en completarse o si el código debe modificarse antes de ejecutar la celda), se puede detener haciendo clic en el botón \"detener\" ■ (\"Interrupt the kernel\"), en la parte superior del notebook.\n",
103 | "\n",
104 | "Para probar esto, ejecute la siguiente celda de código.\n",
105 | "Esto ejecutará un fragmento de código que tardará 20 segundos en completarse.\n",
106 | "Para interrumpir este código, presione la tecla ■ (botón \"Stop\")."
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": null,
112 | "id": "276c8cc7-5500-45f0-bc26-1347bc1cbaf2",
113 | "metadata": {
114 | "tags": []
115 | },
116 | "outputs": [],
117 | "source": [
118 | "import time\n",
119 | "print('Voy a dormir un rato...')\n",
120 | "time.sleep(20)\n",
121 | "print('¡Listo, como tuna!')"
122 | ]
123 | },
124 | {
125 | "cell_type": "markdown",
126 | "id": "200ead3c-b086-446d-bbc0-abf76d055b2f",
127 | "metadata": {
128 | "tags": []
129 | },
130 | "source": [
131 | "Si esto no funciona (si el notebook no responde), se puede reiniciar el notebook. Para hacer esto, navegue a la pestaña \"Kernel\" de la barra de menú, luego seleccione \"Restart Kernel\" o el botón ↻ (\"Restart the kernel\").\n",
132 | "Reiniciar el notebook también sirve para verificar que el código funciona correctamente desde que se inicia por primera vez. Para ello, pulse el botón ⯈⯈.\n",
133 | "\n",
134 | "***"
135 | ]
136 | },
137 | {
138 | "cell_type": "markdown",
139 | "id": "eb82b81e-1d7a-4a12-b73d-d8775544e7ec",
140 | "metadata": {
141 | "tags": []
142 | },
143 | "source": [
144 | "## 1.4 Guardar cambios\n",
145 | "\n",
146 | "Las modificaciones que se hagan sobre el notebook son guardadas automáticamente cada ciertos minutos. Para guardar los cambios en cualquier momento, haga clic en el icono 💾 (\"save\"), en la parte superior izquierda del notebook.\n",
147 | "\n",
148 | "***"
149 | ]
150 | },
151 | {
152 | "cell_type": "markdown",
153 | "id": "a61e0824-f1cf-4da3-bae8-573a691da5cd",
154 | "metadata": {
155 | "tags": []
156 | },
157 | "source": [
158 | "# 2. Explorar productos y mediciones 🛰\n",
159 | "\n",
160 | "Se puede tener una idea de los productos y la cobertura de las imágenes disponibles en el cubo en el [Explorador de imágenes](https://explorer.datacubechile.cl/).\n",
161 | "Dentro de la nomenclatura de DataCube, se habla de productos y mediciones:\n",
162 | "\n",
163 | "1. **Productos**: corresponden a los sensores. Por ejemplo, en el caso de Landsat-8, es una plataforma (satélite) que tiene dos sensores asociados, uno óptico (Operational Land Imager, OLI), que corresponde al producto `landsat8_c2l2_sr` y uno termal (Thermal Infrared Sensor, TIRS), que corresponde al producto `landsat8_c2l2_st`. Adicionalmente, hay un tercer producto para esta misma plataforma, que se llama `landsat8_geomedian_annual`. Todos estos productos son de la colección y nivel 2, pero podrían agregarse otros productos para la misma plataforma de otras colecciones y niveles.\n",
164 | "2. **Mediciones**: corresponden a las bandas que tiene cada producto. Cada banda abarca un rango específico del espectro electromagnético que es capturado por los distintos componentes del sensor y almacenado en matrices independientes. También pueden existir mediciones auxiliares, que indican calidad u otras características relevantes. Siguiendo con el ejemplo anterior, `landsat8_c2l2_sr` tiene un total de 10 mediciones, 3 de las cuales corresponden a bandas de calidad.\n",
165 | "\n",
166 | "***\n",
167 | "\n",
168 | "## 2.1 Cargar paquetes relevantes\n",
169 | "\n",
170 | "La celda a continuación carga los paquetes que permiten acceder al DataCube y sus funcionalidades."
171 | ]
172 | },
173 | {
174 | "cell_type": "code",
175 | "execution_count": null,
176 | "id": "6301662e-1a7e-42bf-9ddd-a73aa6b81fb1",
177 | "metadata": {},
178 | "outputs": [],
179 | "source": [
180 | "from dask.distributed import Client, LocalCluster\n",
181 | "cluster = LocalCluster()\n",
182 | "client = Client(cluster)"
183 | ]
184 | },
185 | {
186 | "cell_type": "code",
187 | "execution_count": null,
188 | "id": "86c740ab-870f-4ccc-85ca-ed68966f1f37",
189 | "metadata": {
190 | "tags": []
191 | },
192 | "outputs": [],
193 | "source": [
194 | "import datacube\n",
195 | "import xarray as xr\n",
196 | "import numpy as np\n",
197 | "import pandas as pd\n",
198 | "import matplotlib.pyplot as plt\n",
199 | "\n",
200 | "from odc.ui import DcViewer\n",
201 | "from datacube.utils import masking\n",
202 | "from datacube.utils.rio import configure_s3_access\n",
203 | "\n",
204 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)"
205 | ]
206 | },
207 | {
208 | "cell_type": "markdown",
209 | "id": "64aa55e8-1214-4590-a475-aab143ac9ca3",
210 | "metadata": {},
211 | "source": [
212 | "Partimos importando todas las librerías y funciones que vamos a necesitar.\n",
213 | "\n",
214 | "Importamos las librerías:\n",
215 | "- `datacube`: la librería principal de datacube.\n",
216 | "- `xarray`: para representar las imágenes,\n",
217 | "- `numpy`: la típica librería de cálculo científico.\n",
218 | "- `pandas`: para manejo y analisis de datos.\n",
219 | "- `matplotlib`: para plotear visualizaciones de datos e imágenes.\n",
220 | "- `odc`: un componente de la librería datacube que tiene funciones de procesamiento de imágenes.\n",
221 | "\n",
222 | "La última linea `configure_s3_access(aws_unsigned=False, requester_pays=True)` permite acceder a los buckets de S3 en donde están almacenados los datos del cátalogo de Open Data de AWS (Landsat y Sentinel-2).\n",
223 | "\n",
224 | "Al iniciar, es necesario generar una app del cubo con un nombre. Esto no afecta en nada el desempeño y solo cumple motivos de trazabilidad en caso de algún problema. El nombre puede ser de cualquier tipo, pero se recomienda que sea algo relacionado con la aplicación que se le dará:"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "id": "e90a0af4-b267-4fe4-8ea6-e74fe8b7a687",
231 | "metadata": {
232 | "tags": []
233 | },
234 | "outputs": [],
235 | "source": [
236 | "dc = datacube.Datacube(app='Basic_tutorial') "
237 | ]
238 | },
239 | {
240 | "cell_type": "markdown",
241 | "id": "ab27e997-31f2-4959-943d-ec848ea85c12",
242 | "metadata": {
243 | "tags": []
244 | },
245 | "source": [
246 | "***\n",
247 | "\n",
248 | "## 2.2 Explorar productos\n",
249 | "\n",
250 | "Una vez se tiene la app funcionando (en este caso `dc`), se pueden listar los productos disponibles.\n",
251 | "\n",
252 | "> **Nota:** Para explorar documentación sobre listar productos haga [click aquí](https://opendatacube.readthedocs.io/en/latest/api/indexed-data/generate/datacube.Datacube.list_products.html#datacube.Datacube.list_products)."
253 | ]
254 | },
255 | {
256 | "cell_type": "code",
257 | "execution_count": null,
258 | "id": "166b0660-ea0e-40aa-9c6d-b6eb7cb6f11f",
259 | "metadata": {
260 | "tags": []
261 | },
262 | "outputs": [],
263 | "source": [
264 | "dc.list_products()"
265 | ]
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "id": "03bd3a81-d05c-4ced-b54c-b2005b85d79d",
270 | "metadata": {},
271 | "source": [
272 | "Actualmente está disponible la serie completa de Landsat (5, 7 y 8), Sentinel-2 y el DEM SRTM.\n",
273 | "\n",
274 | "***\n",
275 | "\n",
276 | "## 2.3 Explorar mediciones\n",
277 | "\n",
278 | "Cada producto contiene mediciones (`measurements`) a las cuáles es posible acceder. A continuación se muestra un listado de las mediciones disponibles para dos sensores (Landsat 8 y Sentinel-2)\n",
279 | "\n",
280 | "> **Nota:** Para explorar documentación sobre listar mediciones haga [click aquí](https://opendatacube.readthedocs.io/en/latest/api/indexed-data/generate/datacube.Datacube.list_measurements.html#datacube.Datacube.list_measurements)"
281 | ]
282 | },
283 | {
284 | "cell_type": "code",
285 | "execution_count": null,
286 | "id": "11a093a9-268f-45f6-9e5d-976da4c60677",
287 | "metadata": {
288 | "tags": []
289 | },
290 | "outputs": [],
291 | "source": [
292 | "products = ['landsat8_c2l2_sr', 's2_l2a']\n",
293 | "dc.list_measurements().loc[products]"
294 | ]
295 | },
296 | {
297 | "cell_type": "markdown",
298 | "id": "9ea5930e-0ae0-4086-a0d0-7aca7aff82ac",
299 | "metadata": {},
300 | "source": [
301 | "Cada una de estas filas se corresponde con una \"banda\" del sensor. Como se puede apreciar, no todas corresponden a mediciones directas realizadas por el sensor. En el caso de Landast y Sentinel, siempre están presentes bandas de calidad. También hay algunos sub-productos en algunos casos.\n",
302 | "\n",
303 | "Hay que prestar atención a las columnas `dtype`, `units` y `nodata`. En ellas se indican el tipo de dato (entero corto, largo, flotante, etc), las unidades, y el valor de datos NA, respectivamente. Hay que tenerlos presente al momento de trabajar con alguna de estas métricas. Por ejemplo, las bandas de reflectancia poseen tipo entero (uint16), pero la reflectancia posee valores en el rango `[0-1]`; es por esta razón que es necesario escalar la información (10.000 en este caso).\n",
304 | "\n",
305 | "***"
306 | ]
307 | },
308 | {
309 | "cell_type": "markdown",
310 | "id": "89ca15a5-e34a-463e-948e-17e64e358a05",
311 | "metadata": {
312 | "tags": []
313 | },
314 | "source": [
315 | "# 3. Definir solicitud 📝\n",
316 | "\n",
317 | "Para poder cargar las imágenes deseadas, es necesario definir algunos puntos primero:\n",
318 | "\n",
319 | "1. Definir producto (sensor)\n",
320 | "1. Definir mediciones requeridas (bandas)\n",
321 | "1. Definir ventana temporal de trabajo (rango)\n",
322 | "1. Definir ventana espacial de trabajo (extensión: latitud, longitud)\n",
323 | "1. Definir una proyección espacial de salida (códigos EPSG)\n",
324 | "1. Definir una resolución espacial de salida (grano)\n",
325 | "\n",
326 | "Los productos y mediciones ya fueron expuestos en las secciones 2.3 y 2.4. \n",
327 | "\n",
328 | "La ventana temporal de trabajo corresponde al rango en formato \"YYYY-MM-DD\" (Año-Mes-Día). Este rango puede ser mayor al rango de imágenes disponible para el producto y debe ser especificado de la siguiente manera `(\"2020-01-01\", \"2020-12-31\")`. Esto quiere decir, desde el 01 de enero del 2020 al 31 de diciembre del 2020.\n",
329 | "\n",
330 | "La ventana espacial de trabajo corresponde a la extensión geográfica cuyas coordenadas se proporcionan en grados decimales. Por ejemplo si quisiera capturar el área metropolitana de Santiago de Chile, debo proporcionar la latitud (eje y) como `(-34.0, -33.0)`, y la longitud (eje x) como `(-71.0, -70.0)`.\n",
331 | "\n",
332 | "La proyección espacial de salida se especifica siguiendo los códigos EPSG, para indicar que utilizaremos UTM en zona 19 Sur debemos escribir `\"EPSG:32719\"`. Puede explorar la siguiente página para más opciones [ESPG](https://epsg.io/)\n",
333 | "\n",
334 | "La resolución espacial de salida se puede solicitar con valores diferente a la resolución nativa. Para ello, basta con modificar la opción de resolución a la deseada. Por defecto, se utiliza el método de remuestreo \"nearest neighbour\", pero los siguientes están también disponibles (agregando por ejemplo, la opción `resampling= \"cubic\"`:\n",
335 | "```\n",
336 | "\"nearest\", \"cubic\", \"bilinear\", \"cubic_spline\", \"lanczos\", \n",
337 | "\"average\", \"mode\", \"gauss\", \"max\", \"min\", \"med\", \"q1\", \"q3\"\n",
338 | "```\n",
339 | ">**Nota**: Las unidades de la opción `resolution` corresponden a las unidades del crs específicado en `output_crs`. Hay que prestar atención a que normalmente el primer elemento es negativo, dada la forma en que se almacenan las coordenadas en las imágenes (el inicio está esquina superior izquierda) y la forma en que operan la mayoría de los crs (con un \"inicio\" en la esquina inferior izquierda).\n",
340 | "\n",
341 | "También es posible añadir la opción `group_by= \"solar_day\"` para que escenas de diferentes horas (diferentes 'tiles'), pero un mismo día queden en el cubo como si fuera una sola gran escena (si pertecene al mismo día).\n",
342 | "\n",
343 | "Siguiendo lo ya mencionado, podemos solicitar todas las mediciones (bandas) de landsat 8 para Santiago en el año 2020."
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": null,
349 | "id": "ba6250b6-8a51-4574-b596-e0ff67a9c4e2",
350 | "metadata": {
351 | "tags": []
352 | },
353 | "outputs": [],
354 | "source": [
355 | "query = {\n",
356 | " \"product\": \"landsat8_c2l2_sr\",\n",
357 | " \"y\": (-34.0, -33.0), \n",
358 | " \"x\": (-71.0, -70.0),\n",
359 | " \"time\": (\"2020-01-01\", \"2020-12-31\"),\n",
360 | " \"output_crs\": \"EPSG:32719\",\n",
361 | " \"resolution\": (-30, 30),\n",
362 | " \"dask_chunks\": {\"time\": 1, 'x':2048, 'y':2048},\n",
363 | " \"group_by\": \"solar_day\"\n",
364 | "}"
365 | ]
366 | },
367 | {
368 | "cell_type": "markdown",
369 | "id": "59833592-df7d-423a-9386-e344e838e42a",
370 | "metadata": {},
371 | "source": [
372 | "Desde el query anterior queda pendiente definir la utilización de \"dask_chunks\". Esto se realizará en notebooks más avanzados.\n",
373 | "\n",
374 | "***"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "id": "a5366a10-2670-4403-ace0-2904b2fa5203",
380 | "metadata": {},
381 | "source": [
382 | "# 4. Solicitar datos 🖅\n",
383 | "\n",
384 | "Utilizaremos el query anterior para cargar el requerimiento al cubo de la siguiente forma:"
385 | ]
386 | },
387 | {
388 | "cell_type": "code",
389 | "execution_count": null,
390 | "id": "9d4cd622-bee4-4500-ad38-3c9cd2a77a91",
391 | "metadata": {
392 | "tags": []
393 | },
394 | "outputs": [],
395 | "source": [
396 | "ds = dc.load(**query)"
397 | ]
398 | },
399 | {
400 | "cell_type": "markdown",
401 | "id": "514a8f13-1bb1-44fc-aebf-8396323c462d",
402 | "metadata": {
403 | "tags": []
404 | },
405 | "source": [
406 | "***\n",
407 | "\n",
408 | "# 5. Explorar datos solicitados 🔎"
409 | ]
410 | },
411 | {
412 | "cell_type": "markdown",
413 | "id": "6472e00b-05b6-48c3-897a-c7e8b606dd15",
414 | "metadata": {},
415 | "source": [
416 | "El objeto `ds` contiene los datos solicitados almacenado como `xarray.Dataset`.\n",
417 | "\n",
418 | "`OpenDataCube` funciona sobre la librería [`xarray`](https://xarray-contrib.github.io/xarray-tutorial/scipy-tutorial/00_overview.html), la cual permite trabajar en multiples dimensiones de manera eficiente. La estructura de datos es muy similar al formato [NetCDF](https://psl.noaa.gov/data/gridded/whatsnetCDF.html) y una de las principales ventajas, es que permite trabajar con la librería [`Dask`](https://dask.org/), la cual permite paralelizar cargas de trabajo."
419 | ]
420 | },
421 | {
422 | "cell_type": "code",
423 | "execution_count": null,
424 | "id": "ccf9b74d-3ac6-4018-a8ba-f08f4cd1290e",
425 | "metadata": {
426 | "tags": []
427 | },
428 | "outputs": [],
429 | "source": [
430 | "ds"
431 | ]
432 | },
433 | {
434 | "cell_type": "markdown",
435 | "id": "609587a3-c869-4036-b694-fe84fb61ff1c",
436 | "metadata": {
437 | "tags": []
438 | },
439 | "source": [
440 | "La ejecución de la celda anterior nos muestra el contenido del objeto `ds`:\n",
441 | "\n",
442 | "1. ***Dimensions***: es un listado simple, que identifica las dimensiones del arreglo. En este caso es tridimencinoal con (tiempo, latitud, longitud), pero cualquier otra combinación es posible (1, 2, 3, 4, ..., X dimensiones).\n",
443 | "1. ***Coordinates***: identifica el valor de cada una de las dimensiones, en cada uno de sus \"instantes\". En este caso `time` representa la fecha para cada tiempo, `x` e `y` son las coordenadas geográficas de cada pixel.\n",
444 | "1. ***Data variables***: son las mediciones, normalmente \"bandas\" del sensor, pero puede ser cualquier otro tipo. En este caso, por cada unidad `time`, existe una imagen completa con coordenadas `x`, `y` (un objeto `xarray.DataArray`).\n",
445 | "1. ***Attributes***: identifica el sistema de coordenadas de referencia de la información cargada.\n",
446 | "\n",
447 | "Se puede ver con mayor detalle alguna de las variables/mediciones. En el fondo esta una colección dentro de otra."
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "execution_count": null,
453 | "id": "afd267c0-1577-4996-af51-2f40386558be",
454 | "metadata": {
455 | "tags": []
456 | },
457 | "outputs": [],
458 | "source": [
459 | "ds['red']"
460 | ]
461 | },
462 | {
463 | "cell_type": "markdown",
464 | "id": "33af57db-cf72-4145-92ef-59aeceadc219",
465 | "metadata": {
466 | "tags": []
467 | },
468 | "source": [
469 | "El objeto anterior corresponde a un subconjunto de `ds`, correspondiente a la banda roja almacenada como `xarray.DataArray`. \n",
470 | "\n",
471 | "Es importante resaltar algunos aspectos respecto a la salida anterior.\n",
472 | "\n",
473 | "1. **Bytes**: Tamaño total de la colección, y el tamaño de cada chunk.\n",
474 | "1. **Shape**: Dimensiones del arreglo. En este caso es (tiempo, y, x) para toda la colección solicitada y para cada chunk. En este último caso, un chunk sólo abarca un tiempo según lo definido en el query.\n",
475 | "1. **Data type**: tipo de datos (entero, flotante, etc) y estructura (numpy array, dask array, etc).\n",
476 | "\n",
477 | "Por lo demás, las coordenadas y atributos son similares al objeto anterior.\n",
478 | "\n",
479 | "Un punto importante sobre el funcionamiento del DataCube, es que `xarray` es *lazy*, lo que quiere decir que no hace todo los cálculos en el momento en que se escribe, si no que lo hace sólo cuando es necesario. Es importante tener esto presente, porque por cada acción que se le solicita al cubo, se van acumulando tareas y si se llega a un número demasiado alto, al momento de hacer un cálculo tomará demasiado tiempo y el sistema podría colapsar.\n",
480 | "\n",
481 | "En otros tutoriales ahondaremos más en Dask; es un tema importante, pero extenso.\n",
482 | "\n",
483 | "***"
484 | ]
485 | },
486 | {
487 | "cell_type": "markdown",
488 | "id": "3064c1c7-fbbb-4250-9777-5bd6d0e777f2",
489 | "metadata": {},
490 | "source": [
491 | "# 6 Visualización 🗺\n",
492 | "\n",
493 | "La visualización de información es una etapa crucial en el manejo de información satelital. En este apartado se verá como desplegar la zona de estudio, mostrar una banda en particular y generar algunos compuestos.\n",
494 | "\n",
495 | "## 6.1 Área de estudio\n",
496 | "\n",
497 | "Dentro de las funciones utilitarias que hay, está la que se muestra a continuación, la cual permite revisar el área de estudio en un mapa interactivo con Google Maps.\n",
498 | "De esta forma, se puede revisar que la zona sea la apropiada para el fenómeno que se desea estudiar."
499 | ]
500 | },
501 | {
502 | "cell_type": "code",
503 | "execution_count": null,
504 | "id": "1831620f-ff8c-49e5-a083-972cdeb911e0",
505 | "metadata": {
506 | "tags": []
507 | },
508 | "outputs": [],
509 | "source": [
510 | "from dea_tools.plotting import display_map, rgb"
511 | ]
512 | },
513 | {
514 | "cell_type": "code",
515 | "execution_count": null,
516 | "id": "64590419-c473-4600-b1e3-97a85fa7431d",
517 | "metadata": {
518 | "tags": []
519 | },
520 | "outputs": [],
521 | "source": [
522 | "display_map(x = query['x'], y = query['y'])"
523 | ]
524 | },
525 | {
526 | "cell_type": "markdown",
527 | "id": "80e307b8-a4ff-4f17-9fd9-4f7694a9154d",
528 | "metadata": {
529 | "tags": []
530 | },
531 | "source": [
532 | "## 6.2 Bandas\n",
533 | "\n",
534 | "Para visualizar bandas individuales utilizaremos `matplotlib`, una librería clásica de Python que está completamente integrada con `xarray`. Esta librería puede ser llamada de manera tradicional o como un método dentro del arreglo.\n",
535 | "\n",
536 | "Como ya exploramos la banda roja del objeto `ds`, ahora exploraremos la primera imagen de esta banda utilizando el siguiente código:"
537 | ]
538 | },
539 | {
540 | "cell_type": "code",
541 | "execution_count": null,
542 | "id": "b9a7c57b-e54f-4ca7-854a-006d6fea012c",
543 | "metadata": {
544 | "tags": []
545 | },
546 | "outputs": [],
547 | "source": [
548 | "primera_roja = ds['red'].isel(time = 0)\n",
549 | "primera_roja"
550 | ]
551 | },
552 | {
553 | "cell_type": "markdown",
554 | "id": "01861865-d7b9-43cb-8d8c-19980982363a",
555 | "metadata": {},
556 | "source": [
557 | "Del resultado anterior destacamos:\n",
558 | "\n",
559 | "1. Como se seleccionó una unidad de tiempo en particular, dicha dimensión fue descartada del arreglo (ahora posee dimensiones `x`, `y` solamente. El tiempo (`time`), pasó a ser una etiqueta informativa solamente.\n",
560 | "2. Se utilizó `isel`, que realiza una selección por ubicación y no por el valor. En este caso `.isel(time=0)` selecciona la primera fecha disponible. `.isel=1` seleccionará la segunda fecha disponible y así hasta el largo de la dimensión. Para seleccionar por el valor y no la posición se utiliza `sel`, que en este caso sería `sel='2022-01-14'`.\n",
561 | "\n",
562 | ">**Nota**: Es importante notar que Python cuenta la posición en los arreglos de cualquier tipo, partiendo por 0. Así, para acceder al primer elemento de un vector (por ejemplo `mi_vector`) de largo 10, se utiliza `mi_vector[0]`; para acceder al último elemento, se puede utilizar `mi_vector[9]` o `mi_vector[-1]`. Si se utiliza `mi_vector[10]` arrojará un error porque no encuentra el elemento.\n",
563 | "\n",
564 | "Para graficar el objeto `primera_roja`, basta con usar el método `plot()` del objeto:"
565 | ]
566 | },
567 | {
568 | "cell_type": "code",
569 | "execution_count": null,
570 | "id": "3cd252b5-5693-4006-a2cf-7426e8b38020",
571 | "metadata": {
572 | "tags": []
573 | },
574 | "outputs": [],
575 | "source": [
576 | "primera_roja.plot()"
577 | ]
578 | },
579 | {
580 | "cell_type": "markdown",
581 | "id": "8853a386-b75c-4102-a70e-869bb5f91f7f",
582 | "metadata": {
583 | "tags": []
584 | },
585 | "source": [
586 | "Para visualizar más escenas en un mismo plot se puede utilizar el siguiente código:"
587 | ]
588 | },
589 | {
590 | "cell_type": "code",
591 | "execution_count": null,
592 | "id": "87c05c07-c761-4fd3-8ad5-b3959d4e956f",
593 | "metadata": {
594 | "tags": []
595 | },
596 | "outputs": [],
597 | "source": [
598 | "ds['red'].isel(time=range(3)).plot(col='time', figsize=(20, 5))"
599 | ]
600 | },
601 | {
602 | "cell_type": "markdown",
603 | "id": "616f47f4-d1d2-493d-86fa-23534fe64a3b",
604 | "metadata": {
605 | "tags": []
606 | },
607 | "source": [
608 | "Podemos continuar agregando argumentos y ajustando parámetros para mejorar la visualización, tal como el siguiente ejemplo:"
609 | ]
610 | },
611 | {
612 | "cell_type": "code",
613 | "execution_count": null,
614 | "id": "963225b1-efca-4fbb-a387-ed03b6b04f99",
615 | "metadata": {
616 | "tags": []
617 | },
618 | "outputs": [],
619 | "source": [
620 | "ds['red'].isel(time=range(3)).plot(col='time', robust=True, cmap='inferno', figsize=(20, 5))"
621 | ]
622 | },
623 | {
624 | "cell_type": "markdown",
625 | "id": "80db6640-e4f7-414b-9b29-eef49d241223",
626 | "metadata": {},
627 | "source": [
628 | "Para mayor información sobre este tipo de gráficos, revisar la [documentación de xarray](http://xarray.pydata.org/en/stable/user-guide/plotting.html#faceting)\n",
629 | "\n",
630 | "## 6.3 Falso color\n",
631 | "\n",
632 | "Visualizar la escena en colores, tal como lo harían nuestros ojos, es un poco más complicado.\n",
633 | "Para esto utilizamos la técnica de falso color, que permite combinar tres bandas para construir una imagen en colores RGB (Red - Green - Blue).\n",
634 | "\n",
635 | "Exploraremos tres formas:\n",
636 | "1. La función `rgb` es una función extra, parte del ecosistema de ODC, que permite visualizar fácilmente una imagen en colores RGB de un dataset."
637 | ]
638 | },
639 | {
640 | "cell_type": "code",
641 | "execution_count": null,
642 | "id": "4e96a737-540a-4cd2-9be6-f4b79f60e37c",
643 | "metadata": {
644 | "tags": []
645 | },
646 | "outputs": [],
647 | "source": [
648 | "rgb(ds, bands=[\"red\", \"green\", \"blue\"], index=1)"
649 | ]
650 | },
651 | {
652 | "cell_type": "markdown",
653 | "id": "3c9908f0-bab8-4dc0-ad7e-631e89cae487",
654 | "metadata": {
655 | "tags": []
656 | },
657 | "source": [
658 | "2. Utilizando `matplotlib`, es una forma más manual pero que permite mayor control."
659 | ]
660 | },
661 | {
662 | "cell_type": "code",
663 | "execution_count": null,
664 | "id": "9d0a0b61-aed5-461e-a2c0-d51ac887af13",
665 | "metadata": {
666 | "tags": []
667 | },
668 | "outputs": [],
669 | "source": [
670 | "ds[[\"red\", \"green\", \"blue\"]].isel(time=1).to_array().plot.imshow(vmin=7500, vmax=15000, robust=True, figsize=(5, 6))"
671 | ]
672 | },
673 | {
674 | "cell_type": "markdown",
675 | "id": "65109d71-fa46-44ae-ab9e-ffac449c3bb2",
676 | "metadata": {
677 | "tags": []
678 | },
679 | "source": [
680 | "3. Utilizando `holoviews`, una librería que permite una mayor dinamismo y velocidad al momento de visualizar una imagen. Es más compleja de utilizar, pero tiene varias ventajas para el usuario final."
681 | ]
682 | },
683 | {
684 | "cell_type": "code",
685 | "execution_count": null,
686 | "id": "56393434-686f-4586-8ca4-066291db4e3e",
687 | "metadata": {
688 | "tags": []
689 | },
690 | "outputs": [],
691 | "source": [
692 | "import holoviews as hv\n",
693 | "import hvplot.xarray\n",
694 | "from datashader import reductions"
695 | ]
696 | },
697 | {
698 | "cell_type": "markdown",
699 | "id": "8f744c13-66f7-445a-8e7b-5e0a6af56c59",
700 | "metadata": {},
701 | "source": [
702 | "Este código es para visualizar una sola banda:"
703 | ]
704 | },
705 | {
706 | "cell_type": "code",
707 | "execution_count": null,
708 | "id": "f39ffbf7-a92e-460c-b3a4-d75fe68c8ad4",
709 | "metadata": {
710 | "tags": []
711 | },
712 | "outputs": [],
713 | "source": [
714 | "ds['red'].hvplot.image(\n",
715 | " x = 'x', y = 'y', # Dataset x,y dimension names \n",
716 | " rasterize = True, # If False, data will not be reduced. This is slow to load but all data is loaded.\n",
717 | " aggregator = reductions.mean(), # Datashader calculates the mean value for reductions (also first, min, max, las, std, mode)\n",
718 | " precompute = True, # Datashader precomputes what it can\n",
719 | " data_aspect = 1\n",
720 | " )"
721 | ]
722 | },
723 | {
724 | "cell_type": "markdown",
725 | "id": "b813a777-3ff1-4c94-ba6d-1488baa419b6",
726 | "metadata": {
727 | "tags": []
728 | },
729 | "source": [
730 | "Este código para visualizar un RGB:"
731 | ]
732 | },
733 | {
734 | "cell_type": "code",
735 | "execution_count": null,
736 | "id": "aa39b8be-cc4d-4403-8ae0-caa711f7cd58",
737 | "metadata": {
738 | "tags": []
739 | },
740 | "outputs": [],
741 | "source": [
742 | "ds[[\"red\", \"green\", \"blue\"]].to_array().hvplot.rgb(\n",
743 | " x = 'x', y = 'y', # Dataset x,y dimension names \n",
744 | " bands = 'variable', \n",
745 | " rasterize = True, # If False, data will not be reduced. This is slow to load but all data is loaded.\n",
746 | " aggregator = reductions.mean(), # Datashader calculates the mean value for reductions (also first, min, max, las, std, mode)\n",
747 | " precompute = True, # Datashader precomputes what it can\n",
748 | " data_aspect = 1, \n",
749 | " )"
750 | ]
751 | },
752 | {
753 | "cell_type": "markdown",
754 | "id": "70761701-ff51-48b9-8534-ba5687e0a11f",
755 | "metadata": {},
756 | "source": [
757 | "¿Por qué no funciona? Porque, en primer lugar, es necesario transformar la información a reflectancia... Lo veremos en el siguiente notebook!"
758 | ]
759 | },
760 | {
761 | "cell_type": "code",
762 | "execution_count": null,
763 | "id": "a15f4554-257e-406a-932f-261cbbff2afb",
764 | "metadata": {},
765 | "outputs": [],
766 | "source": [
767 | "client.close()\n",
768 | "\n",
769 | "cluster.close()"
770 | ]
771 | },
772 | {
773 | "cell_type": "markdown",
774 | "id": "8832b748-952d-4f7c-aed4-4edd4de44547",
775 | "metadata": {
776 | "tags": []
777 | },
778 | "source": [
779 | "***\n",
780 | "\n",
781 | "# *Siguientes pasos* 🐾\n",
782 | "\n",
783 | "Para continuar con el tutorial pueden acceder a los notebooks del siguiente listado.\n",
784 | "\n",
785 | "1. [Acceso](00_Acceso.ipynb)\n",
786 | "2. **Cargar datos**\n",
787 | "3. [Limpieza](02_Limpieza.ipynb)\n",
788 | "4. [Análisis básico](03_Análisis_básico.ipynb)\n",
789 | "5. [Caso de estudio 1](04_Caso_de_estudio_1.ipynb)\n",
790 | "6. [Caso de estudio 2](05_Caso_de_estudio_2.ipynb)\n",
791 | "7. [Trabajo final](04_Actividad_final.ipynb)\n",
792 | "\n",
793 | "***"
794 | ]
795 | }
796 | ],
797 | "metadata": {
798 | "kernelspec": {
799 | "display_name": "Python 3 (ipykernel)",
800 | "language": "python",
801 | "name": "python3"
802 | },
803 | "language_info": {
804 | "codemirror_mode": {
805 | "name": "ipython",
806 | "version": 3
807 | },
808 | "file_extension": ".py",
809 | "mimetype": "text/x-python",
810 | "name": "python",
811 | "nbconvert_exporter": "python",
812 | "pygments_lexer": "ipython3",
813 | "version": "3.10.12"
814 | }
815 | },
816 | "nbformat": 4,
817 | "nbformat_minor": 5
818 | }
819 |
--------------------------------------------------------------------------------
/básico/02_Limpieza.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "9bb1dd0c-c676-4c1d-948c-ddd7c24bc833",
6 | "metadata": {},
7 | "source": [
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "06ada783-3520-40e4-afde-cbbdbd7b33a3",
14 | "metadata": {},
15 | "source": [
16 | "# Limpieza de datos en el DataCube Chile 🌎\n",
17 | "\n",
18 | "En ese apartado, exploraremos las operaciones básicas para limpiar nuestros datos previo a la realización de análisis de cualquier índole, estas operaciones son:\n",
19 | "1. Enmascarar valores no válidos y/o no requeridos\n",
20 | "1. Descartar escenas con insuficiente información\n",
21 | "1. Reescalar valores digitales a reflectancia\n",
22 | "\n",
23 | "> Este notebook se centrará en Landsat-8. Hay tutoriales para Sentinel-2 y Fasat-Charlie en `Material Complementario` al final de este notebook.\n",
24 | "\n",
25 | "Para esta limpieza utilizaremos una escena más pequeña que la del notebook anterior, enfocándonos en el 05 de Agosto del 2020 debido a que tiene una buena combinación de nieve y nubes."
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "id": "90f5592d-095e-4a1f-b1ea-736cf4b8247b",
32 | "metadata": {
33 | "tags": []
34 | },
35 | "outputs": [],
36 | "source": [
37 | "from dask.distributed import Client, LocalCluster\n",
38 | "cluster = LocalCluster()\n",
39 | "client = Client(cluster)"
40 | ]
41 | },
42 | {
43 | "cell_type": "code",
44 | "execution_count": null,
45 | "id": "b0e9aebe-f721-4958-8691-64b79e8f49f7",
46 | "metadata": {
47 | "tags": []
48 | },
49 | "outputs": [],
50 | "source": [
51 | "import datacube\n",
52 | "import xarray as xr\n",
53 | "import numpy as np\n",
54 | "import pandas as pd\n",
55 | "import matplotlib.pyplot as plt\n",
56 | "\n",
57 | "from odc.ui import DcViewer\n",
58 | "from datacube.utils.rio import configure_s3_access\n",
59 | "\n",
60 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "id": "08bc4baa-1ee1-40f6-8e42-78f4f87db48c",
67 | "metadata": {
68 | "tags": []
69 | },
70 | "outputs": [],
71 | "source": [
72 | "dc = datacube.Datacube(app='Basic_tutorial') "
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": null,
78 | "id": "d19e07e4-b498-4166-8c70-e6a47e7c2764",
79 | "metadata": {
80 | "tags": []
81 | },
82 | "outputs": [],
83 | "source": [
84 | "query = {\n",
85 | " \"product\": \"landsat8_c2l2_sr\",\n",
86 | " \"y\": (-33.75, -33.25), \n",
87 | " \"x\": (-70.75, -70.25),\n",
88 | " \"time\": (\"2020-01-01\", \"2020-12-31\"),\n",
89 | " \"output_crs\": \"EPSG:32719\",\n",
90 | " \"resolution\": (-30, 30),\n",
91 | " \"dask_chunks\": {\"time\": 1, 'x':2048, 'y':2048},\n",
92 | " \"group_by\": \"solar_day\"\n",
93 | "}"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "id": "2af5f264-4619-4016-9d0c-d820d29705dd",
100 | "metadata": {
101 | "tags": []
102 | },
103 | "outputs": [],
104 | "source": [
105 | "ds = dc.load(**query)"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": null,
111 | "id": "f936e899-d8f6-4007-b7da-5cff0af5c07f",
112 | "metadata": {
113 | "tags": []
114 | },
115 | "outputs": [],
116 | "source": [
117 | "ds[[\"red\", \"green\", \"blue\"]].sel(time = \"2020-08-05\").squeeze().to_array().plot.imshow(vmin=7500, vmax=15000, robust=True, figsize=(10, 10))"
118 | ]
119 | },
120 | {
121 | "cell_type": "markdown",
122 | "id": "92b1e47f-2597-4bee-aecb-4d79c0c12741",
123 | "metadata": {},
124 | "source": [
125 | "***\n",
126 | "\n",
127 | "# 1. Enmascarar valores no válidos y/o no requeridos\n",
128 | "\n",
129 | "Al realizar cualquier análisis es recomendable enmascarar tanto ciertos valores que de antemano se conocen como inválidos, como ciertos elementos que no sean requeridos, debido a que su inclusión puede llevarnos a mal interpretar resultados. Por ejemplo, en un estudio de la dinámica de la vegetación un sensor pudo captar una nube en un momento determinado, y esa nube puede contaminar los valores de determinados índices. \n",
130 | "\n",
131 | "La mayoría de los sensores más tradicionales incorporan dentro de sus mediciones una banda de calidad que indica, entre otras cosas, la presencia de nubes en un pixel determinado. Utilizando dicha banda se pueden aislar los píxeles dentro de cada escena que no tengan información de interés, en vez de descartar la escena completa. Estas bandas de calidad son calculadas por cada proveedor y dependen de los algoritmos de corrección atmosférica de los que se disponen y, si bien son muy útiles, **no** son completamente infalibles.\n",
132 | "\n",
133 | "Primero, importamos la librería de enmascaramiento, y luego, vemos una descripción de la banda de calidad con la que cuenta Landsat 8:\n"
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": null,
139 | "id": "ca45a39e-f2f5-4ee6-b08c-f75e3258e0ed",
140 | "metadata": {
141 | "tags": []
142 | },
143 | "outputs": [],
144 | "source": [
145 | "from datacube.utils import masking"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": null,
151 | "id": "14b719c5-f975-4208-87e1-449743b5583c",
152 | "metadata": {
153 | "tags": []
154 | },
155 | "outputs": [],
156 | "source": [
157 | "bandas_reflectancia = [\"coastal\", \"blue\", \"green\", \"red\", \"nir08\", \"swir16\", \"swir22\"]\n",
158 | "masking.describe_variable_flags(ds.qa_pixel)"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "id": "d575bef0-99a6-461f-8f1c-e1d6b4e7e58a",
164 | "metadata": {},
165 | "source": [
166 | "Existen diversas formas de realizar el proceso de enmascaramiento, describiremos dos de las formas más sencillas:\n",
167 | "\n",
168 | "1. Marcar los píxeles que queremos (buenos) y enmascarar el resto (asignarles valor Not Available [NA]).\n",
169 | "1. Marcar los píxeles que no queremos (malos) y enmascararlos.\n",
170 | "\n",
171 | "La diferencia radica principalmente en la condición de unión que se dará al momento de construir la máscara, ya sea con el operador lógico `AND` (`&`) o el operador lógico `OR` (`|`). De estar bien construidos ambos entregarán el mismo resultado.\n",
172 | "\n",
173 | "Mostraremos ambas opciones, porque dependiendo de lo que se necesite realizar, a veces es más sencillo utilizar una forma por sobre la otra.\n",
174 | "\n",
175 | "> **Nota**: existen formas incluso \"más manuales\" de realizar el enmascaramiento involucrando operaciones en binario.\n",
176 | "\n",
177 | "> **Nota**: Al enmascarar, hay que considerar que lo que es `True` es lo válido (valores deseados) y lo que se desea enmascarar, es `False`. En el fondo, los píxeles \"buenos\" son los que se marcan. \n",
178 | "\n",
179 | "## 1.1 Operador lógico `AND`\n",
180 | "\n",
181 | "Aquí marcaremos los píxeles que sí queremos mantener y enmascararemos el resto, en ambos casos buscamos sacar de la escena la nieve `\"snow\"`, las nubes `\"cloud\"`, las nubes \"cirrus\" `\"cirrus\"`, y la sombra que producen las nubes `\"cloud_shadow\"`, y los píxeles inválidos `\"nodata\"`."
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": null,
187 | "id": "2557d04a-648f-4644-96cf-17f34a1bac8f",
188 | "metadata": {
189 | "tags": []
190 | },
191 | "outputs": [],
192 | "source": [
193 | "good_pixel_flags = {\n",
194 | " \"snow\": \"not_high_confidence\",\n",
195 | " \"cloud\": \"not_high_confidence\",\n",
196 | " \"cirrus\": \"not_high_confidence\",\n",
197 | " \"cloud_shadow\": \"not_high_confidence\",\n",
198 | " \"nodata\": False\n",
199 | "}"
200 | ]
201 | },
202 | {
203 | "cell_type": "code",
204 | "execution_count": null,
205 | "id": "5c32f38d-47f5-4d7e-ac9c-043ecffbf63e",
206 | "metadata": {
207 | "tags": []
208 | },
209 | "outputs": [],
210 | "source": [
211 | "quality_band = 'qa_pixel'\n",
212 | "cloud_free_mask1 = masking.make_mask(ds[quality_band], **good_pixel_flags)\n",
213 | "dsf = ds[bandas_reflectancia].where(cloud_free_mask1)"
214 | ]
215 | },
216 | {
217 | "cell_type": "code",
218 | "execution_count": null,
219 | "id": "3f770cce-354c-4327-9456-e304a7a5f0be",
220 | "metadata": {
221 | "tags": []
222 | },
223 | "outputs": [],
224 | "source": [
225 | "fig, ax = plt.subplots(1, 1, figsize=(10, 10))\n",
226 | "dsf[[\"red\", \"green\", \"blue\"]].sel(time = \"2020-08-05\").squeeze().to_array().plot.imshow(vmin=7500, vmax=15000, robust=True, ax = ax)\n",
227 | "ax.set_facecolor(\"black\")"
228 | ]
229 | },
230 | {
231 | "cell_type": "markdown",
232 | "id": "f8e6d72c-8c79-4194-a625-921e31c87496",
233 | "metadata": {},
234 | "source": [
235 | "## 1.2 Operador lógico `OR`\n",
236 | "\n",
237 | "Aquí marcaremos los píxeles que no queremos y los enmascaremos.\n"
238 | ]
239 | },
240 | {
241 | "cell_type": "code",
242 | "execution_count": null,
243 | "id": "c062242a-0fe3-45f5-9890-2a0607243e7e",
244 | "metadata": {
245 | "tags": []
246 | },
247 | "outputs": [],
248 | "source": [
249 | "cloud_free_mask2 = (\n",
250 | " masking.make_mask(ds[quality_band], snow='high_confidence') | \n",
251 | " masking.make_mask(ds[quality_band], cloud=\"high_confidence\") |\n",
252 | " masking.make_mask(ds[quality_band], cirrus=\"high_confidence\") |\n",
253 | " masking.make_mask(ds[quality_band], cloud_shadow=\"high_confidence\") |\n",
254 | " masking.make_mask(ds[quality_band], nodata=True)\n",
255 | ")"
256 | ]
257 | },
258 | {
259 | "cell_type": "code",
260 | "execution_count": null,
261 | "id": "8416aecf-0413-47a2-b00d-bf07cc59d4c6",
262 | "metadata": {
263 | "tags": []
264 | },
265 | "outputs": [],
266 | "source": [
267 | "dsf = xr.where(cloud_free_mask2, np.nan, ds[bandas_reflectancia])"
268 | ]
269 | },
270 | {
271 | "cell_type": "code",
272 | "execution_count": null,
273 | "id": "d78bf023-a964-476c-8d70-a18bd8b40663",
274 | "metadata": {
275 | "tags": []
276 | },
277 | "outputs": [],
278 | "source": [
279 | "fig, ax = plt.subplots(1, 1, figsize=(10, 10))\n",
280 | "dsf[[\"red\", \"green\", \"blue\"]].sel(time = \"2020-08-05\").squeeze().to_array().plot.imshow(vmin=7500, vmax=15000, robust=True, ax = ax)\n",
281 | "ax.set_facecolor(\"black\")"
282 | ]
283 | },
284 | {
285 | "cell_type": "markdown",
286 | "id": "378a0c1d-16ad-42e8-ae54-6c8590007c9c",
287 | "metadata": {},
288 | "source": [
289 | "Se aprecia el mismo resultado en ambos casos.\n",
290 | "\n",
291 | "***\n",
292 | "\n",
293 | "# 2. Descartar escenas con insuficiente información\n",
294 | "\n",
295 | "El siguiente paso, también opcional pero recomendado, es descartar imágenes que contengan una cierta cantidad de píxeles inválidos o no requeridos, por sobre un umbral definido. En este ejercicio definimos un umbral de 80% (`valid_threshold = 0.8`), si una fecha tiene una proporción de píxeles válidos mayor o igual al 80%, esta se mantendrá para nuestro análisis."
296 | ]
297 | },
298 | {
299 | "cell_type": "code",
300 | "execution_count": null,
301 | "id": "c7bfae24-789d-4cbc-b158-52f28bdb1966",
302 | "metadata": {
303 | "tags": []
304 | },
305 | "outputs": [],
306 | "source": [
307 | "valid_pixel_proportion = cloud_free_mask1.sum(dim=(\"x\", \"y\"))/(cloud_free_mask1.shape[1] * cloud_free_mask1.shape[2])\n",
308 | "\n",
309 | "valid_threshold = 0.8\n",
310 | "\n",
311 | "observations_to_keep = (valid_pixel_proportion >= valid_threshold)"
312 | ]
313 | },
314 | {
315 | "cell_type": "code",
316 | "execution_count": null,
317 | "id": "ed4b53b5-b75d-4cf5-a18d-ee2172f308da",
318 | "metadata": {
319 | "tags": []
320 | },
321 | "outputs": [],
322 | "source": [
323 | "dsf = ds[bandas_reflectancia].where(cloud_free_mask1)\n",
324 | "ds_keep = dsf.sel(time=observations_to_keep).persist()"
325 | ]
326 | },
327 | {
328 | "cell_type": "markdown",
329 | "id": "6a65fbd7-e138-4512-9b7a-f3d2b3147ecc",
330 | "metadata": {},
331 | "source": [
332 | ">**Nota**: En algunas ocasiones es muy útil utilizar `persist()` para obligar al conjunto de datos a hacer los cálculos que estén pendientes. Esto es útil particularmente antes de realizar una tarea que requiera dos o más veces el arreglo calculado. Por ejemplo, cada vez que se realiza un plot de la imagen, se calculan y extraen los datos. La última línea de la celda anterior utiliza `persist()` debido a que queremos evitar re-calcular los pasos anteriores cada vez que realicemos un gráfico."
333 | ]
334 | },
335 | {
336 | "cell_type": "code",
337 | "execution_count": null,
338 | "id": "8ae12c23-3c17-4c8e-9d77-40b78f350efa",
339 | "metadata": {
340 | "tags": []
341 | },
342 | "outputs": [],
343 | "source": [
344 | "ds_keep"
345 | ]
346 | },
347 | {
348 | "cell_type": "markdown",
349 | "id": "01377e80-9e9e-4ef7-bff5-93956f3016d2",
350 | "metadata": {
351 | "tags": []
352 | },
353 | "source": [
354 | "Podemos ver que el objeto `ds_keep` posee solo 17 escenas, a diferencia del objeto `ds` original que poseía 43. \n",
355 | "\n",
356 | "Estas escenas podemos visualizarlas con `matplotlib`:"
357 | ]
358 | },
359 | {
360 | "cell_type": "code",
361 | "execution_count": null,
362 | "id": "af17d82f-c606-4af7-affc-8c33efcba956",
363 | "metadata": {
364 | "tags": []
365 | },
366 | "outputs": [],
367 | "source": [
368 | "ds_keep[[\"red\", \"green\", \"blue\"]].to_array().plot.imshow(col = \"time\", col_wrap = 4, vmin=7500, vmax=15000, robust=True, figsize=(15, 20))"
369 | ]
370 | },
371 | {
372 | "cell_type": "markdown",
373 | "id": "6319daa2-328e-4262-bde8-47b643f4e9f9",
374 | "metadata": {},
375 | "source": [
376 | "***\n",
377 | "\n",
378 | "# 3. Reescalar valores digitales a reflectancia\n",
379 | "Antes de proceder, es necesario escalar los valores a reflectancia y luego calcular el índice. Para tener una idea sobre los valores de escalamiento para Landsat, se puede ver una guía [aquí](https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2-level-2-science-products) y también [acá](https://www.usgs.gov/faqs/how-do-i-use-a-scale-factor-landsat-level-2-science-products?qt-news_science_products=0#qt-news_science_products).\n",
380 | "Para este producto en particular, esta es la [documentación oficial](https://d9-wret.s3.us-west-2.amazonaws.com/assets/palladium/production/s3fs-public/media/files/LSDS-1619_Landsat-8-9-C2-L2-ScienceProductGuide-v4.pdf).\n",
381 | "\n",
382 | "¿Cómo sabemos que tenemos valores digitales?, porque los valores de reflectividad debe ser un decimal entre 0 y 1.\n",
383 | "Si graficamos una banda en particular, por ejemplo la banda roja, podemos observar el rango de valores que toma la imagen."
384 | ]
385 | },
386 | {
387 | "cell_type": "code",
388 | "execution_count": null,
389 | "id": "f48ae1d7-85f5-4084-b257-163d31c48592",
390 | "metadata": {
391 | "tags": []
392 | },
393 | "outputs": [],
394 | "source": [
395 | "ds_keep['red'].sel(time = \"2020-08-21\").plot(robust = True, figsize=(10,8))"
396 | ]
397 | },
398 | {
399 | "cell_type": "markdown",
400 | "id": "785ce6f7-8c4f-48e6-8675-48bfa15431c8",
401 | "metadata": {
402 | "tags": []
403 | },
404 | "source": [
405 | "Es posible observar valores entre 8000 y 15000, muy lejano de los decimales entre 0 y 1 que requerimos.\n",
406 | "\n",
407 | "Como estamos utilizando Landsat 8 Collection 2 debemos corregir utilizando `ND*0.0000275 + -0.2`, donde `ND` es el valor que posee cada pixel en cada banda, esto lo hacemos con el siguiente código:"
408 | ]
409 | },
410 | {
411 | "cell_type": "code",
412 | "execution_count": null,
413 | "id": "93d7b8e6-7519-41c0-9b97-6f0edfd28b55",
414 | "metadata": {
415 | "tags": []
416 | },
417 | "outputs": [],
418 | "source": [
419 | "ds_keep.update(ds_keep * 0.0000275 + -0.2)"
420 | ]
421 | },
422 | {
423 | "cell_type": "markdown",
424 | "id": "b12a3a59-a099-4382-a538-a7280d711552",
425 | "metadata": {},
426 | "source": [
427 | "Luego, debemos mantener solo los valores que sean válidos. Debido a que la reflectancia debiese entregar valores entre 0 y 1, debemos quedarnos solo con esos valores:"
428 | ]
429 | },
430 | {
431 | "cell_type": "code",
432 | "execution_count": null,
433 | "id": "ac392e38-ba5d-43c1-b7df-99f9b36ffa6b",
434 | "metadata": {
435 | "tags": []
436 | },
437 | "outputs": [],
438 | "source": [
439 | "ds_keep.update(ds_keep.where(ds_keep >= 0).where(ds_keep <= 1))"
440 | ]
441 | },
442 | {
443 | "cell_type": "markdown",
444 | "id": "b50ccaf0-f0f3-44de-aebd-a70225529bb1",
445 | "metadata": {},
446 | "source": [
447 | "Volvemos a graficar, notando que ahora los valores en la escala de la derecha de la imagen han cambiado."
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "execution_count": null,
453 | "id": "96500ef2-e875-4968-a47c-224b65961b5e",
454 | "metadata": {
455 | "tags": []
456 | },
457 | "outputs": [],
458 | "source": [
459 | "ds_keep['red'].sel(time = \"2020-08-21\").plot(robust = True, figsize=(10,8))"
460 | ]
461 | },
462 | {
463 | "cell_type": "code",
464 | "execution_count": null,
465 | "id": "b16a86ca-ef5a-4361-9dad-c36a97534429",
466 | "metadata": {},
467 | "outputs": [],
468 | "source": [
469 | "client.close()\n",
470 | "\n",
471 | "cluster.close()"
472 | ]
473 | },
474 | {
475 | "cell_type": "markdown",
476 | "id": "844103d7-b26b-4992-9cae-7c747c7fc3bc",
477 | "metadata": {
478 | "tags": []
479 | },
480 | "source": [
481 | "***\n",
482 | "\n",
483 | "# *Siguientes pasos* 🐾\n",
484 | "\n",
485 | "Para continuar con el tutorial pueden acceder a los notebooks del siguiente listado.\n",
486 | "\n",
487 | "1. [Acceso](00_Acceso.ipynb)\n",
488 | "2. [Cargar datos](01_Cargar_datos.ipynb)\n",
489 | "3. **Limpieza**\n",
490 | "4. [Análisis básico](03_Análisis_básico.ipynb)\n",
491 | "5. [Caso de estudio 1](04_Caso_de_estudio_1.ipynb)\n",
492 | "6. [Caso de estudio 2](05_Caso_de_estudio_2.ipynb)\n",
493 | "\n",
494 | "***\n",
495 | "\n",
496 | "# *Material Complementario* 📚\n",
497 | "\n",
498 | "* [Limpieza Sentinel-2](../material_complementario/02a_Limpieza_s2l2a.ipynb)\n",
499 | "* [Limpieza Fasat-Charlie](../material_complementario/02b_Limpieza_fasat_charlie_ms.ipynb)\n",
500 | "\n",
501 | "***"
502 | ]
503 | }
504 | ],
505 | "metadata": {
506 | "kernelspec": {
507 | "display_name": "Python 3 (ipykernel)",
508 | "language": "python",
509 | "name": "python3"
510 | },
511 | "language_info": {
512 | "codemirror_mode": {
513 | "name": "ipython",
514 | "version": 3
515 | },
516 | "file_extension": ".py",
517 | "mimetype": "text/x-python",
518 | "name": "python",
519 | "nbconvert_exporter": "python",
520 | "pygments_lexer": "ipython3",
521 | "version": "3.10.12"
522 | }
523 | },
524 | "nbformat": 4,
525 | "nbformat_minor": 5
526 | }
527 |
--------------------------------------------------------------------------------
/básico/03_Análisis_básico.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "a40a7816-8854-4e8b-928e-13d9321119ef",
6 | "metadata": {},
7 | "source": [
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "2789d19d-df28-44d7-967d-c6ea19d0dc48",
14 | "metadata": {},
15 | "source": [
16 | "# Análisis básicos en el DataCube Chile 🌎\n",
17 | "\n",
18 | "En ese apartado, se verán las operaciones básicas que se pueden realizar con el DataCube:\n",
19 | "1. Calcular índices espectrales\n",
20 | "1. Realizar compuestos\n",
21 | "1. Exportar información\n",
22 | "\n",
23 | ">**Nota**: Este notebook contiene elementos extraídos desde [DataCube Australia](https://github.com/GeoscienceAustralia/dea-notebooks).\n",
24 | "\n",
25 | "Para esto volveremos a obtener los datos del notebook anterior, pero mantendremos la nieve dentro de la escena."
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "id": "19781f46-9df2-4fe8-abe1-32fbfcd39194",
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "from dask.distributed import Client, LocalCluster\n",
36 | "cluster = LocalCluster()\n",
37 | "client = Client(cluster)"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": null,
43 | "id": "3f400182-21ec-4ea8-88d0-fb3aa516857c",
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "import datacube\n",
48 | "import xarray as xr\n",
49 | "import numpy as np\n",
50 | "import pandas as pd\n",
51 | "import matplotlib.pyplot as plt\n",
52 | "\n",
53 | "from odc.ui import DcViewer\n",
54 | "from datacube.utils import masking\n",
55 | "from datacube.utils.rio import configure_s3_access\n",
56 | "\n",
57 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)"
58 | ]
59 | },
60 | {
61 | "cell_type": "code",
62 | "execution_count": null,
63 | "id": "ff8600f5-1d63-4ad1-9f07-f3d0a6ddbb60",
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "dc = datacube.Datacube(app='Basic_tutorial') "
68 | ]
69 | },
70 | {
71 | "cell_type": "code",
72 | "execution_count": null,
73 | "id": "29cf9046-5a9c-46ca-9833-d48044cb9409",
74 | "metadata": {},
75 | "outputs": [],
76 | "source": [
77 | "query = {\n",
78 | " \"product\": \"landsat8_c2l2_sr\",\n",
79 | " \"y\": (-33.75, -33.25), \n",
80 | " \"x\": (-70.75, -70.25),\n",
81 | " \"time\": (\"2020-01-01\", \"2020-12-31\"),\n",
82 | " \"output_crs\": \"EPSG:32719\",\n",
83 | " \"resolution\": (-30, 30),\n",
84 | " \"dask_chunks\": {\"time\": 1, 'x':2048, 'y':2048},\n",
85 | " \"group_by\": \"solar_day\"\n",
86 | "}\n",
87 | "good_pixel_flags = {\n",
88 | " #\"snow\": \"not_high_confidence\",\n",
89 | " \"cloud\": \"not_high_confidence\",\n",
90 | " \"cirrus\": \"not_high_confidence\",\n",
91 | " \"cloud_shadow\": \"not_high_confidence\",\n",
92 | " \"nodata\": False\n",
93 | "}"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "id": "7c7d367f-70bd-47b6-a5e9-dc8626212824",
100 | "metadata": {},
101 | "outputs": [],
102 | "source": [
103 | "ds = dc.load(**query)"
104 | ]
105 | },
106 | {
107 | "cell_type": "code",
108 | "execution_count": null,
109 | "id": "43b8c18c-1882-40f6-8fc5-cf96277d5eda",
110 | "metadata": {},
111 | "outputs": [],
112 | "source": [
113 | "bandas_reflectancia = [\"coastal\", \"blue\", \"green\", \"red\", \"nir08\", \"swir16\", \"swir22\"]\n",
114 | "quality_band = 'qa_pixel'\n",
115 | "cloud_free_mask1 = masking.make_mask(ds[quality_band], **good_pixel_flags)"
116 | ]
117 | },
118 | {
119 | "cell_type": "code",
120 | "execution_count": null,
121 | "id": "8642febf-3dec-4bd6-8175-d4589ec1d2dc",
122 | "metadata": {},
123 | "outputs": [],
124 | "source": [
125 | "valid_pixel_proportion = cloud_free_mask1.sum(dim=(\"x\", \"y\"))/(cloud_free_mask1.shape[1] * cloud_free_mask1.shape[2])\n",
126 | "valid_threshold = 0.8\n",
127 | "observations_to_keep = (valid_pixel_proportion >= valid_threshold)"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "id": "0af66516-34a4-4b3d-ab65-4ff191f16977",
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "dsf = ds[bandas_reflectancia].where(cloud_free_mask1)\n",
138 | "ds_keep = dsf.sel(time=observations_to_keep)\n",
139 | "ds_keep.update(ds_keep * 0.0000275 + -0.2)\n",
140 | "ds_keep.update(ds_keep.where(ds_keep >= 0).where(ds_keep <= 1))\n",
141 | "dsf = ds_keep"
142 | ]
143 | },
144 | {
145 | "cell_type": "markdown",
146 | "id": "7a30738b-84cc-453f-b671-229b5214ddf4",
147 | "metadata": {},
148 | "source": [
149 | "***\n",
150 | "\n",
151 | "# 1. Calcular índices espectrales\n",
152 | "\n",
153 | "Los índices espectrales (vegetación, agua, nieve, etc.) son ampliamente utilizados para identificar con mayor facilidad las estructuras que se desean estudiar. Estos índices se crean a partir de una combinación de bandas espectrales, aquí revisaremos algunos de los índices más populares.\n",
154 | "\n",
155 | "## 1.1 NDVI\n",
156 | "\n",
157 | "El NDVI ([Normalised Difference Vegetation Index](https://en.wikipedia.org/wiki/Normalized_difference_vegetation_index)), es uno de los índices para detectar vegetación más populares, y ha sido ampliamente utilizado durante décadas. Este índice obedece a la siguiente fórmula:\n",
158 | "\n",
159 | "$$\\text{NDVI} = \\frac{NIR-Red}{NIR + Red}$$\n",
160 | "\n",
161 | "Siendo $NIR$ la reflectancia en el infrarrojo cercano y $Red$ la reflectancia en la banda roja. Este índice, puede tomar valores que van desde -1 a 1, siendo valores altos indicadores de mayor vegetación/vigor, valores cercanos a 0 suelo desnudo y valores negativos obedecen normalmente a cuerpos de agua, entre otros."
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": null,
167 | "id": "5f0495d6-5d42-4866-a4db-d48f816f9eaa",
168 | "metadata": {
169 | "tags": []
170 | },
171 | "outputs": [],
172 | "source": [
173 | "ndvi = (dsf['nir08'] - dsf['red']) / (dsf['nir08'] + dsf['red'])\n",
174 | "ndvi.attrs = ds['red'].attrs # para mantener atributos geoespaciales\n",
175 | "ndvi = ndvi.where(ndvi >= -1).where(ndvi <= 1).compute()"
176 | ]
177 | },
178 | {
179 | "cell_type": "markdown",
180 | "id": "4f466b3f-0946-4de9-8a2c-81a1bfae43e6",
181 | "metadata": {},
182 | "source": [
183 | ">**Nota**: En algunas ocasiones es muy útil utilizar `compute()` para obligar al conjunto de datos a hacer los cálculos que estén pendientes. Esto es útil particularmente antes de realizar una tarea que requiera dos o más veces el arreglo calculado. Por ejemplo, cada vez que se realiza un plot de la imagen, se calculan y extraen los datos. La última línea de la celda anterior utiliza `compute()` debido a que queremos evitar re-calcular lo realizado en notebooks anteriores cada vez que realicemos un gráfico."
184 | ]
185 | },
186 | {
187 | "cell_type": "code",
188 | "execution_count": null,
189 | "id": "52c3019f-64c9-488c-9b3a-0ec0424dcac2",
190 | "metadata": {
191 | "tags": []
192 | },
193 | "outputs": [],
194 | "source": [
195 | "ndvi.isel(time = range(8, 12)).plot(col = \"time\", col_wrap = 4, figsize = (20, 5))"
196 | ]
197 | },
198 | {
199 | "cell_type": "markdown",
200 | "id": "6ffbdd71-ef8a-409a-bb36-c7087fa63ab4",
201 | "metadata": {
202 | "tags": []
203 | },
204 | "source": [
205 | "## 1.2 EVI\n",
206 | "\n",
207 | "El EVI ([Enhanced Vegetation Index](https://en.wikipedia.org/wiki/Enhanced_vegetation_index)), es otro de los índices para detectar vegetación más populares, diseñado para mejorar la sensibilidad de la vegetación en regiones con alta biomasa. Este índice obedece a la siguiente fórmula:\n",
208 | "\n",
209 | "$$ 2.5\\times \\left( \\frac{NIR - Red}{(NIR + 6\\times Red - 7.5\\times Blue + 1)} \\right)$$\n",
210 | "\n",
211 | "Siendo $NIR$ la reflectancia en el infrarrojo cercano, $Red$ la reflectancia en la banda roja, y $Blue$ la reflectancia en la banda azul. Este índice es simil al NDVI, puede tomar valores que van desde -1 a 1, siendo valores altos indicadores de mayor vegetación/vigor, valores cercanos a 0 suelo desnudo y valores negativos obedecen normalmente a cuerpos de agua, entre otros.\n"
212 | ]
213 | },
214 | {
215 | "cell_type": "code",
216 | "execution_count": null,
217 | "id": "8823fd0e-3723-4c27-ad7d-0639a18fe05e",
218 | "metadata": {
219 | "tags": []
220 | },
221 | "outputs": [],
222 | "source": [
223 | "evi = 2.5 * ((dsf['nir08'] - dsf['red']) / (dsf['nir08'] + 6 * dsf['red'] - 7.5 * dsf['blue'] + 1))\n",
224 | "evi.attrs = ds['red'].attrs # para mantener atributos geoespaciales\n",
225 | "evi = evi.where(evi >= -1).where(evi <= 1).compute()"
226 | ]
227 | },
228 | {
229 | "cell_type": "code",
230 | "execution_count": null,
231 | "id": "33c2cd7e-e7e5-4565-9cb6-dc7f164ada3c",
232 | "metadata": {
233 | "tags": []
234 | },
235 | "outputs": [],
236 | "source": [
237 | "evi.isel(time = range(8, 12)).plot(col = \"time\", col_wrap = 4, figsize = (20, 5))"
238 | ]
239 | },
240 | {
241 | "cell_type": "markdown",
242 | "id": "3b2468d2-ecf9-4780-bc84-ddb92ae3c259",
243 | "metadata": {},
244 | "source": [
245 | "## 1.3 NDWI\n",
246 | "\n",
247 | "El NDWI ([Normalized Difference Water Index](https://en.wikipedia.org/wiki/Normalized_difference_water_index)), es uno de los índices para detectar agua más populares, existen dos de estos índices propuestos en 1996, nosotros utilizaremos el de Gao. Este índice obedece a la siguiente fórmula:\n",
248 | "\n",
249 | "$$\\text{NDWI} = \\frac{NIR-SWIR}{NIR + SWIR}$$\n",
250 | "\n",
251 | "Siendo $NIR$ la reflectancia en el infrarrojo cercano y $SWIR$ la reflectancia en el infrarojo de onda corta, cualquiera de los 2 $SWIR$ entregados por Landsat 8 son factibles de utilizar, utilizaremos `\"swir16\"`. Este índice, puede tomar valores que van desde -1 a 1, siendo valores positivos aquellos que reflejan presencia de agua, y valores negativos ausencia de agua y vegetación."
252 | ]
253 | },
254 | {
255 | "cell_type": "code",
256 | "execution_count": null,
257 | "id": "5edfe9a8-eb79-4010-b985-1b62a3ed56d1",
258 | "metadata": {
259 | "tags": []
260 | },
261 | "outputs": [],
262 | "source": [
263 | "ndwi = (dsf['nir08'] - dsf['swir16']) / (dsf['nir08'] + dsf['swir16'])\n",
264 | "ndwi.attrs = ds['red'].attrs # para mantener atributos geoespaciales\n",
265 | "ndwi = ndwi.where(ndwi >= -1).where(ndwi <= 1).compute()"
266 | ]
267 | },
268 | {
269 | "cell_type": "code",
270 | "execution_count": null,
271 | "id": "45fb0bde-f070-4c84-87fb-a19056dbf4c4",
272 | "metadata": {
273 | "tags": []
274 | },
275 | "outputs": [],
276 | "source": [
277 | "ndwi.isel(time = range(8, 12)).plot(col = \"time\", col_wrap = 4, figsize = (20, 5))"
278 | ]
279 | },
280 | {
281 | "cell_type": "markdown",
282 | "id": "252906a6-84dc-4ef3-a8a8-2303f0af7270",
283 | "metadata": {
284 | "tags": []
285 | },
286 | "source": [
287 | "***\n",
288 | "\n",
289 | "# 2. Realizar compuestos\n",
290 | "\n",
291 | "Dentro de las funcionalidades disponibles, también es posible generar compuestos o productos agregados, como el promedio, mediana, varianza, máximo, etc, a través de alguna dimensión en particular del cubo, usualmente el tiempo.\n",
292 | "\n",
293 | "De esta forma, podemos calcular por ejemplo, una imagen anual promedio para cada banda, utilizando todas las escenas disponibles. Si por ejemplo se cuenta con 30 escenas en un año, al calcular el promedio anual, se termina generando una única escena donde en cada pixel está el promedio de las 30 escenas utilizadas.\n",
294 | "\n",
295 | "Usemos este mismo ejemplo como ejercicio, calculemos el `EVI` promedio y su varianza para el año 2020.\n",
296 | "\n",
297 | "Utilizaremos el parámetro `robust=True` dentro de `plot` para que los límites de color se ajusten a los percentiles 2 y 98 de los datos. Tener en consideración que al utilizar `robust` la interpretación de un mismo color en dos imágenes puede ser distinto."
298 | ]
299 | },
300 | {
301 | "cell_type": "code",
302 | "execution_count": null,
303 | "id": "c7e81479-4ba4-4d93-9407-4797daf28350",
304 | "metadata": {
305 | "tags": []
306 | },
307 | "outputs": [],
308 | "source": [
309 | "evi_mean = evi.mean(dim=\"time\", skipna = True)\n",
310 | "evi_mean.plot(robust = True)"
311 | ]
312 | },
313 | {
314 | "cell_type": "code",
315 | "execution_count": null,
316 | "id": "51ad4f05-c449-4f5b-bd37-a83cfab4b116",
317 | "metadata": {
318 | "tags": []
319 | },
320 | "outputs": [],
321 | "source": [
322 | "evi_var = evi.var(dim=\"time\", skipna = True)\n",
323 | "evi_var.plot(robust = True)"
324 | ]
325 | },
326 | {
327 | "cell_type": "markdown",
328 | "id": "d41ba90c-8e02-481f-a40c-f2172aee3fab",
329 | "metadata": {},
330 | "source": [
331 | "Ahora calcularemos el `EVI` máximo solo para febrero del 2020."
332 | ]
333 | },
334 | {
335 | "cell_type": "code",
336 | "execution_count": null,
337 | "id": "9a3dc4f2-7944-4ec9-9d08-84bf04159752",
338 | "metadata": {
339 | "tags": []
340 | },
341 | "outputs": [],
342 | "source": [
343 | "evi_feb = evi.sel(time=slice('2020-02-01', '2020-02-28')).max(dim='time', skipna=True)\n",
344 | "evi_feb.plot(robust = True)"
345 | ]
346 | },
347 | {
348 | "cell_type": "markdown",
349 | "id": "ec01e5c8-bd99-4845-aa49-e0fade8d1cbb",
350 | "metadata": {},
351 | "source": [
352 | "Desglosando el paso anterior:\n",
353 | "\n",
354 | "1. Se utiliza `sel` y no `isel` para realizar la selección, ya que se filtra la fecha por su valor y no por su posición.\n",
355 | "2. Se utiliza `slice` dentro de `sel` para definir una ventana dentro de la cual se quiere extraer un fragmento de información, en este caso todo febrero.\n",
356 | "3. Se calcula el máximo por medio del método `max`. Dentro del método, se especifica la dimensión de tiempo como `dim=time` y se pide ignorar los valores NA.\n",
357 | "4. El objeto generado `evi_feb`, ya no posee la dimensión `time`.\n",
358 | "5. Notar que la tolanidad del 0.35 de NDVI es distinta con respecto al gráfico de `evi_mean`.\n",
359 | "\n",
360 | "Ahora calcularemos el `EVI` promedio mensual:"
361 | ]
362 | },
363 | {
364 | "cell_type": "code",
365 | "execution_count": null,
366 | "id": "fdfd4418-a78c-4fe0-9273-8dab31540172",
367 | "metadata": {
368 | "tags": []
369 | },
370 | "outputs": [],
371 | "source": [
372 | "evi_monthly = evi.groupby('time.month').mean(dim = \"time\", skipna = True)\n",
373 | "evi_monthly.plot(col = \"month\", col_wrap = 4, figsize = (20, 15), robust = True)"
374 | ]
375 | },
376 | {
377 | "cell_type": "markdown",
378 | "id": "a68ce5a4-cd6c-44ef-ad56-9e48254d8099",
379 | "metadata": {
380 | "tags": []
381 | },
382 | "source": [
383 | "Aquí se comparte la escala de color, por lo que no hay problemas en la interpretación bajo el parámetro `robust=True`.\n",
384 | "\n",
385 | "***\n",
386 | "\n",
387 | "# 3. Exportar información\n",
388 | "\n",
389 | "En algunos casos se desea exportar la información generada. Esta información puede ser descargada como imagen, o como dataframe.\n",
390 | "\n",
391 | "## 3.1 Exportar como imagen\n",
392 | "\n",
393 | "Se puede exportar una sola escena, por ejemplo, el `evi_mean`:"
394 | ]
395 | },
396 | {
397 | "cell_type": "code",
398 | "execution_count": null,
399 | "id": "4ef6c0b0-9b75-498a-bbed-b2f7fcfac8c9",
400 | "metadata": {},
401 | "outputs": [],
402 | "source": [
403 | "from datacube.utils.cog import write_cog\n",
404 | "run = False"
405 | ]
406 | },
407 | {
408 | "cell_type": "code",
409 | "execution_count": null,
410 | "id": "ff5f2a27-8edd-48f3-a2b6-6c4829c3494d",
411 | "metadata": {},
412 | "outputs": [],
413 | "source": [
414 | "if run:\n",
415 | " write_cog(geo_im = evi_mean, fname = 'evi_mean.tif', overwrite=True).compute()"
416 | ]
417 | },
418 | {
419 | "cell_type": "markdown",
420 | "id": "b6883d81-c4b1-4895-81bf-9d59703b0e97",
421 | "metadata": {
422 | "tags": []
423 | },
424 | "source": [
425 | "Se puede exportar cada tiempo de un índice de manera independiente, por ejemplo, cada mes de `evi_monthly`:"
426 | ]
427 | },
428 | {
429 | "cell_type": "code",
430 | "execution_count": null,
431 | "id": "f14170fa-fa88-413e-b523-9db34d0cbbd1",
432 | "metadata": {},
433 | "outputs": [],
434 | "source": [
435 | "if run:\n",
436 | " for i in range(len(evi_monthly.month)):\n",
437 | " month = evi_monthly.month[i].to_numpy().item()\n",
438 | " out = evi_monthly.isel(month=i)\n",
439 | " write_cog(geo_im = out, fname = f'{month}_evi.tif', overwrite=True).compute()"
440 | ]
441 | },
442 | {
443 | "cell_type": "markdown",
444 | "id": "94f18a09-0218-4654-90f5-d6b2f9fbb7a0",
445 | "metadata": {},
446 | "source": [
447 | "de manera similar, cada instante de una banda/índice:"
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "execution_count": null,
453 | "id": "a65a5f09-5110-48a5-8ddf-22054cd507da",
454 | "metadata": {},
455 | "outputs": [],
456 | "source": [
457 | "if run:\n",
458 | " for i in range(len(evi.time)):\n",
459 | " date = evi.time[i].dt.strftime(\"%Y%m%d\").values\n",
460 | " out = evi.isel(time=i)\n",
461 | " write_cog(geo_im=out, fname=f'{date}_evi.tif', overwrite=True).compute()"
462 | ]
463 | },
464 | {
465 | "cell_type": "markdown",
466 | "id": "0e9fcf92-d986-49a2-8592-38b01613b19d",
467 | "metadata": {},
468 | "source": [
469 | "O una imagen multibanda:"
470 | ]
471 | },
472 | {
473 | "cell_type": "code",
474 | "execution_count": null,
475 | "id": "e1413075-9032-4d77-a266-c980a64fd1a5",
476 | "metadata": {},
477 | "outputs": [],
478 | "source": [
479 | "if run:\n",
480 | " write_cog(evi, 'evi.tif', overwrite=True).compute()"
481 | ]
482 | },
483 | {
484 | "cell_type": "markdown",
485 | "id": "0054fdd2-5e96-4076-ad37-614df1497479",
486 | "metadata": {},
487 | "source": [
488 | "## 4.2 Exportar como dataframe\n",
489 | "\n",
490 | "Si bien es posible exportar el arreglo completo, es mejor utilizar este método cuando son pocos datos. Por ejemplo, seleccionaremos algunos puntos al azar para extraer la serie temporal de `NDVI`, para un vector o lista de coordenadas X, Y."
491 | ]
492 | },
493 | {
494 | "cell_type": "code",
495 | "execution_count": null,
496 | "id": "bf0b8454-5192-4c8f-81d0-f3bfc79dee27",
497 | "metadata": {
498 | "tags": []
499 | },
500 | "outputs": [],
501 | "source": [
502 | "my_x = np.random.choice(ndvi.x, 20)\n",
503 | "my_y = np.random.choice(ndvi.y, 20)\n",
504 | "x_ = xr.DataArray(my_x)\n",
505 | "y_ = xr.DataArray(my_y)"
506 | ]
507 | },
508 | {
509 | "cell_type": "markdown",
510 | "id": "c037d6b2-3adf-4493-a488-5096f3bd6992",
511 | "metadata": {},
512 | "source": [
513 | "Para poder extraer correctamente las coordenadas como un par X,Y en la imagen, es necesario transformar estos arreglos, en arreglos de `xarray` o de lo contrario podrían surgir resultados extraños. Eso es lo que hace las últimas dos líneas de la celda anterior.\n",
514 | "\n",
515 | "La ubicación de los puntos desde donde extraeremos la información se muestra en la siguiente figura:"
516 | ]
517 | },
518 | {
519 | "cell_type": "code",
520 | "execution_count": null,
521 | "id": "6ba7fd1f-ccdd-4da1-9b9b-65ebaaaaf1c7",
522 | "metadata": {
523 | "tags": []
524 | },
525 | "outputs": [],
526 | "source": [
527 | "ndvi.isel(time=1).plot(figsize=(12, 10), robust=True)\n",
528 | "points = [(x, y) for x, y in zip(my_x, my_y)]\n",
529 | "plt.scatter([p[0] for p in points], [p[1] for p in points], c='g', s=40)\n",
530 | "for i, point in enumerate(points):\n",
531 | " plt.text(point[0], point[1], f'{i}', fontsize=8, bbox=dict(facecolor='white', edgecolor='black', pad=2))\n",
532 | "plt.show()"
533 | ]
534 | },
535 | {
536 | "cell_type": "markdown",
537 | "id": "00f374c0-5b3d-4491-8757-9545120bd20d",
538 | "metadata": {
539 | "tags": []
540 | },
541 | "source": [
542 | "Luego, para exportar los datos de esta ubicación utilizaremos el siguiente código:"
543 | ]
544 | },
545 | {
546 | "cell_type": "code",
547 | "execution_count": null,
548 | "id": "b9d2b53c-3f6d-4527-9941-560557b648db",
549 | "metadata": {
550 | "tags": []
551 | },
552 | "outputs": [],
553 | "source": [
554 | "df = ndvi.sel(x=x_, y=y_).to_pandas()\n",
555 | "df.to_csv('ndvi.csv')"
556 | ]
557 | },
558 | {
559 | "cell_type": "code",
560 | "execution_count": null,
561 | "id": "b7a31f19-5ab8-4f23-ba82-4f27fcc23e8c",
562 | "metadata": {},
563 | "outputs": [],
564 | "source": [
565 | "client.close()\n",
566 | "\n",
567 | "cluster.close()"
568 | ]
569 | },
570 | {
571 | "cell_type": "markdown",
572 | "id": "535d03e6-6a78-4485-82fc-07ba97fdc370",
573 | "metadata": {
574 | "tags": []
575 | },
576 | "source": [
577 | "***\n",
578 | "\n",
579 | "# *Siguientes pasos* 🐾\n",
580 | "\n",
581 | "Para continuar con el tutorial pueden acceder a los notebooks del siguiente listado.\n",
582 | "\n",
583 | "1. [Acceso](00_Acceso.ipynb)\n",
584 | "2. [Cargar datos](01_Cargar_datos.ipynb)\n",
585 | "3. [Limpieza](02_Limpieza.ipynb)\n",
586 | "4. **Análisis básico**\n",
587 | "5. [Caso de estudio 1](04_Caso_de_estudio_1.ipynb)\n",
588 | "6. [Caso de estudio 2](05_Caso_de_estudio_2.ipynb)\n",
589 | "\n",
590 | "***"
591 | ]
592 | },
593 | {
594 | "cell_type": "code",
595 | "execution_count": null,
596 | "id": "49e1286f-85c4-4f50-af81-e138f4a0983c",
597 | "metadata": {},
598 | "outputs": [],
599 | "source": []
600 | }
601 | ],
602 | "metadata": {
603 | "kernelspec": {
604 | "display_name": "Python 3 (ipykernel)",
605 | "language": "python",
606 | "name": "python3"
607 | },
608 | "language_info": {
609 | "codemirror_mode": {
610 | "name": "ipython",
611 | "version": 3
612 | },
613 | "file_extension": ".py",
614 | "mimetype": "text/x-python",
615 | "name": "python",
616 | "nbconvert_exporter": "python",
617 | "pygments_lexer": "ipython3",
618 | "version": "3.10.12"
619 | }
620 | },
621 | "nbformat": 4,
622 | "nbformat_minor": 5
623 | }
624 |
--------------------------------------------------------------------------------
/básico/04_Caso_de_estudio_1.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "16b06301-ad66-4112-9890-6f9ee4b20c98",
6 | "metadata": {},
7 | "source": [
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "75fd7690-8e30-4ac7-a866-3336508c48d1",
14 | "metadata": {},
15 | "source": [
16 | "# Caso de estudio 1: Cambio de uso\n",
17 | "\n",
18 | "Este caso de estudio es una adaptación del caso original desarrollado por Digital Earth Australia, que puede ser encontrado [aquí](https://github.com/csiro-easi/eocsi-hackathon-2022/blob/main/case-studies/Change_detection.ipynb).\n",
19 | "\n",
20 | "El objetivo es detectar cambio de uso de suelo vinculado a la vegetación, ya sea un aumento o una disminución de la vegetación en el espacio, entre los años 2014 y 2022.\n",
21 | "\n",
22 | "Las actividades claves son:\n",
23 | "* Definir área de estudio, temporalidad, sensores y bandas a utilizar\n",
24 | "* Limpiar los datos\n",
25 | "* Calcular índices\n",
26 | "* Realizar prueba de hipótesis\n",
27 | "* Visualizar resultados\n",
28 | "\n",
29 | "***"
30 | ]
31 | },
32 | {
33 | "cell_type": "code",
34 | "execution_count": null,
35 | "id": "3a913d06-b84a-499b-8614-f87833fd402f",
36 | "metadata": {
37 | "tags": []
38 | },
39 | "outputs": [],
40 | "source": [
41 | "import os\n",
42 | "\n",
43 | "os.environ[\"USE_PYGEOS\"] = \"0\"\n",
44 | "\n",
45 | "import datacube\n",
46 | "import xarray as xr\n",
47 | "import numpy as np\n",
48 | "import pandas as pd\n",
49 | "import matplotlib.pyplot as plt\n",
50 | "\n",
51 | "from odc.ui import DcViewer\n",
52 | "from datacube.utils import masking\n",
53 | "from datacube.utils.rio import configure_s3_access\n",
54 | "\n",
55 | "from dask.distributed import Client, LocalCluster\n",
56 | "cluster = LocalCluster()\n",
57 | "client = Client(cluster)\n",
58 | "\n",
59 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)\n",
60 | "\n",
61 | "from dea_tools.plotting import display_map, rgb"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": null,
67 | "id": "48678efe-863b-44d0-b86c-80fdeedb4c23",
68 | "metadata": {
69 | "tags": []
70 | },
71 | "outputs": [],
72 | "source": [
73 | "dc = datacube.Datacube(app='caso_1') # https://opendatacube.readthedocs.io/en/latest/api/core-classes/datacube.html"
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "id": "7b35093a-6e4c-4d8a-a75d-53101324bd40",
79 | "metadata": {},
80 | "source": [
81 | "# Definir área de estudio, temporalidad, sensores y bandas a utilizar\n",
82 | "\n",
83 | "Nos centraremos en un sector de la cordillera de la costa de la región de los Ríos, colindando con la región de la Araucanía (-39.41, -72.96).\n",
84 | "Debido a que nos enfocaremos en la vegetación, las bandas de interes serán roja e infrarroja para el cálculo de NDVI. También requerimos las bandas de calidad, para filtrar las nubes y otros artefactos.\n",
85 | "\n",
86 | "Aprovecharemos la mejor resolución espacial que nos entrega Landsat, que es de 30x30 metros, y utilizaremos el siguiente sistema de referencia de coordenadas: \"WGS84 UTM 19 Sur (EPSG: 32719)\""
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": null,
92 | "id": "f23d633c-96c4-45ca-ab34-4fa79608a313",
93 | "metadata": {
94 | "tags": []
95 | },
96 | "outputs": [],
97 | "source": [
98 | "buffer = .1\n",
99 | "\n",
100 | "query = {\n",
101 | " \"product\": \"landsat8_c2l2_sr\",\n",
102 | " \"measurements\" : [\"red\", \"nir08\", \"qa_pixel\"],\n",
103 | " \"x\": -72.9575 + np.array([buffer, -buffer]),\n",
104 | " \"y\": -39.4054 + np.array([buffer, -buffer]),\n",
105 | " \"time\": (\"2014-01-01\", \"2022-12-31\"),\n",
106 | " \"output_crs\": \"EPSG:32719\",\n",
107 | " \"resolution\": (-30, 30),\n",
108 | " \"dask_chunks\": {\"time\": 1, 'x':2048, 'y':2048},\n",
109 | " \"group_by\": \"solar_day\"\n",
110 | "}"
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "id": "a06bb9f7-5931-4473-ad70-77ed24fdb08f",
117 | "metadata": {
118 | "tags": []
119 | },
120 | "outputs": [],
121 | "source": [
122 | "display_map(x=query['x'], y=query['y'])"
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": null,
128 | "id": "d733d478-6e53-41bf-8997-6658b460c277",
129 | "metadata": {
130 | "tags": []
131 | },
132 | "outputs": [],
133 | "source": [
134 | "ds = dc.load(**query)"
135 | ]
136 | },
137 | {
138 | "cell_type": "markdown",
139 | "id": "49882cbc-f765-4f52-a4c1-3ef6efaec599",
140 | "metadata": {},
141 | "source": [
142 | "***\n",
143 | "\n",
144 | "# Limpiar los datos\n",
145 | "\n",
146 | "Eliminaremos pixeles con nodata, nieve, nubes, cirrus, y sombras producidas por nubes desde la banda de calidad."
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": null,
152 | "id": "f1a6ad8f-34b2-4bac-b5a8-3d6ecaa664cf",
153 | "metadata": {
154 | "tags": []
155 | },
156 | "outputs": [],
157 | "source": [
158 | "masking.describe_variable_flags(ds.qa_pixel)"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "id": "326712d5-bdd8-480a-aea0-676abc550bff",
165 | "metadata": {
166 | "tags": []
167 | },
168 | "outputs": [],
169 | "source": [
170 | "quality_band = 'qa_pixel'\n",
171 | "cloud_free_mask = (\n",
172 | " masking.make_mask(ds[quality_band], snow='high_confidence') + \n",
173 | " masking.make_mask(ds[quality_band], cloud=\"high_confidence\") +\n",
174 | " masking.make_mask(ds[quality_band], cirrus=\"high_confidence\") +\n",
175 | " masking.make_mask(ds[quality_band], cloud_shadow=\"high_confidence\") +\n",
176 | " masking.make_mask(ds[quality_band], nodata=True)\n",
177 | ")"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": null,
183 | "id": "16314f16-2fbf-4354-a8cb-194fd2f4a958",
184 | "metadata": {
185 | "tags": []
186 | },
187 | "outputs": [],
188 | "source": [
189 | "dsf = xr.where(cloud_free_mask, np.nan, ds)"
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "execution_count": null,
195 | "id": "e726c550-717d-48d2-8631-475dec552132",
196 | "metadata": {
197 | "tags": []
198 | },
199 | "outputs": [],
200 | "source": [
201 | "fig, ax = plt.subplots(1, 1)\n",
202 | "dsf.red.isel(time=1).plot()\n",
203 | "ax.set_facecolor(\"black\")"
204 | ]
205 | },
206 | {
207 | "cell_type": "code",
208 | "execution_count": null,
209 | "id": "8ae3eef5-3f0f-4383-b270-f1a2b3b8963e",
210 | "metadata": {
211 | "tags": []
212 | },
213 | "outputs": [],
214 | "source": [
215 | "dsf.update(dsf * 0.0000275 + -0.2)"
216 | ]
217 | },
218 | {
219 | "cell_type": "code",
220 | "execution_count": null,
221 | "id": "1dee2b20-4519-466b-aa53-921441def09c",
222 | "metadata": {
223 | "tags": []
224 | },
225 | "outputs": [],
226 | "source": [
227 | "fig, ax = plt.subplots(1, 1)\n",
228 | "dsf.red.isel(time=1).plot(robust=True)\n",
229 | "ax.set_facecolor(\"black\")"
230 | ]
231 | },
232 | {
233 | "cell_type": "markdown",
234 | "id": "ebf65666-1ee4-43eb-9525-884c5f84f6aa",
235 | "metadata": {},
236 | "source": [
237 | "***\n",
238 | "# Calcular índices\n",
239 | "\n",
240 | "Calculamos el NDVI siguiendo la siguiente celda:"
241 | ]
242 | },
243 | {
244 | "cell_type": "code",
245 | "execution_count": null,
246 | "id": "aacd1dfd-e5ec-497e-ba51-f1f8963fd9a6",
247 | "metadata": {
248 | "tags": []
249 | },
250 | "outputs": [],
251 | "source": [
252 | "ndvi = (dsf['nir08'] - dsf['red']) / (dsf['nir08'] + dsf['red'])\n",
253 | "ndvi"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": null,
259 | "id": "f7d520bd-60a5-48f0-a47a-9004a81e3ded",
260 | "metadata": {
261 | "tags": []
262 | },
263 | "outputs": [],
264 | "source": [
265 | "fig, ax = plt.subplots(1, 1)\n",
266 | "ndvi.isel(time=1).plot(robust=True)\n",
267 | "ax.set_facecolor(\"black\")"
268 | ]
269 | },
270 | {
271 | "cell_type": "markdown",
272 | "id": "de216eb7-fb7e-48ac-80a3-3bbe4e87a042",
273 | "metadata": {},
274 | "source": [
275 | "# Realizar prueba de hipótesis\n",
276 | "\n",
277 | "La idea es revisar si es que hay cambios significativos en el NDVI de la zona de estudio. Es un ejercicio simplificado, pero perfectamente aplicable. Podríamos contruir una hipótesis de la forma:\n",
278 | "\n",
279 | "$$\n",
280 | "\\begin{aligned}\n",
281 | "\\text{hipótesis nula } (H_0) &: \\text{no hubieron cambios,} \\\\\n",
282 | "\\text{hipótesis alternativa } (H_1) &: \\text{hubieron algunos cambios.}\n",
283 | "\\end{aligned}\n",
284 | "$$\n",
285 | "\n",
286 | "## Selección de muestras\n",
287 | "Solo requerimos datos para el año 2014 y 2022, por lo los filtraremos para realizar la prueba de hipótesis:"
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": null,
293 | "id": "c9b93ed6-54d2-41be-a9e5-40394a5954d1",
294 | "metadata": {
295 | "tags": []
296 | },
297 | "outputs": [],
298 | "source": [
299 | "t0_date = np.datetime64('2014-12-31')\n",
300 | "t1_date = np.datetime64('2022-12-31')\n",
301 | "t0 = ndvi.time.dt.year == t0_date.astype(object).year\n",
302 | "t1 = ndvi.time.dt.year == t1_date.astype(object).year\n",
303 | "\n",
304 | "t0_sample = ndvi.sel(time = t0)\n",
305 | "t1_sample = ndvi.sel(time = t1)"
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": null,
311 | "id": "23b6b762-64b7-41c7-a5e4-325628924f45",
312 | "metadata": {
313 | "tags": []
314 | },
315 | "outputs": [],
316 | "source": [
317 | "print(f\"Observaciones de base: {len(t0_sample.time)}\")\n",
318 | "print(f\"Observaciones posteriores: {len(t1_sample.time)}\")"
319 | ]
320 | },
321 | {
322 | "cell_type": "code",
323 | "execution_count": null,
324 | "id": "cbb07e4e-a141-4272-b0fc-5d86c1b3a2fa",
325 | "metadata": {
326 | "tags": []
327 | },
328 | "outputs": [],
329 | "source": [
330 | "sample_lat_coords = ndvi.coords['y']\n",
331 | "sample_lon_coords = ndvi.coords['x']"
332 | ]
333 | },
334 | {
335 | "cell_type": "markdown",
336 | "id": "b55a9cdf-8162-4793-9b15-8878b82253a3",
337 | "metadata": {},
338 | "source": [
339 | "## Prueba estadística para verificar el cambio\n",
340 | "\n",
341 | "Para buscar evidencia de que el NDVI promedio ha cambiado entre las dos muestras (ya sea positiva o negativamente), usamos la *prueba t* de Welch. Esto se usa para probar la hipótesis de que dos poblaciones tienen promedios iguales. En este caso, las poblaciones son el área de interés antes y después de las fechas de referencia, y el promedio que se prueba es el NDVI en ambos rangos. Se usa la *prueba t* de Welch (a diferencia de la prueba t de Student) porque las dos muestras en el estudio pueden no tener necesariamente varianzas iguales.\n",
342 | "\n",
343 | "La prueba se ejecuta utilizando la biblioteca de estadísticas del paquete Scipy, que proporciona la función `ttest_ind` para ejecutar pruebas t. Establecer `equal_var=False` significa que la función ejecutará la *prueba t* de Welch. La función devuelve el *estadístico t* y el valor p para cada píxel después de probar la diferencia en el NDVI promedio. Estos se almacenan como `t_stat` y `p_val` dentro del conjunto de datos `t_test` para su uso en la siguiente sección"
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": null,
349 | "id": "6f2eb254-dbe0-42bf-8a37-a16300d286ec",
350 | "metadata": {
351 | "tags": []
352 | },
353 | "outputs": [],
354 | "source": [
355 | "from scipy import stats\n",
356 | "\n",
357 | "tstat, p_tstat = stats.ttest_ind(\n",
358 | " t1_sample.values,\n",
359 | " t0_sample.values,\n",
360 | " equal_var=False,\n",
361 | " nan_policy='omit',\n",
362 | ")"
363 | ]
364 | },
365 | {
366 | "cell_type": "code",
367 | "execution_count": null,
368 | "id": "881bf9bc-b227-40b1-b0a7-93dbb897ab05",
369 | "metadata": {
370 | "tags": []
371 | },
372 | "outputs": [],
373 | "source": [
374 | "# Convert results to an xarray for further analysis\n",
375 | "t_test = xr.Dataset(\n",
376 | " {\n",
377 | " 't_stat': (['y', 'x'], tstat),\n",
378 | " 'p_val': (['y', 'x'], p_tstat)\n",
379 | " },\n",
380 | " coords={\n",
381 | " 'x': (['x'], sample_lon_coords.values),\n",
382 | " 'y': (['y'], sample_lat_coords.values)\n",
383 | " }, \n",
384 | " attrs={\n",
385 | " 'crs': 'EPSG:32719',\n",
386 | " })\n",
387 | "\n",
388 | "t_test"
389 | ]
390 | },
391 | {
392 | "cell_type": "markdown",
393 | "id": "164ca6cb-940f-458f-95a5-a71e952a97f9",
394 | "metadata": {},
395 | "source": [
396 | "# Visualizar resultados\n",
397 | "\n",
398 | "De la prueba, nos interesan dos condiciones: si el cambio es significativo (rechazo de la hipótesis nula) y si el cambio fue positivo (forestación) o negativo (deforestación).\n",
399 | "\n",
400 | "La hipótesis nula se puede rechazar si el *valor-p* (`p_val`) es menor que el nivel de significación elegido, que se establece como `sig_level = 0.05` (que es lo habitual) para este análisis. Si se rechaza la hipótesis nula, el píxel se clasificará como que ha sufrido un cambio significativo (si `p_val < 0.05`).\n",
401 | "\n",
402 | "La dirección del cambio se puede deducir de la diferencia en el NDVI promedio de cada muestra, que se calcula como:\n",
403 | "\n",
404 | "$$\n",
405 | "\\text{diferencia en la media} = \\text{promedio(t0)} - \\text{promedio(t1)}\n",
406 | "$$\n",
407 | "\n",
408 | "Esto significa que:\n",
409 | "\n",
410 | "* si el NDVI promedio para un píxel determinado es más **alto** en `t1` en comparación con `t0`, entonces `diff_mean` para ese píxel será **positivo**.\n",
411 | "* si el NDVI promedio para un píxel dado es más **bajo** en `t1` en comparación con `t0`, entonces `diff_mean` para ese píxel será **negativo**.\n",
412 | "\n",
413 | "Al ejecutar las siguientes celdas, **el cambio positivo se muestra en verde (forestación) y el cambio negativo en rojo (deforestación).**"
414 | ]
415 | },
416 | {
417 | "cell_type": "code",
418 | "execution_count": null,
419 | "id": "ce08da75-5321-4267-b1eb-351086f74d84",
420 | "metadata": {
421 | "tags": []
422 | },
423 | "outputs": [],
424 | "source": [
425 | "# Set the significance level\n",
426 | "sig_level = 0.05\n",
427 | "\n",
428 | "# Plot any difference in the mean\n",
429 | "diff_mean = t1_sample.mean(dim=['time']) - t0_sample.mean(dim=['time'])\n",
430 | "\n",
431 | "fig, ax = plt.subplots(1, 1, figsize=(7, 5))\n",
432 | "diff_mean.plot(cmap='RdYlGn', robust=True)\n",
433 | "ax.set_title('Any difference in the mean')\n",
434 | "plt.show()\n",
435 | "\n",
436 | "# Plot any difference in the mean classified as significant\n",
437 | "sig_diff_mean = t1_sample.mean(dim=['time']).where(t_test.p_val < sig_level) - t0_sample.mean(dim=['time']).where(t_test.p_val < sig_level)\n",
438 | "\n",
439 | "fig, ax = plt.subplots(1, 1, figsize=(7, 5))\n",
440 | "sig_diff_mean.plot(cmap='RdYlGn', robust=True)\n",
441 | "ax.set_title('Statistically significant difference in the mean')\n",
442 | "ax.set_facecolor(\"white\")\n",
443 | "plt.show()"
444 | ]
445 | },
446 | {
447 | "cell_type": "code",
448 | "execution_count": null,
449 | "id": "d4adfe88-4f8f-4b02-b6ab-92ca72154b35",
450 | "metadata": {},
451 | "outputs": [],
452 | "source": [
453 | "client.close()\n",
454 | "\n",
455 | "cluster.close()"
456 | ]
457 | },
458 | {
459 | "cell_type": "markdown",
460 | "id": "69679421-f56e-4dfe-b0c2-cfc69f7aea1c",
461 | "metadata": {},
462 | "source": [
463 | "***\n",
464 | "\n",
465 | "# *Siguientes pasos* 🐾\n",
466 | "\n",
467 | "Para continuar con el tutorial pueden acceder a los notebooks del siguiente listado.\n",
468 | "\n",
469 | "1. [Acceso](00_Acceso.ipynb)\n",
470 | "2. [Cargar datos](01_Cargar_datos.ipynb)\n",
471 | "3. [Limpieza](02_Limpieza.ipynb)\n",
472 | "4. [Análisis básico](03_Análisis_básico.ipynb)\n",
473 | "5. **Caso de estudio 1**\n",
474 | "6. [Caso de estudio 2](05_Caso_de_estudio_2.ipynb)\n",
475 | "\n",
476 | "***"
477 | ]
478 | }
479 | ],
480 | "metadata": {
481 | "kernelspec": {
482 | "display_name": "Python 3 (ipykernel)",
483 | "language": "python",
484 | "name": "python3"
485 | },
486 | "language_info": {
487 | "codemirror_mode": {
488 | "name": "ipython",
489 | "version": 3
490 | },
491 | "file_extension": ".py",
492 | "mimetype": "text/x-python",
493 | "name": "python",
494 | "nbconvert_exporter": "python",
495 | "pygments_lexer": "ipython3",
496 | "version": "3.10.12"
497 | }
498 | },
499 | "nbformat": 4,
500 | "nbformat_minor": 5
501 | }
502 |
--------------------------------------------------------------------------------
/básico/05_Caso_de_estudio_2.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "e415306c-fd53-4fc5-b233-08e9fb1b7047",
6 | "metadata": {},
7 | "source": [
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "d036140c-1fa1-45a7-bd67-aa57bb23ec48",
14 | "metadata": {},
15 | "source": [
16 | "# Caso de estudio 2\n",
17 | "\n",
18 | "Este caso de estudio es parte de un ensayo para el monitoreo de humedales por medio de imágenes satelitales Landsat en la región de Atacama, Chile.\n",
19 | "\n",
20 | "Nuestro objetivo es replicar este estudio realizando algunas ligeras modificaciones. [Aquí](https://cases.dataobservatory.net/datacube/monitoreo-humedales.html) se puede encontrar mayor información sobre el estudio.\n",
21 | "\n",
22 | "Las actividades claves son:\n",
23 | "* Definir área de estudio, temporalidad, sensores y bandas a utilizar\n",
24 | "* Limpiar los datos\n",
25 | "* Definir índices a utilizar\n",
26 | "* Realizar compuestos\n",
27 | "* Obtener estadísticas zonales\n",
28 | "* Visualizar resultados\n",
29 | "\n",
30 | "***"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": null,
36 | "id": "7122027c-2a5f-4684-824b-2a8a8f635cf1",
37 | "metadata": {},
38 | "outputs": [],
39 | "source": [
40 | "import os\n",
41 | "\n",
42 | "os.environ[\"USE_PYGEOS\"] = \"0\"\n",
43 | "\n",
44 | "import datacube\n",
45 | "import xarray as xr\n",
46 | "import numpy as np\n",
47 | "import pandas as pd\n",
48 | "import matplotlib.pyplot as plt\n",
49 | "\n",
50 | "from odc.ui import DcViewer\n",
51 | "from datacube.utils import masking\n",
52 | "from datacube.utils.rio import configure_s3_access\n",
53 | "\n",
54 | "from dask.distributed import Client, LocalCluster\n",
55 | "cluster = LocalCluster()\n",
56 | "client = Client(cluster)\n",
57 | "\n",
58 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)\n",
59 | "\n",
60 | "from dea_tools.plotting import display_map, rgb"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "id": "072639aa-9334-47ba-9ab6-c420bf98358e",
67 | "metadata": {
68 | "tags": []
69 | },
70 | "outputs": [],
71 | "source": [
72 | "dc = datacube.Datacube(app='caso_humedal') "
73 | ]
74 | },
75 | {
76 | "cell_type": "markdown",
77 | "id": "4788774f-58fa-4560-9a6a-48ef6ab471d6",
78 | "metadata": {},
79 | "source": [
80 | "# Definir área de estudio, temporalidad, sensores y bandas a utilizar\n",
81 | "\n",
82 | "El área de estudio será definida momentaneamente como un rectángulo, luego lo acotaremos."
83 | ]
84 | },
85 | {
86 | "cell_type": "code",
87 | "execution_count": null,
88 | "id": "367c1f1c-680c-47bd-b460-e2f5522063f4",
89 | "metadata": {
90 | "tags": []
91 | },
92 | "outputs": [],
93 | "source": [
94 | "study_area_lat = (-27.446, -27.327) # (ymin, ymax)\n",
95 | "study_area_lon = (-69.067, -68.966) # (xmin, xmax)"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": null,
101 | "id": "0d689b3d-ee07-49a4-86a9-83650aae0149",
102 | "metadata": {
103 | "tags": []
104 | },
105 | "outputs": [],
106 | "source": [
107 | "display_map(x=study_area_lon, y=study_area_lat)"
108 | ]
109 | },
110 | {
111 | "cell_type": "markdown",
112 | "id": "e56c1805-b4b4-4c39-a89b-a042646eda43",
113 | "metadata": {
114 | "tags": []
115 | },
116 | "source": [
117 | "Listando los productos terminados con 'c2l2_sr' podemos ver los productos de Landsat disponibles para reflectancia de la superficie."
118 | ]
119 | },
120 | {
121 | "cell_type": "code",
122 | "execution_count": null,
123 | "id": "971ffe23-9b85-4c27-9a5f-e03e8e512f02",
124 | "metadata": {
125 | "tags": []
126 | },
127 | "outputs": [],
128 | "source": [
129 | "products = [f['name'] for i, f in dc.list_products().iterrows() if f['name'].endswith('c2l2_sr')]\n",
130 | "products"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "id": "e2b51c95-006d-46c7-a996-09c7843ec5bc",
137 | "metadata": {
138 | "tags": []
139 | },
140 | "outputs": [],
141 | "source": [
142 | "dc.list_measurements().loc[products]"
143 | ]
144 | },
145 | {
146 | "cell_type": "markdown",
147 | "id": "0b04039f-48f6-4050-90a0-d8e872b72a94",
148 | "metadata": {
149 | "tags": []
150 | },
151 | "source": [
152 | "Ahora que se tiene mayor claridad, sobre los productos, podemos especificar las bandas de interes, y revisar sus fechas. Utilizando las bandas azul, verde, roja e infrarroja podemos armar los índices de vegetación más conocidos para explorar los humedales. También requerimos las bandas de calidad, para filtrar las nubes y otros artefactos.\n",
153 | "\n",
154 | "Aprovecharemos la mejor resolución espacial que nos entrega Landsat, que es de 30x30 metros, y utilizaremos el siguiente sistema de referencia de coordenadas: \"WGS84 UTM 19 Sur (EPSG: 32719)\"\n",
155 | "\n",
156 | "Primero, configuramos la siguiente consulta:"
157 | ]
158 | },
159 | {
160 | "cell_type": "code",
161 | "execution_count": null,
162 | "id": "3733b118-066a-4811-9b9e-fd58732203a5",
163 | "metadata": {
164 | "tags": []
165 | },
166 | "outputs": [],
167 | "source": [
168 | "set_measurements = [\n",
169 | " \"blue\",\n",
170 | " \"green\",\n",
171 | " \"red\",\n",
172 | " \"nir08\",\n",
173 | " \"qa_pixel\",\n",
174 | " \"qa_radsat\"\n",
175 | "]\n",
176 | "\n",
177 | "set_crs = 'EPSG:32719'\n",
178 | "\n",
179 | "set_resolution = (-30, 30)"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "id": "801363bf-d57a-419a-89d8-45aea59050ac",
185 | "metadata": {},
186 | "source": [
187 | "Luego, cargamos los datos para cada producto"
188 | ]
189 | },
190 | {
191 | "cell_type": "code",
192 | "execution_count": null,
193 | "id": "53400f1c-92e7-4e77-abae-497d0c4c78a4",
194 | "metadata": {
195 | "tags": []
196 | },
197 | "outputs": [],
198 | "source": [
199 | "images = {}\n",
200 | "for set_product in products:\n",
201 | " images[set_product] = dc.load(\n",
202 | " product=set_product,\n",
203 | " x=study_area_lon,\n",
204 | " y=study_area_lat,\n",
205 | " # time=set_time,\n",
206 | " measurements=set_measurements,\n",
207 | " output_crs=set_crs,\n",
208 | " resolution=set_resolution,\n",
209 | " dask_chunks={\"time\": 1},\n",
210 | " group_by=\"solar_day\",\n",
211 | " skip_broken_datasets=True\n",
212 | " )"
213 | ]
214 | },
215 | {
216 | "cell_type": "markdown",
217 | "id": "5b523779-015d-456a-b266-51f498ec0724",
218 | "metadata": {},
219 | "source": [
220 | "y consultamos el rango de fechas con imágenes disponibles"
221 | ]
222 | },
223 | {
224 | "cell_type": "code",
225 | "execution_count": null,
226 | "id": "97609777-eba2-4efb-98bb-f5a7602bc6fa",
227 | "metadata": {
228 | "tags": []
229 | },
230 | "outputs": [],
231 | "source": [
232 | "pd.DataFrame( {v: [min(images[v].time.values), max(images[v].time.values)] for v in images if images[v]} )"
233 | ]
234 | },
235 | {
236 | "cell_type": "markdown",
237 | "id": "1f80f30d-9377-460d-b83a-b940042e8b7c",
238 | "metadata": {},
239 | "source": [
240 | "Seleccionamos las imágenes landsat 5 y 7, debido a que poseen compatibilidad radiométrica."
241 | ]
242 | },
243 | {
244 | "cell_type": "code",
245 | "execution_count": null,
246 | "id": "326c9cdb-9594-4979-950e-a7f66bb72dac",
247 | "metadata": {
248 | "tags": []
249 | },
250 | "outputs": [],
251 | "source": [
252 | "desired_products = ['landsat5_c2l2_sr', 'landsat7_c2l2_sr']\n",
253 | "\n",
254 | "ds = xr.concat([images[i] for i in desired_products], dim='time')"
255 | ]
256 | },
257 | {
258 | "cell_type": "markdown",
259 | "id": "b2e79c62-81a9-47aa-a00a-4256e2a507ac",
260 | "metadata": {},
261 | "source": [
262 | "***\n",
263 | "\n",
264 | "# Limpiar los datos\n",
265 | "\n",
266 | "## Enmascarar valores no válidos y/o no requeridos\n"
267 | ]
268 | },
269 | {
270 | "cell_type": "code",
271 | "execution_count": null,
272 | "id": "cb699e9a-0b4e-45b1-906c-3ca8e0d573b4",
273 | "metadata": {
274 | "tags": []
275 | },
276 | "outputs": [],
277 | "source": [
278 | "bandas_reflectancia = [\"blue\", \"green\", \"red\", \"nir08\"]\n",
279 | "quality_band = 'qa_pixel'\n",
280 | "cloud_free_mask = masking.make_mask(ds.qa_pixel, cloud='not_high_confidence', cloud_shadow='not_high_confidence', snow='not_high_confidence', nodata=False)\n",
281 | "mask_sat = ds.qa_radsat == 0\n",
282 | "dsf = ds[bandas_reflectancia].where(cloud_free_mask & mask_sat) \n",
283 | "dsf.update(dsf.where((dsf >= 1) & (dsf <= 65455), np.nan))"
284 | ]
285 | },
286 | {
287 | "cell_type": "markdown",
288 | "id": "4ee480a2-f230-4bd7-9216-d38e9988157d",
289 | "metadata": {},
290 | "source": [
291 | "Se aprecia que la obtención de 864 mediciones combinadas entre landsat 5 y landsat 7.\n",
292 | "\n",
293 | "## Descartar escenas con insuficiente información"
294 | ]
295 | },
296 | {
297 | "cell_type": "code",
298 | "execution_count": null,
299 | "id": "83e4153f-4cb4-45d6-b3fb-f88683233e0e",
300 | "metadata": {
301 | "tags": []
302 | },
303 | "outputs": [],
304 | "source": [
305 | "valid_pixel_proportion = cloud_free_mask.sum(dim=(\"x\", \"y\"))/(cloud_free_mask.shape[1] * cloud_free_mask.shape[2])\n",
306 | "valid_threshold = 0.8\n",
307 | "observations_to_keep = (valid_pixel_proportion >= valid_threshold)"
308 | ]
309 | },
310 | {
311 | "cell_type": "code",
312 | "execution_count": null,
313 | "id": "a77e881c-e3e0-4cd1-9dc5-c4bf01c98679",
314 | "metadata": {
315 | "tags": []
316 | },
317 | "outputs": [],
318 | "source": [
319 | "ds_keep = dsf.sel(time=observations_to_keep)#.compute()"
320 | ]
321 | },
322 | {
323 | "cell_type": "code",
324 | "execution_count": null,
325 | "id": "01521bc8-6273-4163-8b8e-5c922b74952c",
326 | "metadata": {
327 | "tags": []
328 | },
329 | "outputs": [],
330 | "source": [
331 | "ds_keep"
332 | ]
333 | },
334 | {
335 | "cell_type": "markdown",
336 | "id": "28a8bdbb-2a54-4187-825d-a254af7bbd85",
337 | "metadata": {},
338 | "source": [
339 | "Se aprecia que la obtención de 536 mediciones combinadas entre landsat 5 y landsat 7 con al menos un 80% de píxeles válidos.\n",
340 | "\n",
341 | "## Reescalar valores digitales a reflectancia"
342 | ]
343 | },
344 | {
345 | "cell_type": "code",
346 | "execution_count": null,
347 | "id": "ab161ade-c552-4c08-8cca-671e09e7e6e5",
348 | "metadata": {},
349 | "outputs": [],
350 | "source": [
351 | "ds_keep.update(ds_keep * 0.0000275 + -0.2)\n",
352 | "ds_keep.update(ds_keep.where(ds_keep >= 0).where(ds_keep <= 1))\n",
353 | "dsf = ds_keep"
354 | ]
355 | },
356 | {
357 | "cell_type": "markdown",
358 | "id": "ebb4a605-6e11-40d8-8a87-a05d00e294f6",
359 | "metadata": {},
360 | "source": [
361 | "> **Nota importante**: cada banda del producto está en valores enteros (int16), para indicar la reflectancia. Se guardan de esta manera para disminuir el tamaño del archivo, pero antes de realizar cálculos, deben ser escalados de vuelta a valores decimales contenidos entre [0, 1]. Este factor de escalamiento es de 0.0000275 y un factor de adición de -0.2. Todo esto está especificado en la [documentación](https://d9-wret.s3.us-west-2.amazonaws.com/assets/palladium/production/s3fs-public/media/files/LSDS-1618_Landsat-4-7_C2-L2-ScienceProductGuide-v4.pdf).\n",
362 | "\n",
363 | "# Definir índices a utilizar\n",
364 | "\n",
365 | "Los índices de vegetación son muy utilizados en el monitreo del estado de la vegetación, entre otros fenómenos de interés. Uno de los más usados para ver el estado de vigor (y uno de las más antiguos), es el NDVI que utiliza las bandas rojo e infra-rojo cercano.\n",
366 | "\n",
367 | "Se usará este índice a modo de mostración, pero se podría utilizar cualquier otro."
368 | ]
369 | },
370 | {
371 | "cell_type": "code",
372 | "execution_count": null,
373 | "id": "5480d61a-cfbc-40ac-8baf-f0936f3cf01c",
374 | "metadata": {
375 | "tags": []
376 | },
377 | "outputs": [],
378 | "source": [
379 | "ndvi = (dsf['nir08'] - dsf['red']) / (dsf['nir08'] + dsf['red'])"
380 | ]
381 | },
382 | {
383 | "cell_type": "code",
384 | "execution_count": null,
385 | "id": "56be1589-8036-4e19-b567-f342dd62d6e0",
386 | "metadata": {
387 | "tags": []
388 | },
389 | "outputs": [],
390 | "source": [
391 | "plt.figure(figsize=(10, 10))\n",
392 | "ndvi.isel(time=0).plot(cmap=\"RdYlGn\", vmin=-.2, vmax=.8)\n",
393 | "plt.show()"
394 | ]
395 | },
396 | {
397 | "cell_type": "code",
398 | "execution_count": null,
399 | "id": "bbc3d44e-5936-4c0c-8442-b2d377bd72fd",
400 | "metadata": {
401 | "tags": []
402 | },
403 | "outputs": [],
404 | "source": [
405 | "dsf[[\"red\", \"green\", \"blue\"]].isel(time=0).to_array().plot.imshow(vmin=0,vmax=.3,figsize=(10,10))\n",
406 | "\n",
407 | "# Set the title and axis labels\n",
408 | "ax = plt.gca()\n",
409 | "ax.set_xlabel('Easting (m)', fontweight='bold')\n",
410 | "ax.set_ylabel('Northing (m)', fontweight='bold')\n",
411 | "\n",
412 | "# Display the plot\n",
413 | "plt.show()"
414 | ]
415 | },
416 | {
417 | "cell_type": "code",
418 | "execution_count": null,
419 | "id": "a22f0889-1668-4370-9d84-05558621a5cb",
420 | "metadata": {
421 | "tags": []
422 | },
423 | "outputs": [],
424 | "source": [
425 | "dsf = dsf.assign(ndvi=ndvi)\n",
426 | "dsf"
427 | ]
428 | },
429 | {
430 | "cell_type": "markdown",
431 | "id": "8d228dce-7e2a-49f8-b91c-9c0ece88f14e",
432 | "metadata": {
433 | "tags": []
434 | },
435 | "source": [
436 | "## Limitar análisis a áreas previamente definidas\n",
437 | "\n",
438 | "Utilizaremos un archivo vectorial, del Ministerio del Medio Ambiente, que delimita los humedales para el año 2015. Utilizaremos el siguiente [sitio](https://humedaleschile.mma.gob.cl/inventario-humadales/) para descargar los datos a través de las siguientes celdas:"
439 | ]
440 | },
441 | {
442 | "cell_type": "code",
443 | "execution_count": null,
444 | "id": "676d8b14-b82e-4f0a-92d0-c298423b697d",
445 | "metadata": {
446 | "tags": []
447 | },
448 | "outputs": [],
449 | "source": [
450 | "import urllib.request\n",
451 | "import os\n",
452 | "\n",
453 | "if not os.path.exists(\"humedales_vector.zip\"):\n",
454 | " urllib.request.urlretrieve(\"https://humedaleschile.mma.gob.cl/wp-content/uploads/2017/10/inventario_humedales_publico.gdb.zip\", \"humedales_vector.zip\")"
455 | ]
456 | },
457 | {
458 | "cell_type": "code",
459 | "execution_count": null,
460 | "id": "acac9184-cf88-4f97-86c2-5f11f5fcffc7",
461 | "metadata": {
462 | "tags": []
463 | },
464 | "outputs": [],
465 | "source": [
466 | "import geopandas as gpd\n",
467 | "import rasterstats as rs\n",
468 | "import pyproj\n",
469 | "from shapely.geometry import Polygon"
470 | ]
471 | },
472 | {
473 | "cell_type": "code",
474 | "execution_count": null,
475 | "id": "2875893e-21b1-47c3-95b3-61043684f3a4",
476 | "metadata": {
477 | "tags": []
478 | },
479 | "outputs": [],
480 | "source": [
481 | "humedales = gpd.read_file('zip://humedales_vector.zip!Inventario_humedales_publico.gdb', layer='inventario_plataforma')"
482 | ]
483 | },
484 | {
485 | "cell_type": "code",
486 | "execution_count": null,
487 | "id": "ee56ed8b-5937-43dc-81ce-5ced8aefaa9d",
488 | "metadata": {
489 | "tags": []
490 | },
491 | "outputs": [],
492 | "source": [
493 | "y0, y1 = study_area_lat\n",
494 | "x1, x0 = study_area_lon\n",
495 | "polygon = Polygon([(x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0)])\n",
496 | "clipper = gpd.GeoDataFrame([1], geometry=[polygon], crs=pyproj.CRS(\"EPSG:4326\"))\n",
497 | "clipper = clipper.to_crs(humedales.crs)\n",
498 | "# clipper.plot()\n",
499 | "humedales_ = gpd.clip(humedales, clipper)\n",
500 | "humedales_ = humedales_.loc[humedales_.Clase != \"Rios\"]"
501 | ]
502 | },
503 | {
504 | "cell_type": "markdown",
505 | "id": "aad23bb2-5ec0-4495-8815-cd6e95c7090c",
506 | "metadata": {
507 | "tags": []
508 | },
509 | "source": [
510 | "Más información sobre [como armar geometrías y como cortar geometrías](https://geopandas.org/gallery/plot_clip.html). Un vistazo al archivo vectorial:"
511 | ]
512 | },
513 | {
514 | "cell_type": "code",
515 | "execution_count": null,
516 | "id": "3276bbaa-d52d-4362-8d1f-0b0bb279ea71",
517 | "metadata": {
518 | "tags": []
519 | },
520 | "outputs": [],
521 | "source": [
522 | "humedales_.plot()"
523 | ]
524 | },
525 | {
526 | "cell_type": "code",
527 | "execution_count": null,
528 | "id": "f5b37349-f736-4535-94f7-0597f0b39bb4",
529 | "metadata": {
530 | "tags": []
531 | },
532 | "outputs": [],
533 | "source": [
534 | "humedales_or = humedales_.copy()"
535 | ]
536 | },
537 | {
538 | "cell_type": "code",
539 | "execution_count": null,
540 | "id": "db243b20-3ebd-454b-805f-8e3bfd88d0c0",
541 | "metadata": {
542 | "tags": []
543 | },
544 | "outputs": [],
545 | "source": [
546 | "# humedales_['geometry'] = humedales_.geometry.buffer(distance = 100) #### Not working\n",
547 | "# humedales_ = humedales_.geometry[~(humedales_.geometry.is_empty | humedales_.geometry.isna())] #### Did not fix the problem\n",
548 | "for i in range(humedales_.shape[0]):\n",
549 | " humedales_['geometry'].iloc[i] = humedales_.geometry.iloc[i].buffer(distance = 100)\n",
550 | "# El buffer en la totalidad o en un subconjunto daba error, pero 1 a 1 funcionaba bien, por esto se implementó el loop"
551 | ]
552 | },
553 | {
554 | "cell_type": "code",
555 | "execution_count": null,
556 | "id": "b3ac9359-b5f7-418f-ad85-bc83e3d487e3",
557 | "metadata": {
558 | "tags": []
559 | },
560 | "outputs": [],
561 | "source": [
562 | "humedales_.plot()"
563 | ]
564 | },
565 | {
566 | "cell_type": "markdown",
567 | "id": "8e32afae-3733-4b16-857f-4fe2947e3a27",
568 | "metadata": {
569 | "tags": []
570 | },
571 | "source": [
572 | "***\n",
573 | "# Realizar compuestos \n",
574 | "\n",
575 | "Agruparemos por tiempo calculando la mediana anual de los meses de diciembre, enero, febrero y marzo.\n",
576 | "\n",
577 | "Primero, armamos un vector con las fechas de interés que están en el cubo, y generamos un subconjunto.\n"
578 | ]
579 | },
580 | {
581 | "cell_type": "code",
582 | "execution_count": null,
583 | "id": "eb7c1b0b-b40b-48b4-91ca-1c2360304a76",
584 | "metadata": {
585 | "tags": []
586 | },
587 | "outputs": [],
588 | "source": [
589 | "summer_dates = dsf.time[dsf.time.dt.month.isin([12, 1, 2, 3])]\n",
590 | "ds_summer = dsf.sel(time=summer_dates)"
591 | ]
592 | },
593 | {
594 | "cell_type": "markdown",
595 | "id": "d92ba775-39df-4c70-8a59-32f55de14a5a",
596 | "metadata": {},
597 | "source": [
598 | "Es necesario generar una variable por temporada. Diciembre (12), pertenece al año anterior, por lo que no se puede utilizar el año directamente. \n",
599 | "\n",
600 | "Para solucionarlo, tomaremos la coordenada de base `time` y le añadimos 31 días (aquí nos interesa el año, no el mes, por lo tanto no hay problema al hacerlo de esta forma)."
601 | ]
602 | },
603 | {
604 | "cell_type": "code",
605 | "execution_count": null,
606 | "id": "dbcbb3a5-f73c-4cf6-9337-66db99784a27",
607 | "metadata": {
608 | "tags": []
609 | },
610 | "outputs": [],
611 | "source": [
612 | "ds_summer = ds_summer.assign_coords(season=(ds_summer.time + np.timedelta64(31, 'D')))\n",
613 | "ds_summer"
614 | ]
615 | },
616 | {
617 | "cell_type": "markdown",
618 | "id": "e8019e07-c786-4d86-a7c4-04bfb9caf11d",
619 | "metadata": {
620 | "tags": []
621 | },
622 | "source": [
623 | "La coordenada ha sido añadida satisfactoriamente, por lo que ahora se puede usar como variable de agrupamiento y calcular la mediana de la temporada (ignorando valores ausentes)"
624 | ]
625 | },
626 | {
627 | "cell_type": "code",
628 | "execution_count": null,
629 | "id": "6642f86b-19b6-4ec2-afd3-23ad8ed97502",
630 | "metadata": {
631 | "tags": []
632 | },
633 | "outputs": [],
634 | "source": [
635 | "# ds_summer = ds_summer.groupby('season.year').reduce(np.nanmean) # más lento: favorecer las funciones de xarray\n",
636 | "ds_summer_ag = ds_summer.groupby('season.year').median(skipna=True).persist()\n",
637 | "# ds_summer = ds_summer.groupby('season.year').mean(skipna=True)\n",
638 | "ds_summer_ag"
639 | ]
640 | },
641 | {
642 | "cell_type": "markdown",
643 | "id": "5a3f5d71-2195-4f2b-9206-84be2ac4f636",
644 | "metadata": {
645 | "tags": []
646 | },
647 | "source": [
648 | "La serie temporal agregada está lista, podemos visualizarla para el 2014 (en color y el índice NDVI):"
649 | ]
650 | },
651 | {
652 | "cell_type": "code",
653 | "execution_count": null,
654 | "id": "a1551671-a1d4-4ddb-bfe0-78249443a355",
655 | "metadata": {
656 | "tags": []
657 | },
658 | "outputs": [],
659 | "source": [
660 | "fig = plt.figure(figsize=(20,10))\n",
661 | "ax1 = fig.add_subplot(121)\n",
662 | "\n",
663 | "ds_summer_ag[[\"red\", \"green\", \"blue\"]].sel(year=2014).to_array().plot.imshow(vmin=0,vmax=.3,ax=ax1) #figsize=(10,10)\n",
664 | "ax1.set_xlabel('Easting (m)', fontweight='bold')\n",
665 | "ax1.set_ylabel('Northing (m)', fontweight='bold')\n",
666 | "\n",
667 | "ax2 = fig.add_subplot(122)\n",
668 | "ds_summer_ag['ndvi'].sel(year=2014).plot.imshow(cmap=\"RdYlGn\", ax=ax2)"
669 | ]
670 | },
671 | {
672 | "cell_type": "markdown",
673 | "id": "e239fe76-da08-434b-90ef-74d04830ded3",
674 | "metadata": {},
675 | "source": [
676 | "Si enmascaramos la zona particular del estudio, utilizando el buffer de humedales, el resultado se ve más limpio:"
677 | ]
678 | },
679 | {
680 | "cell_type": "code",
681 | "execution_count": null,
682 | "id": "0434e4b7-921d-47af-94e8-b74ae26dea70",
683 | "metadata": {
684 | "tags": []
685 | },
686 | "outputs": [],
687 | "source": [
688 | "import rasterio as rio\n",
689 | "\n",
690 | "smask = rio.features.geometry_mask(humedales_.geometry, \n",
691 | " out_shape=(len(ds.y), len(ds.x)),\n",
692 | " transform=ds.geobox.transform,\n",
693 | " invert=True)\n",
694 | "\n",
695 | "ds_summer_masked = ds_summer_ag.where(smask)\n",
696 | "\n",
697 | "\n",
698 | "fig = plt.figure(figsize=(20,10))\n",
699 | "ax1 = fig.add_subplot(121)\n",
700 | "\n",
701 | "ds_summer_masked[[\"red\", \"green\", \"blue\"]].sel(year=2014).to_array().plot.imshow(vmin=0,vmax=.3,ax=ax1) #figsize=(10,10)\n",
702 | "ax1.set_xlabel('Easting (m)', fontweight='bold')\n",
703 | "ax1.set_ylabel('Northing (m)', fontweight='bold')\n",
704 | "\n",
705 | "ax2 = fig.add_subplot(122)\n",
706 | "ds_summer_masked['ndvi'].sel(year=2014).plot.imshow(cmap=\"RdYlGn\", ax=ax2)"
707 | ]
708 | },
709 | {
710 | "cell_type": "markdown",
711 | "id": "747c7a92-7954-4db9-8f11-88e1c2bc46c4",
712 | "metadata": {},
713 | "source": [
714 | "Además, podemos sobreponer los contornos de los humedales delimitados:"
715 | ]
716 | },
717 | {
718 | "cell_type": "code",
719 | "execution_count": null,
720 | "id": "64df9d10-63ba-4fac-8bd7-42310b6b9b03",
721 | "metadata": {
722 | "tags": []
723 | },
724 | "outputs": [],
725 | "source": [
726 | "fig = plt.figure(figsize=(20,10))\n",
727 | "ax1 = fig.add_subplot(121)\n",
728 | "\n",
729 | "ds_summer_ag[[\"red\", \"green\", \"blue\"]].sel(year=2014).to_array().plot.imshow(vmin=0,vmax=.3,ax=ax1) #figsize=(10,10)\n",
730 | "humedales_or.plot(ax=ax1, facecolor='none', edgecolor='red')\n",
731 | "ax1.set_xlabel('Easting (m)', fontweight='bold')\n",
732 | "ax1.set_ylabel('Northing (m)', fontweight='bold')\n",
733 | "\n",
734 | "ax2 = fig.add_subplot(122)\n",
735 | "ds_summer_masked['ndvi'].sel(year=2014).plot.imshow(cmap=\"RdYlGn\", ax=ax2)\n",
736 | "humedales_or.plot(ax=ax2, facecolor='none', edgecolor='black')"
737 | ]
738 | },
739 | {
740 | "cell_type": "markdown",
741 | "id": "7c2441a2-1201-4a3a-8c6a-00aae865387c",
742 | "metadata": {},
743 | "source": [
744 | "Finalmente, la información puede ser exportada como netcdf, para ser utilizada en otras plataformas/programas"
745 | ]
746 | },
747 | {
748 | "cell_type": "code",
749 | "execution_count": null,
750 | "id": "235729c5-b220-4627-85bb-44fd722209fe",
751 | "metadata": {
752 | "tags": []
753 | },
754 | "outputs": [],
755 | "source": [
756 | "from datacube.drivers.netcdf import write_dataset_to_netcdf\n",
757 | "import warnings\n",
758 | "warnings.filterwarnings('ignore')\n",
759 | "\n",
760 | "if os.path.exists('humedales.nc'):\n",
761 | " os.remove('humedales.nc')\n",
762 | " \n",
763 | "write_dataset_to_netcdf(ds_summer_masked, 'humedales.nc')"
764 | ]
765 | },
766 | {
767 | "cell_type": "markdown",
768 | "id": "338a3306-61b2-4090-aa52-2897638c21d6",
769 | "metadata": {},
770 | "source": [
771 | "***\n",
772 | "\n",
773 | "# Obtener estadísticas zonales\n",
774 | "\n",
775 | "Extraemos las estadísticas para cada zona iterando por cada año. Debemos procurar que tanto el archivo vectorial como el xarray tengan el mismo CRS."
776 | ]
777 | },
778 | {
779 | "cell_type": "code",
780 | "execution_count": null,
781 | "id": "a796ccef-8d8d-4875-bc3e-e45397c173df",
782 | "metadata": {
783 | "tags": []
784 | },
785 | "outputs": [],
786 | "source": [
787 | "my_stats = []\n",
788 | "for i, date in enumerate(ds_summer_ag.year.values):\n",
789 | " if i % 5 == 0:\n",
790 | " print('{} - {}'.format(i, date))\n",
791 | " temp = rs.zonal_stats(humedales_, \n",
792 | " ds_summer_ag.ndvi.isel(year=i).values, \n",
793 | " affine=ndvi.affine, \n",
794 | " stats=\"count min mean max median std\", \n",
795 | " nodata=np.nan) # especificar no data, solo para evitar advertencias. Los valores NA ya están como np.nan\n",
796 | " temp_ = pd.merge(pd.DataFrame(humedales_.reset_index().drop(columns=\"geometry\")), \n",
797 | " pd.DataFrame(temp), \n",
798 | " left_index=True, \n",
799 | " right_index=True)\n",
800 | " temp_['Año'] = date\n",
801 | " my_stats.append(temp_)"
802 | ]
803 | },
804 | {
805 | "cell_type": "markdown",
806 | "id": "792dd040-4539-4125-b27a-a41f40c3899e",
807 | "metadata": {},
808 | "source": [
809 | "***\n",
810 | "\n",
811 | "# Visualizar resultados\n",
812 | "\n",
813 | "Observemos la variación de la mediana de todos los humedales del área de estudio:"
814 | ]
815 | },
816 | {
817 | "cell_type": "code",
818 | "execution_count": null,
819 | "id": "dd7d421d-08e6-413a-8f3f-c76be5fc6317",
820 | "metadata": {
821 | "tags": []
822 | },
823 | "outputs": [],
824 | "source": [
825 | "serie_temporal = pd.concat(my_stats)\n",
826 | "pd.pivot(serie_temporal, index=\"Año\", columns='Id_humedal', values=\"median\").plot(figsize=(12,12))"
827 | ]
828 | },
829 | {
830 | "cell_type": "markdown",
831 | "id": "73651750-9f32-4b5b-a11f-5172b09aabc1",
832 | "metadata": {},
833 | "source": [
834 | "Esta es la ubicación de cada humedal:"
835 | ]
836 | },
837 | {
838 | "cell_type": "code",
839 | "execution_count": null,
840 | "id": "dbf7b2e1-56f2-401d-b365-06816b5e93f1",
841 | "metadata": {
842 | "tags": []
843 | },
844 | "outputs": [],
845 | "source": [
846 | "ax = humedales_or.plot(figsize=(15, 15))\n",
847 | "humedales_or.apply(lambda x: ax.annotate(text=x.Id_humedal, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);"
848 | ]
849 | },
850 | {
851 | "cell_type": "markdown",
852 | "id": "59140e6c-40e3-42d3-9038-909524ad35c7",
853 | "metadata": {},
854 | "source": [
855 | "Los humedales de interés son:"
856 | ]
857 | },
858 | {
859 | "cell_type": "code",
860 | "execution_count": null,
861 | "id": "aee6ca3d-95fd-4f73-85e2-79fa3da30544",
862 | "metadata": {
863 | "tags": []
864 | },
865 | "outputs": [],
866 | "source": [
867 | "humedales_id = ['HU-OT-4023', # humedal objetivo\n",
868 | " 'HU-OT-4028', 'HU-OT-4029', 'HU-OT-4030', 'HU-OT-4031', 'HU-OT-4033', 'HU-OT-4040'] # humedales control"
869 | ]
870 | },
871 | {
872 | "cell_type": "markdown",
873 | "id": "a870bed7-8b16-4d51-914a-88137bfd99a8",
874 | "metadata": {},
875 | "source": [
876 | "Utilizando solo el humedal objetivo visualicemos sus estadísticas en el tiempo:"
877 | ]
878 | },
879 | {
880 | "cell_type": "code",
881 | "execution_count": null,
882 | "id": "28f8f95d-d6d4-4c62-b9fe-291256ddda1b",
883 | "metadata": {
884 | "tags": []
885 | },
886 | "outputs": [],
887 | "source": [
888 | "fig = plt.figure(figsize=(20,10))\n",
889 | "ax1 = fig.add_subplot(121)\n",
890 | "ax2 = fig.add_subplot(122)\n",
891 | "\n",
892 | "agg_ = serie_temporal.loc[serie_temporal.Id_humedal == humedales_id[0]].set_index('Año')[['min', 'mean', 'median', 'max']]\n",
893 | "\n",
894 | "agg_.plot(ax = ax1) #, subplots=True)\n",
895 | "agg_[['min', 'mean', 'median']].plot(ax = ax2) #, subplots=True)"
896 | ]
897 | },
898 | {
899 | "cell_type": "markdown",
900 | "id": "2fa369a7-7f23-47a3-b47b-400b279b6376",
901 | "metadata": {
902 | "tags": []
903 | },
904 | "source": [
905 | "Comparemos el humedal objetivo con los de control (en su media y viendo su tendencia general):"
906 | ]
907 | },
908 | {
909 | "cell_type": "code",
910 | "execution_count": null,
911 | "id": "af324dc3-c491-4d14-a22f-cc17db04ae38",
912 | "metadata": {
913 | "tags": []
914 | },
915 | "outputs": [],
916 | "source": [
917 | "compara = serie_temporal.loc[serie_temporal.Id_humedal.isin(humedales_id)][['Año', 'Id_humedal', 'mean']]\n",
918 | "pd.pivot(compara, index=\"Año\", columns='Id_humedal', values=\"mean\").plot(figsize=(12,12))"
919 | ]
920 | },
921 | {
922 | "cell_type": "code",
923 | "execution_count": null,
924 | "id": "c6dbf157-6a82-4eba-8775-aa90902b07e3",
925 | "metadata": {
926 | "tags": []
927 | },
928 | "outputs": [],
929 | "source": [
930 | "cmap = plt.cm.tab10\n",
931 | "colores = cmap(np.linspace(0, 1, len(humedales_id)))\n",
932 | "colores = {humedales_id[i]: colores[i] for i in range(len(humedales_id))}\n",
933 | "\n",
934 | "fig, ax = plt.subplots(figsize=(15, 15))\n",
935 | "\n",
936 | "for i in humedales_id:\n",
937 | " temp = serie_temporal.loc[serie_temporal.Id_humedal == i]\n",
938 | " x, y = temp[['Año', 'mean']].T.to_numpy()\n",
939 | " plt.plot(x, y, '-o', color=colores[i])\n",
940 | " z = np.polyfit(x, y, 1)\n",
941 | " p = np.poly1d(z)\n",
942 | " plt.plot(x, p(x), \"--\", color=colores[i])"
943 | ]
944 | },
945 | {
946 | "cell_type": "markdown",
947 | "id": "3ea43e9f-bb03-4a64-9d57-ff437c9ad374",
948 | "metadata": {},
949 | "source": [
950 | "Los resultados son bastante similares a los obtenidos por el Ministerio del Medio Ambiental, y se aprecia claramente que el humedal objetivo (HU-OT-4023, en azul), tiene un marcado descenso que no está presente en el resto de los humedales"
951 | ]
952 | },
953 | {
954 | "cell_type": "code",
955 | "execution_count": null,
956 | "id": "d2148472-50ad-42c6-873e-e7da286946e7",
957 | "metadata": {},
958 | "outputs": [],
959 | "source": [
960 | "client.close()\n",
961 | "\n",
962 | "cluster.close()"
963 | ]
964 | },
965 | {
966 | "cell_type": "markdown",
967 | "id": "7e069c20-14c6-44c6-8b65-502278ae6f43",
968 | "metadata": {},
969 | "source": [
970 | "***\n",
971 | "\n",
972 | "# *Siguientes pasos* 🐾\n",
973 | "\n",
974 | "Para continuar con el tutorial pueden acceder a los notebooks del siguiente listado.\n",
975 | "\n",
976 | "1. [Acceso](00_Acceso.ipynb)\n",
977 | "2. [Cargar datos](01_Cargar_datos.ipynb)\n",
978 | "3. [Limpieza](02_Limpieza.ipynb)\n",
979 | "4. [Análisis básico](03_Análisis_básico.ipynb)\n",
980 | "5. [Caso de estudio 1](04_Caso_de_estudio_1.ipynb)\n",
981 | "6. **Caso de estudio 2**\n",
982 | "\n",
983 | "***"
984 | ]
985 | }
986 | ],
987 | "metadata": {
988 | "kernelspec": {
989 | "display_name": "Python 3 (ipykernel)",
990 | "language": "python",
991 | "name": "python3"
992 | },
993 | "language_info": {
994 | "codemirror_mode": {
995 | "name": "ipython",
996 | "version": 3
997 | },
998 | "file_extension": ".py",
999 | "mimetype": "text/x-python",
1000 | "name": "python",
1001 | "nbconvert_exporter": "python",
1002 | "pygments_lexer": "ipython3",
1003 | "version": "3.10.12"
1004 | }
1005 | },
1006 | "nbformat": 4,
1007 | "nbformat_minor": 5
1008 | }
1009 |
--------------------------------------------------------------------------------
/extra/capturas/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/01.png
--------------------------------------------------------------------------------
/extra/capturas/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/02.png
--------------------------------------------------------------------------------
/extra/capturas/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/03.png
--------------------------------------------------------------------------------
/extra/capturas/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/04.png
--------------------------------------------------------------------------------
/extra/capturas/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/05.png
--------------------------------------------------------------------------------
/extra/capturas/06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/06.png
--------------------------------------------------------------------------------
/extra/capturas/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/07.png
--------------------------------------------------------------------------------
/extra/capturas/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/08.png
--------------------------------------------------------------------------------
/extra/capturas/Server1_2023-05-23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/Server1_2023-05-23.png
--------------------------------------------------------------------------------
/extra/capturas/Server2_2023-05-23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/Server2_2023-05-23.png
--------------------------------------------------------------------------------
/extra/capturas/c1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/c1.png
--------------------------------------------------------------------------------
/extra/capturas/c1_2023_05_23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/c1_2023_05_23.png
--------------------------------------------------------------------------------
/extra/capturas/git.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/git.PNG
--------------------------------------------------------------------------------
/extra/capturas/intro1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/intro1.png
--------------------------------------------------------------------------------
/extra/capturas/login1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/login1.PNG
--------------------------------------------------------------------------------
/extra/capturas/login2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/login2.PNG
--------------------------------------------------------------------------------
/extra/capturas/primer_login.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/primer_login.PNG
--------------------------------------------------------------------------------
/extra/capturas/s1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/s1.png
--------------------------------------------------------------------------------
/extra/capturas/server_options.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/capturas/server_options.PNG
--------------------------------------------------------------------------------
/extra/functions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/functions/__init__.py
--------------------------------------------------------------------------------
/extra/functions/holoplots.py:
--------------------------------------------------------------------------------
1 | import holoviews as hv
2 | import datashader
3 | import xarray as xr
4 | import numpy as np
5 | from holoviews.operation.datashader import regrid
6 |
7 |
8 | def normalize(agg,max):
9 | min_val = 0
10 | max_val = max
11 | c = 25
12 | th = .11
13 | range_val = max_val - min_val
14 | norm = (agg - min_val) / range_val
15 | norm = 1 / (1 + np.exp(c * (th - norm)))
16 | norm = norm * 255
17 | return norm
18 |
19 |
20 | def combine_bands(r: xr.DataArray, g: xr.DataArray, b: xr.DataArray, gamma: float = .8):
21 | rmax = r.max().values.item() * gamma
22 | gmax = g.max().values.item() * gamma
23 | bmax = b.max().values.item() * gamma
24 | xs, ys = r['x'], r['y']
25 | r, g, b = [datashader.utils.orient_array(img) for img in (r, g, b)]
26 |
27 | a = (np.where(np.isnan(b),0,255)).astype(np.uint8)
28 | r = (normalize(r,rmax)).astype(np.uint8) # try it to return valid RGB (0-255 range)
29 | g = (normalize(g,gmax)).astype(np.uint8)
30 | b = (normalize(b,bmax)).astype(np.uint8)
31 | return hv.RGB((xs, ys[::-1], r, g, b, a), vdims=list('RGBA'))
32 |
33 |
34 | def plot_single_hv(ds: xr.Dataset, variables: list = ['red', 'green', 'blue'], **kwargs):
35 | """
36 | 2d image plot using holoviews. 2d array only, not 3d (discard time or other coord before using)
37 | only the first three variables will be used
38 | """
39 | if len(variables) != 3:
40 | raise Exception('The number of data variables provided, must be 3')
41 |
42 | r=ds[variables[0]]
43 | g=ds[variables[1]]
44 | b=ds[variables[2]]
45 |
46 | true_color = combine_bands(r,g,b, **kwargs)
47 | return regrid(true_color)
--------------------------------------------------------------------------------
/extra/logos/logos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/extra/logos/logos.png
--------------------------------------------------------------------------------
/material_complementario/02b_Limpieza_fasat_charlie_ms.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "a2aef4ab-a953-4f20-a70b-75cb79eb7810",
6 | "metadata": {
7 | "tags": []
8 | },
9 | "source": [
10 | "
"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "195ad897-0616-458c-98fa-a52f1f11d4f1",
16 | "metadata": {
17 | "tags": []
18 | },
19 | "source": [
20 | "# Limpieza Fasat Charlie multi-spectral (fasat_charlie_ms)\n",
21 | "\n",
22 | "Utilizaremos el notebook `02_Limpieza.ipynb` y `03_Análisis_básico.ipynb` como base para el proceso a desarrollar. \n",
23 | "\n",
24 | "Exploraremos las imágenes disponibles y su extensión.\n",
25 | "\n",
26 | "## 0. Cargar librerías y datos"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "id": "15d8be86-688e-4034-9e53-f66ea96eecf8",
33 | "metadata": {
34 | "tags": []
35 | },
36 | "outputs": [],
37 | "source": [
38 | "from dask.distributed import Client, LocalCluster\n",
39 | "cluster = LocalCluster()\n",
40 | "client = Client(cluster)"
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": null,
46 | "id": "172c44fb-275c-4989-8d53-b615da4a43aa",
47 | "metadata": {
48 | "tags": []
49 | },
50 | "outputs": [],
51 | "source": [
52 | "import datacube\n",
53 | "import xarray as xr\n",
54 | "import numpy as np\n",
55 | "import pandas as pd\n",
56 | "import matplotlib.pyplot as plt\n",
57 | "\n",
58 | "from odc.ui import DcViewer\n",
59 | "from datacube.utils import masking\n",
60 | "from datacube.utils.rio import configure_s3_access\n",
61 | "\n",
62 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)"
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "id": "9416abe2-0e4f-402a-bf93-1b542f025ba5",
69 | "metadata": {
70 | "tags": []
71 | },
72 | "outputs": [],
73 | "source": [
74 | "dc = datacube.Datacube(app='limpieza-fc') "
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "id": "6506a46b-389c-4fa8-a224-96503fe2fd30",
81 | "metadata": {
82 | "tags": []
83 | },
84 | "outputs": [],
85 | "source": [
86 | "query = {\n",
87 | " \"product\": \"fasat_charlie_ms\",\n",
88 | " #\"y\": (-34.00, -32.00), \n",
89 | " #\"x\": (-71.85, -70.00),\n",
90 | " #\"time\": (\"1990-01-01\", \"2024-12-31\"),\n",
91 | " \"output_crs\": \"EPSG:32719\",\n",
92 | " \"resolution\": (-5.8, 5.8),\n",
93 | " \"dask_chunks\": {\"time\": 1, 'x':2048, 'y':2048},\n",
94 | " \"group_by\": \"solar_day\"\n",
95 | "}"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": null,
101 | "id": "b6980ab2-918d-4f38-b625-387f32e8d411",
102 | "metadata": {
103 | "tags": []
104 | },
105 | "outputs": [],
106 | "source": [
107 | "ds = dc.load(**query)\n",
108 | "ds"
109 | ]
110 | },
111 | {
112 | "cell_type": "markdown",
113 | "id": "2e16a33b-65b7-4259-8896-5b4659ce2718",
114 | "metadata": {},
115 | "source": [
116 | "Podemos observar que obtenemos 28 tiempos, y una gran cantidad de píxeles en el eje x e y.\n",
117 | "Adicionalmente, se obtienen solo las bandas `blue`, `green`, `red`, y `nir`.\n",
118 | "\n",
119 | "Por otra parte, `Fasat Charlie` no cuenta con un `Quality Assessment Band`, por lo que no podremos generar máscaras para excluir nubes u otros elementos, como en los tutoriales de `Landsat` y `Sentinel`.\n",
120 | "\n",
121 | "***\n",
122 | "\n",
123 | "Exploraremos con consultar un tiempo en particular obtener su extensión.\n",
124 | "\n",
125 | "> **Advertencia: la ubicación de la imagen y sus píxeles son aproximados.**"
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "execution_count": null,
131 | "id": "eb5bea06-5d2a-450d-80a0-c6df661e2660",
132 | "metadata": {
133 | "tags": []
134 | },
135 | "outputs": [],
136 | "source": [
137 | "ds.time.values"
138 | ]
139 | },
140 | {
141 | "cell_type": "code",
142 | "execution_count": null,
143 | "id": "977bab3d-b228-4414-946e-d7b47048b4c4",
144 | "metadata": {
145 | "tags": []
146 | },
147 | "outputs": [],
148 | "source": [
149 | "ds_ = dc.load(time = \"2014-02-14\", **query)\n",
150 | "ds_"
151 | ]
152 | },
153 | {
154 | "cell_type": "code",
155 | "execution_count": null,
156 | "id": "9fa87e72-50cc-4702-9cda-92333a4f7c45",
157 | "metadata": {
158 | "tags": []
159 | },
160 | "outputs": [],
161 | "source": [
162 | "ds_[['red', 'green', 'blue']].squeeze().to_array().plot.imshow(vmin = 0, vmax = 3000, robust=True, figsize=(15,15))"
163 | ]
164 | },
165 | {
166 | "cell_type": "markdown",
167 | "id": "2ac6e19c-1a0b-42a5-8fb5-0d2d627c75b3",
168 | "metadata": {},
169 | "source": [
170 | "Aquí podemos observar que el borde de la imágen está rellena con valores `0`, y se visualizan de color negro. Esto puede provocar que, al obtener estadísticas temporales, estos valores `0` sean tomados en cuenta en la estadística, obteniendo valores erróneos. Por este motivo, debemos ejecutar la siguiente celda para cambiar estos valores a `nan`."
171 | ]
172 | },
173 | {
174 | "cell_type": "code",
175 | "execution_count": null,
176 | "id": "affdd9d1-efc4-488c-839f-9014992b9cf7",
177 | "metadata": {
178 | "tags": []
179 | },
180 | "outputs": [],
181 | "source": [
182 | "dsf = ds_.where(ds_ != 0, np.nan)"
183 | ]
184 | },
185 | {
186 | "cell_type": "markdown",
187 | "id": "09553ae3-a324-4677-9add-88bc4406e388",
188 | "metadata": {
189 | "tags": []
190 | },
191 | "source": [
192 | "# 1. Reescalar valores digitales a reflectancia\n",
193 | "Es siempre necesario transformar los valores digitales a reflectancia antes de continuar con cualquier análisis. Para el producto Fasat Charlie Multi-Spectral, de momento, no contamos con información oficial para su obtención. Por este motivo, los códigos utilizados a continuación entregarán un valor de reflectividad aproximado.\n",
194 | "\n",
195 | "> **Advertencia: los valores de reflectividad son aproximados, y los métodos de obtención no son oficiales.**\n",
196 | "\n",
197 | "¿Cómo sabemos que tenemos valores digitales?, porque los valores de reflectividad debe ser un decimal entre 0 y 1.\n",
198 | "Si graficamos una banda en particular, por ejemplo la banda roja, podemos observar el rango de valores que toma la imagen."
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": null,
204 | "id": "d60d7c85-7a0f-4702-a3b9-c75ade584baf",
205 | "metadata": {
206 | "tags": []
207 | },
208 | "outputs": [],
209 | "source": [
210 | "dsf.red.isel(time = 0).plot(robust = True)"
211 | ]
212 | },
213 | {
214 | "cell_type": "markdown",
215 | "id": "8d885250-7bde-45d8-8bf9-001cceed5cd4",
216 | "metadata": {
217 | "tags": []
218 | },
219 | "source": [
220 | "Es posible observar valores entre 1000 y 7000, muy lejano de los decimales entre 0 y 1 que requerimos.\n",
221 | "\n",
222 | "En este caso, vamos a utilizar el método de corrección `ND*0.0001`, donde `ND` es el valor que posee cada pixel en cada banda, esto lo hacemos con el siguiente código:"
223 | ]
224 | },
225 | {
226 | "cell_type": "code",
227 | "execution_count": null,
228 | "id": "2b898494-894d-4902-a894-71df7874b61a",
229 | "metadata": {
230 | "tags": []
231 | },
232 | "outputs": [],
233 | "source": [
234 | "dsf.update(dsf * 0.0001)"
235 | ]
236 | },
237 | {
238 | "cell_type": "markdown",
239 | "id": "aaf1888a-0374-4493-9680-c84c76fee002",
240 | "metadata": {
241 | "tags": []
242 | },
243 | "source": [
244 | "Luego, debemos mantener solo los valores que sean válidos. Debido a que la reflectancia debiese entregar valores entre 0 y 1, debemos quedarnos solo con esos valores:"
245 | ]
246 | },
247 | {
248 | "cell_type": "code",
249 | "execution_count": null,
250 | "id": "2c16a9ad-c6f9-4914-9847-5e06acce9f0d",
251 | "metadata": {
252 | "tags": []
253 | },
254 | "outputs": [],
255 | "source": [
256 | "dsf.update(dsf.where((dsf >= 0) & (dsf <= 1)))"
257 | ]
258 | },
259 | {
260 | "cell_type": "markdown",
261 | "id": "209c7aef-a082-42cc-b6d1-520fb761aea4",
262 | "metadata": {},
263 | "source": [
264 | "Volvemos a graficar, notando que ahora los valores en la escala de la derecha de la imagen han cambiado."
265 | ]
266 | },
267 | {
268 | "cell_type": "code",
269 | "execution_count": null,
270 | "id": "d998b0a0-b4b8-486e-9394-c2f317fb86a8",
271 | "metadata": {
272 | "tags": []
273 | },
274 | "outputs": [],
275 | "source": [
276 | "dsf.red.isel(time = 0).plot(robust = True)"
277 | ]
278 | },
279 | {
280 | "cell_type": "markdown",
281 | "id": "7f099a2e-8872-4bae-b809-056dabaa537c",
282 | "metadata": {},
283 | "source": [
284 | "***\n",
285 | "\n",
286 | "# 2. Explorar imágenes Fasat Charlie\n",
287 | "\n",
288 | "Durante esta sección exploraremos las imágenes enfocados en el sector de Petorca, Valparaíso y Glaciar Grey.\n",
289 | "Para esto nos apoyaremos en un archivo vectorial de extensión geopackage llamado \"dataton_zonas.gpkg\""
290 | ]
291 | },
292 | {
293 | "cell_type": "code",
294 | "execution_count": null,
295 | "id": "38bc80eb-aaa4-43a9-a6f6-343430ab10e4",
296 | "metadata": {
297 | "tags": []
298 | },
299 | "outputs": [],
300 | "source": [
301 | "import geopandas as gpd\n",
302 | "poly = gpd.read_file(\"dosafio_fach2024/dataton_zonas.gpkg\")\n",
303 | "poly"
304 | ]
305 | },
306 | {
307 | "cell_type": "markdown",
308 | "id": "3c427cb0-e0bb-4d8a-abad-e2aba06eac03",
309 | "metadata": {},
310 | "source": [
311 | "Exploraremos individualmente cada una de las zonas.\n",
312 | "\n",
313 | "## 2.1. Petorca\n",
314 | "\n",
315 | "Utilizando el primer polígono del vector `dataton_zonas` extraemos las coordenadas que nos ayudaran en la descarga de imágenes."
316 | ]
317 | },
318 | {
319 | "cell_type": "code",
320 | "execution_count": null,
321 | "id": "fe55ca22-ae42-49b2-b2c7-b8407b0f5b9b",
322 | "metadata": {
323 | "tags": []
324 | },
325 | "outputs": [],
326 | "source": [
327 | "xmin, ymin, xmax, ymax = poly.iloc[[0]].total_bounds\n",
328 | "ds1 = dc.load(x = (xmin, xmax), y = (ymin, ymax), **query)\n",
329 | "ds1"
330 | ]
331 | },
332 | {
333 | "cell_type": "markdown",
334 | "id": "40e17ff0-53aa-4862-97d6-68d90a41ae28",
335 | "metadata": {},
336 | "source": [
337 | "Podemos observar que tenemos 3 imágenes, una de 2013, otra 2014, y una última de 2017.\n",
338 | "\n",
339 | "Corregimos los valores ceros a `nan`, obtenemos la reflectividad."
340 | ]
341 | },
342 | {
343 | "cell_type": "code",
344 | "execution_count": null,
345 | "id": "1fc178b1-2df4-42fb-9c2d-cbe5a63d93ac",
346 | "metadata": {
347 | "tags": []
348 | },
349 | "outputs": [],
350 | "source": [
351 | "ds1 = ds1.where(ds1 != 0, np.nan)\n",
352 | "ds1.update(ds1 * 0.0001)\n",
353 | "ds1.update(ds1.where((ds1 >= 0) & (ds1 <= 1)))"
354 | ]
355 | },
356 | {
357 | "cell_type": "markdown",
358 | "id": "5ea2f310-97ad-4a6e-bb9d-7980018daf73",
359 | "metadata": {},
360 | "source": [
361 | "Finalmente, utilizamos la raíz cúbica de la reflectividad multiplicada por 0.6 como una forma de aumentar el contraste de una imagen al visualizarla. Exploraremos la imagen con índice `time = 0`."
362 | ]
363 | },
364 | {
365 | "cell_type": "code",
366 | "execution_count": null,
367 | "id": "74010fd8-26e5-42ed-8f75-42db257b56f6",
368 | "metadata": {
369 | "tags": []
370 | },
371 | "outputs": [],
372 | "source": [
373 | "np.cbrt(0.6*ds1[['red', 'green', 'blue']].isel(time = 0)).to_array().plot.imshow(robust = True, figsize = (15,15), aspect = 1)"
374 | ]
375 | },
376 | {
377 | "cell_type": "markdown",
378 | "id": "830b9624-3cdc-4688-93a2-b3c9fcf0bf24",
379 | "metadata": {
380 | "tags": []
381 | },
382 | "source": [
383 | "## 2.2. Valparaíso\n",
384 | "\n",
385 | "Utilizando el segundo polígono del vector `dataton_zonas` hacemos el proceso que en la zona anterior."
386 | ]
387 | },
388 | {
389 | "cell_type": "code",
390 | "execution_count": null,
391 | "id": "13187bd3-7b14-43e9-bd88-737e397c83e7",
392 | "metadata": {
393 | "tags": []
394 | },
395 | "outputs": [],
396 | "source": [
397 | "xmin, ymin, xmax, ymax = poly.iloc[[1]].total_bounds\n",
398 | "ds2 = dc.load(x = (xmin, xmax), y = (ymin, ymax), **query)\n",
399 | "ds2"
400 | ]
401 | },
402 | {
403 | "cell_type": "code",
404 | "execution_count": null,
405 | "id": "abc2c553-0097-493d-84cd-932b123be692",
406 | "metadata": {
407 | "tags": []
408 | },
409 | "outputs": [],
410 | "source": [
411 | "ds2 = ds2.where(ds2 != 0, np.nan)\n",
412 | "ds2.update(ds2 * 0.0001)\n",
413 | "ds2.update(ds2.where((ds2 >= 0) & (ds2 <= 1)))"
414 | ]
415 | },
416 | {
417 | "cell_type": "code",
418 | "execution_count": null,
419 | "id": "05fd2d1d-789c-45e9-8b7b-81af428c1855",
420 | "metadata": {
421 | "tags": []
422 | },
423 | "outputs": [],
424 | "source": [
425 | "np.cbrt(0.6*ds2[['red', 'green', 'blue']].isel(time = 15)).to_array().plot.imshow(robust = True, aspect = 1, figsize = (15,15))"
426 | ]
427 | },
428 | {
429 | "cell_type": "markdown",
430 | "id": "02ff1988-25c3-4245-9401-ba971682f85d",
431 | "metadata": {},
432 | "source": [
433 | "Y ahora en falso color `nir` `red` `green`."
434 | ]
435 | },
436 | {
437 | "cell_type": "code",
438 | "execution_count": null,
439 | "id": "fa6a4df0-5023-45bd-af3a-9906a6348c8e",
440 | "metadata": {
441 | "tags": []
442 | },
443 | "outputs": [],
444 | "source": [
445 | "np.cbrt(0.6*ds2[['nir', 'red', 'green']].isel(time = 15)).to_array().plot.imshow(robust = True, aspect = 1, figsize = (15,15))"
446 | ]
447 | },
448 | {
449 | "cell_type": "markdown",
450 | "id": "36ac3735-fb52-4848-9f00-b12b5de292c8",
451 | "metadata": {},
452 | "source": [
453 | "## 3. Glaciar Grey\n",
454 | "\n",
455 | "Utilizando el tercer polígono del vector `dataton_zonas` hacemos el proceso que en la zona anterior."
456 | ]
457 | },
458 | {
459 | "cell_type": "code",
460 | "execution_count": null,
461 | "id": "5e64412b-8880-4d9a-ae35-eb1ab93e48a1",
462 | "metadata": {
463 | "tags": []
464 | },
465 | "outputs": [],
466 | "source": [
467 | "xmin, ymin, xmax, ymax = poly.iloc[[2]].total_bounds\n",
468 | "ds3 = dc.load(x = (xmin, xmax), y = (ymin, ymax), **query)\n",
469 | "ds3"
470 | ]
471 | },
472 | {
473 | "cell_type": "code",
474 | "execution_count": null,
475 | "id": "65547f41-2e71-4ac9-862a-884684885b4f",
476 | "metadata": {
477 | "tags": []
478 | },
479 | "outputs": [],
480 | "source": [
481 | "ds3 = ds3.where(ds3 != 0, np.nan)\n",
482 | "ds3.update(ds3 * 0.0001)\n",
483 | "ds3.update(ds3.where((ds3 >= 0) & (ds3 <= 1)))"
484 | ]
485 | },
486 | {
487 | "cell_type": "code",
488 | "execution_count": null,
489 | "id": "8e111147-e812-4ac2-a926-53e67dab3c52",
490 | "metadata": {
491 | "tags": []
492 | },
493 | "outputs": [],
494 | "source": [
495 | "np.cbrt(0.6*ds3[['red', 'green', 'blue']].isel(time = 6)).to_array().plot.imshow(robust = True, figsize = (15,15), aspect = 1)"
496 | ]
497 | },
498 | {
499 | "cell_type": "markdown",
500 | "id": "3ac6283e-2cf1-4a79-a264-fc748563ed21",
501 | "metadata": {},
502 | "source": [
503 | "***\n",
504 | "\n",
505 | "Finalmente, cerramos el cluster que hemos utilizado."
506 | ]
507 | },
508 | {
509 | "cell_type": "code",
510 | "execution_count": null,
511 | "id": "095e7b73-78a2-410f-b96b-442aac058fe5",
512 | "metadata": {
513 | "tags": []
514 | },
515 | "outputs": [],
516 | "source": [
517 | "client.close()\n",
518 | "\n",
519 | "cluster.close()"
520 | ]
521 | },
522 | {
523 | "cell_type": "code",
524 | "execution_count": null,
525 | "id": "8154cc50-8c06-49b5-95c1-93bacf1c3f37",
526 | "metadata": {},
527 | "outputs": [],
528 | "source": []
529 | }
530 | ],
531 | "metadata": {
532 | "kernelspec": {
533 | "display_name": "Python 3 (ipykernel)",
534 | "language": "python",
535 | "name": "python3"
536 | },
537 | "language_info": {
538 | "codemirror_mode": {
539 | "name": "ipython",
540 | "version": 3
541 | },
542 | "file_extension": ".py",
543 | "mimetype": "text/x-python",
544 | "name": "python",
545 | "nbconvert_exporter": "python",
546 | "pygments_lexer": "ipython3",
547 | "version": "3.10.12"
548 | }
549 | },
550 | "nbformat": 4,
551 | "nbformat_minor": 5
552 | }
553 |
--------------------------------------------------------------------------------
/material_complementario/dosafio_fach2024/dataton_zonas.gpkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/material_complementario/dosafio_fach2024/dataton_zonas.gpkg
--------------------------------------------------------------------------------
/material_complementario/dosafio_fach2024/imagenes_no_indexadas.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "74e21314-24d5-49c1-a43b-3dacc2e3e02d",
6 | "metadata": {
7 | "tags": []
8 | },
9 | "source": [
10 | "# Utilización de imágenes de diferentes sensores/plataformas no indexadas en Data Cube Chile (DCC)"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "834fd07a-f840-493b-9d27-117a8cd91d60",
16 | "metadata": {
17 | "tags": []
18 | },
19 | "source": [
20 | "Durante este notebook exploraremos dos temáticas principales:\n",
21 | "\n",
22 | "* Leer imágenes directamente desde los buckets de s3 para imágenes no indexadas en DCC.\n",
23 | "* Manejar imágenes de diferentes extensiones, resoluciones, y alineaciones espaciales. Esto es sólo necesario para imágenes que no están indexadas al catálogo, como las que se describen a continuación. Si un producto está indexado en DCC, al hacer la consulta la alineación se realiza de manera automática y recomendamos hacerlo de esa manera.\n",
24 | "\n",
25 | "\n",
26 | "## Leer imágenes desde buckets de s3\n",
27 | "\n",
28 | "Para este ejemplo, utilizaremos imágenes desde s3 que estén vinculadas al Glaciar Grey.\n",
29 | "\n",
30 | "Para esto necesitamos conocer las rutas específicas de cada imágen en el bucket.\n",
31 | "\n",
32 | "Leamos el csv a continuación para conocer las rutas de cada imagen."
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "11255565-5083-4c53-8218-b5255f48f25e",
39 | "metadata": {
40 | "tags": []
41 | },
42 | "outputs": [],
43 | "source": [
44 | "import pandas as pd\n",
45 | "s3_images = pd.read_csv('s3_imagenes_disponibles.csv')"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "id": "2f4efc52-16da-4308-9f6f-46bd1d7ad486",
52 | "metadata": {
53 | "tags": []
54 | },
55 | "outputs": [],
56 | "source": [
57 | "s3_images"
58 | ]
59 | },
60 | {
61 | "cell_type": "markdown",
62 | "id": "f8c9fb5e-39e7-4ba6-a863-e967f6e54b58",
63 | "metadata": {
64 | "tags": []
65 | },
66 | "source": [
67 | "La columna `full_path` tiene la información que requerimos para obtener las imagenes desde s3.\n",
68 | "Sin embargo, aprovecharemos de filtrar el DataFrame para quedarnos con el sector, producto y tipo de nuestro interés."
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "id": "621daf6a-2703-43ac-a373-4a8a299bd855",
75 | "metadata": {
76 | "tags": []
77 | },
78 | "outputs": [],
79 | "source": [
80 | "s3_images.loc[(s3_images['sector'] == 'Glaciar Grey')]"
81 | ]
82 | },
83 | {
84 | "cell_type": "markdown",
85 | "id": "17b5023d-9c88-46db-a74d-80dbb6e2aa25",
86 | "metadata": {},
87 | "source": [
88 | "Podemos observar que, en el bucket `s3://easido-prod-dc-data-projects/saf/` disponemos de imágenes `aerial` y `planet_scope` para el glaciar grey."
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "id": "757524ec-c3a0-4b22-ab08-109f41b11dbc",
95 | "metadata": {
96 | "tags": []
97 | },
98 | "outputs": [],
99 | "source": [
100 | "s3_images.loc[(s3_images['sector'] == 'Glaciar Grey') & (s3_images['product'] == 'planet_scope') & (s3_images['type'] == 'mss')]"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "id": "d089b213-ca4e-4bdd-a177-2be4f5ac202d",
106 | "metadata": {},
107 | "source": [
108 | "De la misma forma, para `planet_scope` de tipo `mss` existen 4 escenas distintas con múltiples bandas espectrales.\n",
109 | "\n",
110 | "Carguemos una escena con todas sus bandas."
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "id": "64dc871f-27be-4e34-92ce-d32ee158e7f2",
117 | "metadata": {
118 | "tags": []
119 | },
120 | "outputs": [],
121 | "source": [
122 | "from dask.distributed import Client, LocalCluster\n",
123 | "cluster = LocalCluster()\n",
124 | "client = Client(cluster)"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": null,
130 | "id": "eba7d7e6-e9ea-48c7-8eaf-c8c6d80b6430",
131 | "metadata": {
132 | "tags": []
133 | },
134 | "outputs": [],
135 | "source": [
136 | "import datacube\n",
137 | "import xarray as xr\n",
138 | "import numpy as np\n",
139 | "import pandas as pd\n",
140 | "import matplotlib.pyplot as plt\n",
141 | "\n",
142 | "from odc.ui import DcViewer\n",
143 | "from datacube.utils import masking\n",
144 | "from datacube.utils.rio import configure_s3_access\n",
145 | "\n",
146 | "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": null,
152 | "id": "66f12c0d-891d-46eb-8290-d39a30f7c268",
153 | "metadata": {
154 | "tags": []
155 | },
156 | "outputs": [],
157 | "source": [
158 | "import rioxarray\n",
159 | "from rasterio.enums import Resampling"
160 | ]
161 | },
162 | {
163 | "cell_type": "code",
164 | "execution_count": null,
165 | "id": "d1177d9b-9736-4c42-a9b8-72394deea5ea",
166 | "metadata": {
167 | "tags": []
168 | },
169 | "outputs": [],
170 | "source": [
171 | "dc = datacube.Datacube(app='MSS and Multiplatform') "
172 | ]
173 | },
174 | {
175 | "cell_type": "code",
176 | "execution_count": null,
177 | "id": "25f7cb7e-ca78-4c9e-9482-abae7037c6b5",
178 | "metadata": {
179 | "tags": []
180 | },
181 | "outputs": [],
182 | "source": [
183 | "idx = (s3_images['sector'] == 'Glaciar Grey') & (s3_images['product'] == 'planet_scope') & (s3_images['type'] == 'mss') & (s3_images['scene'] == 'REQ_6794')\n",
184 | "bands_name = s3_images.loc[idx]['full_path'].values\n",
185 | "\n",
186 | "# Las bandas para que esten ordenadas de forma asendente según su longitud de onda deben tener el siguiente orden:\n",
187 | "band_order = [\"coastal\", \"blue\", \"green1\", \"green\", \"yellow\", \"red\", \"redegde\", \"nir\"]"
188 | ]
189 | },
190 | {
191 | "cell_type": "code",
192 | "execution_count": null,
193 | "id": "73a28460-0d1a-47d8-ad36-0ccc0a11a3a0",
194 | "metadata": {
195 | "tags": []
196 | },
197 | "outputs": [],
198 | "source": [
199 | "bandsi = [rioxarray.open_rasterio(f, chunks={'x':2048, 'y':2048}).squeeze(drop=True) for f in bands_name]\n",
200 | "\n",
201 | "# extraemos el nombre de cada banda usando los últimos caracteres, después de \"_\", de la lista\n",
202 | "for i, im in enumerate(bandsi):\n",
203 | " bandsi[i].name = bands_name[i].split(\"_\")[-1].split('.')[0]\n",
204 | "\n",
205 | "# Filtramos según el orden y las bandas que definimos anteriormente:\n",
206 | "bandsif = [f for b in band_order for f in bandsi if b == f.name]\n",
207 | "\n",
208 | "ds = xr.merge(bandsif)"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "id": "5b92a237-bfcd-48e1-b7e9-1ca5eb8ba805",
215 | "metadata": {
216 | "tags": []
217 | },
218 | "outputs": [],
219 | "source": [
220 | "ds"
221 | ]
222 | },
223 | {
224 | "cell_type": "markdown",
225 | "id": "457d019e-952d-40db-97da-81e0a29905f1",
226 | "metadata": {},
227 | "source": [
228 | "> Alternativamente, podemos cargar la imagen de manera lazy con Dask, como lo hace DCC pasando como argumento ´chunks´ de la misma forma que las consultas habituales que se hacen al catálogo indexado: `bandsi = [rioxarray.open_rasterio(f, chunks={'x':2048, 'y':2048}).squeeze(drop=True) for f in bands_name]`. De esta forma, podemos sacar mayor provecho a la capacida de cómputo.\n",
229 | "\n",
230 | "Podemos añadir también la fecha de la escena siguiendo el siguiente código:"
231 | ]
232 | },
233 | {
234 | "cell_type": "code",
235 | "execution_count": null,
236 | "id": "8e17c091-ff51-48f1-94fc-a4b453c67c86",
237 | "metadata": {
238 | "tags": []
239 | },
240 | "outputs": [],
241 | "source": [
242 | "date = s3_images.loc[idx]['date'].max()\n",
243 | "ds = ds.expand_dims(time=pd.to_datetime([date], format = \"%Y%m%d\"))"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "id": "9c2ccbce-ce16-4cb1-87e9-134ba1bf077b",
250 | "metadata": {
251 | "tags": []
252 | },
253 | "outputs": [],
254 | "source": [
255 | "ds"
256 | ]
257 | },
258 | {
259 | "cell_type": "code",
260 | "execution_count": null,
261 | "id": "257649bf-eaf2-4a18-9c75-ae4863d4d57a",
262 | "metadata": {
263 | "tags": []
264 | },
265 | "outputs": [],
266 | "source": [
267 | "ds[[\"red\", \"green\", \"blue\"]].squeeze().to_array().plot.imshow(robust=True, figsize=(12, 10), vmin=500)"
268 | ]
269 | },
270 | {
271 | "cell_type": "markdown",
272 | "id": "6511331b-1d6c-4397-90f0-1dc7723e163f",
273 | "metadata": {},
274 | "source": [
275 | "¿Qué resolución tenemos?"
276 | ]
277 | },
278 | {
279 | "cell_type": "code",
280 | "execution_count": null,
281 | "id": "99995477-b85f-453c-8f6d-ec0bdf79d67b",
282 | "metadata": {
283 | "tags": []
284 | },
285 | "outputs": [],
286 | "source": [
287 | "ds.rio.resolution()"
288 | ]
289 | },
290 | {
291 | "cell_type": "markdown",
292 | "id": "e5802b99-5686-4347-9592-ac515da7c3e3",
293 | "metadata": {},
294 | "source": [
295 | "¿Qué sistema de referencia de coordenadas tenemos?"
296 | ]
297 | },
298 | {
299 | "cell_type": "code",
300 | "execution_count": null,
301 | "id": "53179830-e95e-40b2-a6f2-b0ba73d88931",
302 | "metadata": {
303 | "tags": []
304 | },
305 | "outputs": [],
306 | "source": [
307 | "ds.rio.crs"
308 | ]
309 | },
310 | {
311 | "cell_type": "markdown",
312 | "id": "072e0668-cc7f-449d-a397-cb2c29bde48f",
313 | "metadata": {
314 | "tags": []
315 | },
316 | "source": [
317 | "Genial!\n",
318 | "\n",
319 | "Ya es posible utilizar esta escena `planet_scope`.\n",
320 | "\n",
321 | "***\n",
322 | "\n",
323 | "Pero, ¿Cómo genero un cubo con otras imágenes?"
324 | ]
325 | },
326 | {
327 | "cell_type": "code",
328 | "execution_count": null,
329 | "id": "46dbb2a8-e0b9-4af3-8a4b-ee2619faf4ff",
330 | "metadata": {},
331 | "outputs": [],
332 | "source": [
333 | "query = {\n",
334 | " \"product\": \"s2_l2a\",\n",
335 | " #\"measurements\": \"\",\n",
336 | " \"y\": (-51.05, -50.95), \n",
337 | " \"x\": (-73.35, -73.15),\n",
338 | " \"time\": (\"2023-12-25\", \"2024-01-05\"),\n",
339 | " \"output_crs\": \"EPSG:32718\",\n",
340 | " \"resolution\": (-10, 10),\n",
341 | " \"dask_chunks\": {\"time\": 1, 'x':2048, 'y':2048},\n",
342 | " \"group_by\": \"solar_day\"\n",
343 | "}"
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": null,
349 | "id": "2395cdc9-cc35-49e2-bce3-b9a0b1da6250",
350 | "metadata": {
351 | "tags": []
352 | },
353 | "outputs": [],
354 | "source": [
355 | "s2_ds = dc.load(**query)"
356 | ]
357 | },
358 | {
359 | "cell_type": "code",
360 | "execution_count": null,
361 | "id": "2e16ac93-8a85-4793-8b74-a85b0c3ef833",
362 | "metadata": {
363 | "tags": []
364 | },
365 | "outputs": [],
366 | "source": [
367 | "s2_ds"
368 | ]
369 | },
370 | {
371 | "cell_type": "code",
372 | "execution_count": null,
373 | "id": "4d25d1c2-e944-4bca-aaf1-7b2a72b470b4",
374 | "metadata": {
375 | "tags": []
376 | },
377 | "outputs": [],
378 | "source": [
379 | "s2_ds[['red', 'green', 'blue']].to_array().plot.imshow(vmin = 100, vmax = 5000, col = 'time', col_wrap = 5)"
380 | ]
381 | },
382 | {
383 | "cell_type": "markdown",
384 | "id": "89b58202-6844-4e80-a65d-3ba77ec4ba56",
385 | "metadata": {
386 | "tags": []
387 | },
388 | "source": [
389 | "Vamos a elegir algunas bandas espectrales que se comparten en ambas imágenes"
390 | ]
391 | },
392 | {
393 | "cell_type": "code",
394 | "execution_count": null,
395 | "id": "7ebc0036-30c4-4fe8-8b01-b8c0c36b2be5",
396 | "metadata": {
397 | "tags": []
398 | },
399 | "outputs": [],
400 | "source": [
401 | "ds = ds[['coastal', 'blue', 'green', 'red', 'nir']]\n",
402 | "s2_ds = s2_ds[['coastal', 'blue', 'green', 'red', 'nir']]"
403 | ]
404 | },
405 | {
406 | "cell_type": "markdown",
407 | "id": "0db062db-b44f-49b3-9bb8-a5eff3797fb1",
408 | "metadata": {},
409 | "source": [
410 | "Luego vamos a reproyectar `planet_scope` debido a que queremos combinarlo con `sentinel 2`. Siendo `sentinel 2` quien tiene una menor resolución espacial.\n",
411 | "\n",
412 | "Este código ajusta la imagen de `planet_scope` al sistema de referencia de coordenada, extensión, resolución y alineación de `sentinel 2` usando el método de remuestreo `nearest`."
413 | ]
414 | },
415 | {
416 | "cell_type": "code",
417 | "execution_count": null,
418 | "id": "7cc8e2a1-7d04-45e9-a36f-519d6eca738d",
419 | "metadata": {
420 | "tags": []
421 | },
422 | "outputs": [],
423 | "source": [
424 | "ps_ds = ds.rio.reproject(\n",
425 | " s2_ds.rio.crs,\n",
426 | " transform=s2_ds.rio.transform(),\n",
427 | " shape=s2_ds.rio.shape,\n",
428 | " resampling = Resampling.nearest\n",
429 | ")"
430 | ]
431 | },
432 | {
433 | "cell_type": "markdown",
434 | "id": "99f77402-d86c-4dfa-a660-5aac6c2046d0",
435 | "metadata": {
436 | "tags": []
437 | },
438 | "source": [
439 | "Reasignamos el atributo `spatial_ref` para que tenga el valor de EPSG."
440 | ]
441 | },
442 | {
443 | "cell_type": "code",
444 | "execution_count": null,
445 | "id": "04922097-691f-45da-a9ac-03e593b6c5bd",
446 | "metadata": {
447 | "tags": []
448 | },
449 | "outputs": [],
450 | "source": [
451 | "ps_ds.spatial_ref.values = np.array([ps_ds.rio.crs.to_epsg()]).reshape(())"
452 | ]
453 | },
454 | {
455 | "cell_type": "markdown",
456 | "id": "4cb3ff66-345e-46ef-8f3e-0abfcfe530cc",
457 | "metadata": {
458 | "tags": []
459 | },
460 | "source": [
461 | "Y unimos ambos imagenes"
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "execution_count": null,
467 | "id": "0f00c5f4-a551-40aa-843f-d4126ea43652",
468 | "metadata": {
469 | "tags": []
470 | },
471 | "outputs": [],
472 | "source": [
473 | "join_ds = xr.merge([s2_ds, ps_ds])\n",
474 | "join_ds"
475 | ]
476 | },
477 | {
478 | "cell_type": "markdown",
479 | "id": "245f9463-357f-472a-b422-cfc819a6f762",
480 | "metadata": {},
481 | "source": [
482 | "Listo, ¡ya tenemos ambos productos unidos!\n",
483 | "\n",
484 | "¿Veamos los resultados?"
485 | ]
486 | },
487 | {
488 | "cell_type": "code",
489 | "execution_count": null,
490 | "id": "ff9eb869-d1f6-4034-9365-a62d36cf2475",
491 | "metadata": {
492 | "tags": []
493 | },
494 | "outputs": [],
495 | "source": [
496 | "join_ds[[\"red\", \"green\", \"blue\"]].isel(time = [0, 2]).to_array().plot.imshow(vmin = 100, vmax = 5000, col = 'time', figsize = (20,10))"
497 | ]
498 | },
499 | {
500 | "cell_type": "code",
501 | "execution_count": null,
502 | "id": "6698809d-9f32-4daa-9189-707f49980c6e",
503 | "metadata": {
504 | "tags": []
505 | },
506 | "outputs": [],
507 | "source": [
508 | "client.close()\n",
509 | "\n",
510 | "cluster.close()"
511 | ]
512 | }
513 | ],
514 | "metadata": {
515 | "kernelspec": {
516 | "display_name": "Python 3 (ipykernel)",
517 | "language": "python",
518 | "name": "python3"
519 | },
520 | "language_info": {
521 | "codemirror_mode": {
522 | "name": "ipython",
523 | "version": 3
524 | },
525 | "file_extension": ".py",
526 | "mimetype": "text/x-python",
527 | "name": "python",
528 | "nbconvert_exporter": "python",
529 | "pygments_lexer": "ipython3",
530 | "version": "3.10.12"
531 | }
532 | },
533 | "nbformat": 4,
534 | "nbformat_minor": 5
535 | }
536 |
--------------------------------------------------------------------------------
/samsara/LC_mix_matybosquenat2013-2014_cog.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/samsara/LC_mix_matybosquenat2013-2014_cog.tif
--------------------------------------------------------------------------------
/samsara/LC_union_4y7_cog.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/samsara/LC_union_4y7_cog.tif
--------------------------------------------------------------------------------
/samsara/RF_v03_all-trained_negative_of_first.joblib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/samsara/RF_v03_all-trained_negative_of_first.joblib
--------------------------------------------------------------------------------
/samsara/denuncias.gpkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Data-Observatory/DataCubeTraining/7c5ff45fa829980c061baae7d582a838b592c305/samsara/denuncias.gpkg
--------------------------------------------------------------------------------