Files
remote-sensing/01.train_ODC.ipynb
T

1323 lines
128 KiB
Plaintext
Raw Normal View History

2025-12-21 14:34:18 +07:00
{
"cells": [
{
"cell_type": "code",
2026-03-03 08:58:45 +00:00
"execution_count": 1,
2025-12-21 14:34:18 +07:00
"id": "912ed572-1658-406b-976c-cd6de2d4e89e",
"metadata": {
2026-03-03 09:19:27 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
"outputs": [
{
2026-02-26 07:46:22 +00:00
"data": {
"text/html": [
"<script type=\"esms-options\">{\"shimMode\": true}</script><style>*[data-root-id],\n",
"*[data-root-id] > * {\n",
" box-sizing: border-box;\n",
" font-family: var(--jp-ui-font-family);\n",
" font-size: var(--jp-ui-font-size1);\n",
" color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
"}\n",
"\n",
"/* Override VSCode background color */\n",
".cell-output-ipywidget-background:has(\n",
" > .cell-output-ipywidget-background > .lm-Widget > *[data-root-id]\n",
" ),\n",
".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
" background-color: transparent !important;\n",
"}\n",
"</style>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n const py_version = '3.6.0'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = false;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = enc
"application/vnd.holoviews_load.v0+json": ""
2026-02-26 07:46:22 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"scrip
"application/vnd.holoviews_load.v0+json": ""
2026-02-26 07:46:22 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
2026-03-03 08:58:45 +00:00
"<div id='p1002'>\n",
2026-03-03 09:32:11 +00:00
" <div id=\"d1e24ea7-727d-4df6-823e-c9db6a0f64b4\" data-root-id=\"p1002\" style=\"display: contents;\"></div>\n",
2026-02-26 07:46:22 +00:00
"</div>\n",
"<script type=\"application/javascript\">(function(root) {\n",
2026-03-03 09:32:11 +00:00
" var docs_json = {\"bfd3bf85-24bc-4b01-aa55-72224928ece3\":{\"version\":\"3.6.0\",\"title\":\"Bokeh Application\",\"roots\":[{\"type\":\"object\",\"name\":\"panel.models.browser.BrowserInfo\",\"id\":\"p1002\"},{\"type\":\"object\",\"name\":\"panel.models.comm_manager.CommManager\",\"id\":\"p1003\",\"attributes\":{\"plot_id\":\"p1002\",\"comm_id\":\"7517c5cb391a4fba8c300abbc4eebafe\",\"client_comm_id\":\"fbf822af69474be0a290c6e3504f5dbf\"}}],\"defs\":[{\"type\":\"model\",\"name\":\"ReactiveHTML1\"},{\"type\":\"model\",\"name\":\"FlexBox1\",\"properties\":[{\"name\":\"align_content\",\"kind\":\"Any\",\"default\":\"flex-start\"},{\"name\":\"align_items\",\"kind\":\"Any\",\"default\":\"flex-start\"},{\"name\":\"flex_direction\",\"kind\":\"Any\",\"default\":\"row\"},{\"name\":\"flex_wrap\",\"kind\":\"Any\",\"default\":\"wrap\"},{\"name\":\"gap\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"justify_content\",\"kind\":\"Any\",\"default\":\"flex-start\"}]},{\"type\":\"model\",\"name\":\"FloatPanel1\",\"properties\":[{\"name\":\"config\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"contained\",\"kind\":\"Any\",\"default\":true},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"right-top\"},{\"name\":\"offsetx\",\"kind\":\"Any\",\"default\":null},{\"name\":\"offsety\",\"kind\":\"Any\",\"default\":null},{\"name\":\"theme\",\"kind\":\"Any\",\"default\":\"primary\"},{\"name\":\"status\",\"kind\":\"Any\",\"default\":\"normalized\"}]},{\"type\":\"model\",\"name\":\"GridStack1\",\"properties\":[{\"name\":\"mode\",\"kind\":\"Any\",\"default\":\"warn\"},{\"name\":\"ncols\",\"kind\":\"Any\",\"default\":null},{\"name\":\"nrows\",\"kind\":\"Any\",\"default\":null},{\"name\":\"allow_resize\",\"kind\":\"Any\",\"default\":true},{\"name\":\"allow_drag\",\"kind\":\"Any\",\"default\":true},{\"name\":\"state\",\"kind\":\"Any\",\"default\":[]}]},{\"type\":\"model\",\"name\":\"drag1\",\"properties\":[{\"name\":\"slider_width\",\"kind\":\"Any\",\"default\":5},{\"name\":\"slider_color\",\"kind\":\"Any\",\"default\":\"black\"},{\"name\":\"value\",\"kind\":\"Any\",\"default\":50}]},{\"type\":\"model\",\"name\":\"click1\",\"properties\":[{\"name\":\"terminal_output\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"debug_name\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"clears\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"FastWrapper1\",\"properties\":[{\"name\":\"object\",\"kind\":\"Any\",\"default\":null},{\"name\":\"style\",\"kind\":\"Any\",\"default\":null}]},{\"type\":\"model\",\"name\":\"NotificationAreaBase1\",\"properties\":[{\"name\":\"js_events\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"bottom-right\"},{\"name\":\"_clear\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"NotificationArea1\",\"properties\":[{\"name\":\"js_events\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"notifications\",\"kind\":\"Any\",\"default\":[]},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"bottom-right\"},{\"name\":\"_clear\",\"kind\":\"Any\",\"default\":0},{\"name\":\"types\",\"kind\":\"Any\",\"default\":[{\"type\":\"map\",\"entries\":[[\"type\",\"warning\"],[\"background\",\"#ffc107\"],[\"icon\",{\"type\":\"map\",\"entries\":[[\"className\",\"fas fa-exclamation-triangle\"],[\"tagName\",\"i\"],[\"color\",\"white\"]]}]]},{\"type\":\"map\",\"entries\":[[\"type\",\"info\"],[\"background\",\"#007bff\"],[\"icon\",{\"type\":\"map\",\"entries\":[[\"className\",\"fas fa-info-circle\"],[\"tagName\",\"i\"],[\"color\",\"white\"]]}]]}]}]},{\"type\":\"model\",\"name\":\"Notification\",\"properties\":[{\"name\":\"background\",\"kind\":\"Any\",\"default\":null},{\"name\":\"duration\",\"kind\":\"Any\",\"default\":3000},{\"name\":\"icon\",\"kind\":\"Any\",\"default\":null},{\"name\":\"message\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"notification_type\",\"kind\":\"Any\",\"default\":null},{\"name\":\"_destroyed\",\"kind\":\"Any\",\"default\":false}]},{\"type\":\"model\",\"name\":\"TemplateActions1\",\"properties\":[{\"name\
" var render_items = [{\"docid\":\"bfd3bf85-24bc-4b01-aa55-72224928ece3\",\"roots\":{\"p1002\":\"d1e24ea7-727d-4df6-823e-c9db6a0f64b4\"},\"root_ids\":[\"p1002\"]}];\n",
2026-02-26 07:46:22 +00:00
" var docs = Object.values(docs_json)\n",
" if (!docs) {\n",
" return\n",
" }\n",
" const py_version = docs[0].version.replace('rc', '-rc.').replace('.dev', '-dev.')\n",
" async function embed_document(root) {\n",
" var Bokeh = get_bokeh(root)\n",
" await Bokeh.embed.embed_items_notebook(docs_json, render_items);\n",
" for (const render_item of render_items) {\n",
" for (const root_id of render_item.root_ids) {\n",
"\tconst id_el = document.getElementById(root_id)\n",
"\tif (id_el.children.length && id_el.children[0].hasAttribute('data-root-id')) {\n",
"\t const root_el = id_el.children[0]\n",
"\t root_el.id = root_el.id + '-rendered'\n",
"\t for (const child of root_el.children) {\n",
" // Ensure JupyterLab does not capture keyboard shortcuts\n",
" // see: https://jupyterlab.readthedocs.io/en/4.1.x/extension/notebook.html#keyboard-interaction-model\n",
"\t child.setAttribute('data-lm-suppress-shortcuts', 'true')\n",
"\t }\n",
"\t}\n",
" }\n",
" }\n",
" }\n",
" function get_bokeh(root) {\n",
" if (root.Bokeh === undefined) {\n",
" return null\n",
" } else if (root.Bokeh.version !== py_version) {\n",
" if (root.Bokeh.versions === undefined || !root.Bokeh.versions.has(py_version)) {\n",
"\treturn null\n",
" }\n",
" return root.Bokeh.versions.get(py_version);\n",
" } else if (root.Bokeh.version === py_version) {\n",
" return root.Bokeh\n",
" }\n",
" return null\n",
" }\n",
" function is_loaded(root) {\n",
" var Bokeh = get_bokeh(root)\n",
" return (Bokeh != null && Bokeh.Panel !== undefined)\n",
" }\n",
" if (is_loaded(root)) {\n",
" embed_document(root);\n",
" } else {\n",
" var attempts = 0;\n",
" var timer = setInterval(function(root) {\n",
" if (is_loaded(root)) {\n",
" clearInterval(timer);\n",
" embed_document(root);\n",
" } else if (document.readyState == \"complete\") {\n",
" attempts++;\n",
" if (attempts > 200) {\n",
" clearInterval(timer);\n",
"\t var Bokeh = get_bokeh(root)\n",
"\t if (Bokeh == null || Bokeh.Panel == null) {\n",
" console.warn(\"Panel: ERROR: Unable to run Panel code because Bokeh or Panel library is missing\");\n",
"\t } else {\n",
"\t console.warn(\"Panel: WARNING: Attempting to render but not all required libraries could be resolved.\")\n",
"\t embed_document(root)\n",
"\t }\n",
" }\n",
" }\n",
" }, 25, root)\n",
" }\n",
"})(window);</script>"
]
},
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
2026-03-03 08:58:45 +00:00
"id": "p1002"
2026-02-26 07:46:22 +00:00
}
},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<script type=\"esms-options\">{\"shimMode\": true}</script><style>*[data-root-id],\n",
"*[data-root-id] > * {\n",
" box-sizing: border-box;\n",
" font-family: var(--jp-ui-font-family);\n",
" font-size: var(--jp-ui-font-size1);\n",
" color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
"}\n",
"\n",
"/* Override VSCode background color */\n",
".cell-output-ipywidget-background:has(\n",
" > .cell-output-ipywidget-background > .lm-Widget > *[data-root-id]\n",
" ),\n",
".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
" background-color: transparent !important;\n",
"}\n",
"</style>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = false;\n const py_version = '3.6.0'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = true;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = enc
"application/vnd.holoviews_load.v0+json": ""
2026-02-26 07:46:22 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"scrip
"application/vnd.holoviews_load.v0+json": ""
2026-03-03 08:58:45 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<script type=\"esms-options\">{\"shimMode\": true}</script><style>*[data-root-id],\n",
"*[data-root-id] > * {\n",
" box-sizing: border-box;\n",
" font-family: var(--jp-ui-font-family);\n",
" font-size: var(--jp-ui-font-size1);\n",
" color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
"}\n",
"\n",
"/* Override VSCode background color */\n",
".cell-output-ipywidget-background:has(\n",
" > .cell-output-ipywidget-background > .lm-Widget > *[data-root-id]\n",
" ),\n",
".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
" background-color: transparent !important;\n",
"}\n",
"</style>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = false;\n const py_version = '3.6.0'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = true;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = enc
"application/vnd.holoviews_load.v0+json": ""
2026-03-03 08:30:10 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"scrip
"application/vnd.holoviews_load.v0+json": ""
2026-03-03 09:19:27 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<script type=\"esms-options\">{\"shimMode\": true}</script><style>*[data-root-id],\n",
"*[data-root-id] > * {\n",
2026-03-03 08:30:10 +00:00
" box-sizing: border-box;\n",
" font-family: var(--jp-ui-font-family);\n",
" font-size: var(--jp-ui-font-size1);\n",
" color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
"}\n",
"\n",
2026-03-03 08:58:45 +00:00
"/* Override VSCode background color */\n",
".cell-output-ipywidget-background:has(\n",
" > .cell-output-ipywidget-background > .lm-Widget > *[data-root-id]\n",
" ),\n",
".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
" background-color: transparent !important;\n",
"}\n",
"</style>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = false;\n const py_version = '3.6.0'.replace('rc', '-rc.').replace('.dev', '-dev.');\n const reloading = true;\n const Bokeh = root.Bokeh;\n\n // Set a timeout for this load but only if we are not already initializing\n if (typeof (root._bokeh_timeout) === \"undefined\" || (force || !root._bokeh_is_initializing)) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n // Don't load bokeh if it is still initializing\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n } else if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n // There is nothing to load\n run_callbacks();\n return null;\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error(e) {\n const src_el = e.srcElement\n console.error(\"failed to load \" + (src_el.href || src_el.src));\n }\n\n const skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {}, 'shim': {}});\n root._bokeh_is_loading = css_urls.length + 0;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n const existing_stylesheets = []\n const links = document.getElementsByTagName('link')\n for (let i = 0; i < links.length; i++) {\n const link = links[i]\n if (link.href != null) {\n existing_stylesheets.push(link.href)\n }\n }\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const escaped = encodeURI(url)\n if (existing_stylesheets.indexOf(escaped) !== -1) {\n on_load()\n continue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } var existing_scripts = []\n const scripts = document.getElementsByTagName('script')\n for (let i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n existing_scripts.push(script.src)\n }\n }\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const escaped = encodeURI(url)\n if (skip.indexOf(escaped) !== -1 || existing_scripts.indexOf(escaped) !== -1) {\n if (!window.requirejs) {\n on_load();\n }\n continue;\n }\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (let i = 0; i < js_modules.length; i++) {\n const url = js_modules[i];\n const escaped = enc
"application/vnd.holoviews_load.v0+json": ""
2026-03-03 08:30:10 +00:00
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-03-03 19:22:26 +07:00
"application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"scrip
"application/vnd.holoviews_load.v0+json": ""
2026-03-01 10:29:42 +00:00
},
"metadata": {},
"output_type": "display_data"
},
2026-02-26 07:46:22 +00:00
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ All modules loaded successfully\n",
2026-03-03 09:32:11 +00:00
"CPU times: user 18.6 s, sys: 3.6 s, total: 22.2 s\n",
"Wall time: 13.6 s\n"
2025-12-21 14:34:18 +07:00
]
}
],
"source": [
"%%time\n",
"%matplotlib inline\n",
"\n",
"import importlib\n",
"import new_import_ODC \n",
"\n",
"importlib.reload(new_import_ODC)\n",
"\n",
"from new_import_ODC import *\n",
"\n",
"print(\"✅ All modules loaded successfully\")"
]
},
{
"cell_type": "code",
2026-03-03 08:58:45 +00:00
"execution_count": 2,
2025-12-21 14:34:18 +07:00
"id": "d824dc4f-994b-4d1c-8d24-ce6674da141c",
"metadata": {
2026-03-03 08:30:10 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
2026-03-03 08:30:10 +00:00
"✅ AWS credentials found in environment variables\n",
2025-12-21 14:34:18 +07:00
"✅ Dask cluster initialized\n",
2026-03-03 09:32:11 +00:00
" Cluster: LocalCluster(7d4f419a, 'tcp://127.0.0.1:45935', workers=4, threads=40, memory=128.00 GiB)\n",
2026-02-26 08:13:05 +00:00
"✅ S3 access configured (requester_pays=True)\n",
2025-12-21 14:34:18 +07:00
"✅ Datacube connected (metadata only)\n",
"\n",
"======================================================================\n",
2026-03-03 09:32:11 +00:00
"CPU times: user 1.14 s, sys: 66.3 ms, total: 1.2 s\n",
"Wall time: 6.14 s\n"
2025-12-21 14:34:18 +07:00
]
}
],
"source": [
"%%time\n",
"import os\n",
"import sys\n",
"\n",
2026-03-03 15:10:49 +07:00
"# =====================================================================\n",
"# AWS CREDENTIALS SETUP\n",
"# =====================================================================\n",
"# Ưu tiên load theo thứ tự:\n",
"# 1. Environment variables (CSIRO server - tự động)\n",
"# 2. File .env trong thư mục dự án (local machine)\n",
"# 3. ~/.aws/credentials (AWS CLI config)\n",
"# =====================================================================\n",
"\n",
"def load_env_file(path):\n",
" \"\"\"Load key=value từ file .env\"\"\"\n",
" loaded = {}\n",
" try:\n",
" with open(path) as f:\n",
" for line in f:\n",
" line = line.strip()\n",
" if line and not line.startswith('#') and '=' in line:\n",
" k, _, v = line.partition('=')\n",
" os.environ.setdefault(k.strip(), v.strip())\n",
" loaded[k.strip()] = True\n",
" except Exception:\n",
" pass\n",
" return loaded\n",
"\n",
"# 1. Thử load từ .env local\n",
"env_file = os.path.join(os.path.dirname(os.path.abspath(\".\")), \n",
" os.getcwd(), \".env\")\n",
"env_file2 = \".env\"\n",
"for ef in [env_file2, os.path.expanduser(\"~/.env\")]:\n",
" if os.path.exists(ef):\n",
" loaded = load_env_file(ef)\n",
" if loaded:\n",
" print(f\"✅ AWS credentials loaded from {ef}\")\n",
" break\n",
"else:\n",
" # 2. Kiểm tra env vars đã có chưa\n",
" if os.environ.get(\"AWS_ACCESS_KEY_ID\"):\n",
" print(\"✅ AWS credentials found in environment variables\")\n",
" else:\n",
" # 3. Thử import qua boto3 (sẽ dùng ~/.aws/credentials)\n",
" try:\n",
" import boto3\n",
" identity = boto3.client(\"sts\").get_caller_identity()\n",
" print(f\"✅ AWS credentials loaded from ~/.aws/credentials\")\n",
" print(f\" Account: {identity['Account']}\")\n",
" except Exception:\n",
" print(\"⚠️ WARNING: No AWS credentials found!\")\n",
" print(\" → Tạo file .env từ template: cp .env.template .env\")\n",
" print(\" → Điền AWS_ACCESS_KEY_ID và AWS_SECRET_ACCESS_KEY\")\n",
" print(\" → Hoặc chạy: aws configure\")\n",
"\n",
"# Explicitly set GDAL/rasterio env vars từ AWS creds nếu có\n",
"if os.environ.get(\"AWS_ACCESS_KEY_ID\"):\n",
" os.environ[\"AWS_REQUEST_PAYER\"] = \"requester\"\n",
" os.environ.setdefault(\"AWS_DEFAULT_REGION\", \"ap-southeast-2\")\n",
" os.environ[\"GDAL_DISABLE_READDIR_ON_OPEN\"] = \"EMPTY_DIR\"\n",
" os.environ[\"CPL_VSIL_CURL_ALLOWED_EXTENSIONS\"] = \"TIF,tif,jp2,JP2\"\n",
2025-12-21 14:34:18 +07:00
"\n",
"# Cấu hình Dask local\n",
"from dask.distributed import Client, LocalCluster\n",
"\n",
2026-03-03 15:10:49 +07:00
"try:\n",
" cluster = LocalCluster(n_workers=4)\n",
" client = Client(cluster)\n",
" print(\"✅ Dask cluster initialized\")\n",
" print(f\" Cluster: {cluster}\")\n",
"except Exception as e:\n",
" print(f\"⚠️ Dask cluster error: {e}\")\n",
" client = None\n",
" cluster = None\n",
2025-12-21 14:34:18 +07:00
"\n",
2026-03-03 15:10:49 +07:00
"# Cấu hình S3 access cho rasterio/GDAL\n",
"try:\n",
" configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)\n",
" print(\"✅ S3 access configured (requester_pays=True)\")\n",
"except Exception as e:\n",
" print(f\"⚠️ S3 config warning: {e}\")\n",
2026-02-26 14:48:37 +07:00
"\n",
2026-03-03 15:10:49 +07:00
"# Khai báo Datacube\n",
2025-12-21 14:34:18 +07:00
"import datacube\n",
"try:\n",
" dc = datacube.Datacube()\n",
" print(\"✅ Datacube connected (metadata only)\")\n",
"except Exception as e:\n",
" print(f\"⚠️ Datacube connection not critical: {e}\")\n",
" dc = None\n",
"\n",
2026-02-26 14:48:37 +07:00
"print(\"\\n\" + \"=\"*70)\n"
2025-12-21 14:34:18 +07:00
]
},
{
"cell_type": "code",
2026-03-03 08:58:45 +00:00
"execution_count": 3,
2025-12-21 14:34:18 +07:00
"id": "1e113730",
2026-03-03 08:30:10 +00:00
"metadata": {
"jupyter": {
"source_hidden": true
}
},
2025-12-21 14:34:18 +07:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"GETTING SENTINEL-2 SCENE METADATA\n",
"======================================================================\n",
"\n",
"[1] Loading metadata from datacube...\n",
2026-02-26 07:46:22 +00:00
" ✅ Found 180337 scenes\n",
2025-12-21 14:34:18 +07:00
"\n",
2026-02-26 07:46:22 +00:00
"[2] Selected scene: S2B_56MNC_20230525_0_L2A\n",
" Date: 2023-05-25 00:17:32.223000+00:00\n",
2025-12-21 14:34:18 +07:00
"\n",
"[3] Available bands:\n",
2026-02-26 07:46:22 +00:00
" - aot: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - nir: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - red: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - scl: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - wvp: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - blue: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - green: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - nir08: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - nir09: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - swir16: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - swir22: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - coastal: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - rededge1: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - rededge2: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
" - rededge3: s3://sentinel-cogs/sentinel-s2-l2a-cogs/56/M/NC/2023/5/S2B_56MNC_20230525_0_L2A/\n",
2025-12-21 14:34:18 +07:00
"======================================================================\n",
2026-03-03 09:32:11 +00:00
"CPU times: user 1min 26s, sys: 9.98 s, total: 1min 35s\n",
"Wall time: 4min 9s\n"
2025-12-21 14:34:18 +07:00
]
}
],
"source": [
"%%time\n",
"# 🔧 Get Sentinel-2 scene metadata from datacube\n",
"print(\"=\"*70)\n",
"print(\"GETTING SENTINEL-2 SCENE METADATA\")\n",
"print(\"=\"*70)\n",
"\n",
"date_range = (\"2023-03-01\", \"2023-12-31\")\n",
"longtitude_range = (105.5, 106.4)\n",
"latitude_range = (9.2, 10.0)\n",
"\n",
"try:\n",
" print(f\"\\n[1] Loading metadata from datacube...\")\n",
" datasets = list(dc.find_datasets(product='s2_l2a', time=date_range))\n",
" print(f\" ✅ Found {len(datasets)} scenes\")\n",
" \n",
" if datasets:\n",
" selected = datasets[0]\n",
" print(f\"\\n[2] Selected scene: {selected.metadata.label}\")\n",
" scene_datetime = selected.time.begin if hasattr(selected.time, 'begin') else selected.time\n",
" print(f\" Date: {scene_datetime}\")\n",
" \n",
" # Display measurement paths\n",
" print(f\"\\n[3] Available bands:\")\n",
" for name, measurement in selected.measurements.items():\n",
" print(f\" - {name}: {measurement['path'][:80]}\")\n",
" \n",
"except Exception as e:\n",
" print(f\"❌ Error: {e}\")\n",
" import traceback\n",
" traceback.print_exc()\n",
"\n",
"print(\"=\"*70)"
]
},
{
"cell_type": "code",
2026-03-03 08:58:45 +00:00
"execution_count": 4,
2025-12-21 14:34:18 +07:00
"id": "3cd69645",
2026-03-03 08:30:10 +00:00
"metadata": {
"jupyter": {
"source_hidden": true
}
},
2025-12-21 14:34:18 +07:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"CHECKING FOR CACHED DATASET\n",
"======================================================================\n",
"\n",
"⏳ Cache file not found: dataset_cache/sentinel2_timeseries_40scenes.nc\n",
" Will download from S3 and save cache\n",
" (Next run will use cache automatically)\n",
"======================================================================\n",
2026-03-03 09:32:11 +00:00
"CPU times: user 0 ns, sys: 1.12 ms, total: 1.12 ms\n",
"Wall time: 7.48 ms\n"
2025-12-21 14:34:18 +07:00
]
}
],
"source": [
"%%time\n",
"# 🔍 CHECK IF DATASET CACHE EXISTS (Skip download if available)\n",
"print(\"=\"*70)\n",
"print(\"CHECKING FOR CACHED DATASET\")\n",
"print(\"=\"*70)\n",
"\n",
"import os\n",
"import xarray as xr\n",
"\n",
"cache_dir = \"dataset_cache\"\n",
"cache_file = f\"{cache_dir}/sentinel2_timeseries_40scenes.nc\"\n",
"\n",
"use_cache = False\n",
"\n",
"if os.path.exists(cache_file):\n",
" print(f\"\\n✅ Cache file found: {cache_file}\")\n",
" \n",
" # Get file info\n",
" file_size_gb = os.path.getsize(cache_file) / (1024**3)\n",
" print(f\" File size: {file_size_gb:.2f} GB\")\n",
" \n",
" # Try to load\n",
" try:\n",
" print(f\"\\n🔄 Loading dataset from cache...\")\n",
" data = xr.open_dataset(cache_file)\n",
" \n",
" print(f\"✅ Dataset loaded from cache!\")\n",
" print(f\" Total scenes: {len(data['time'])}\")\n",
" print(f\" Variables: {len(data.data_vars)}\")\n",
" print(f\" Dimensions: {dict(data.dims)}\")\n",
" print(f\"\\n ⏭️ Skipping S3 download (using cached data)\")\n",
" \n",
" use_cache = True\n",
" \n",
" except Exception as e:\n",
" print(f\"❌ Error loading cache: {e}\")\n",
" print(f\" Will download fresh data from S3\")\n",
" use_cache = False\n",
"else:\n",
" print(f\"\\n⏳ Cache file not found: {cache_file}\")\n",
" print(f\" Will download from S3 and save cache\")\n",
" print(f\" (Next run will use cache automatically)\")\n",
"\n",
"print(\"=\"*70)"
]
},
{
"cell_type": "code",
2026-03-03 19:22:26 +07:00
"execution_count": null,
2025-12-21 14:34:18 +07:00
"id": "435f9f78-a9a4-4226-86ca-d4bec42d454e",
"metadata": {
2026-03-03 08:58:45 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
2026-03-03 09:32:11 +00:00
"LOADING SENTINEL-2 DATA VIA PLANETARY COMPUTER (cloud-filtered)\n",
2025-12-21 14:34:18 +07:00
"======================================================================\n",
"\n",
2026-03-03 09:32:11 +00:00
"[1] Importing Planetary Computer libraries...\n",
" ✅ pystac_client, planetary_computer, odc.stac ready\n",
2025-12-21 14:34:18 +07:00
"\n",
2026-03-03 09:32:11 +00:00
"[2] Searching Sentinel-2 L2A on Planetary Computer...\n",
" Found 79 scenes with cloud ≤ 30%\n",
2026-03-03 08:58:45 +00:00
"\n",
" Selected 40 least-cloudy scenes:\n",
" 2023-03-08 cloud=0.1%\n",
2026-03-03 09:32:11 +00:00
" 2023-03-08 cloud=0.1%\n",
" 2023-03-08 cloud=0.5%\n",
2026-03-03 08:58:45 +00:00
" 2023-03-08 cloud=0.5%\n",
" 2023-03-11 cloud=14.6%\n",
" ... (35 more)\n",
"\n",
2026-03-03 09:32:11 +00:00
"[3] Loading 40 scenes via odc.stac.load()...\n",
" Bands: ['blue', 'green', 'red', 'nir08', 'swir16', 'swir22', 'coastal', 'rededge1', 'rededge2', 'rededge3', 'scl']\n",
"❌ Error: No such band/alias: nir08\n",
"======================================================================\n",
"CPU times: user 622 ms, sys: 19.2 ms, total: 641 ms\n",
"Wall time: 2.84 s\n"
2026-03-03 09:19:27 +00:00
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
2026-03-03 09:32:11 +00:00
"Traceback (most recent call last):\n",
" File \"<timed exec>\", line 84, in <module>\n",
" File \"/env/lib/python3.12/site-packages/odc/stac/_stac_load.py\", line 383, in load\n",
" bands_to_load = collection.resolve_bands(bands)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/env/lib/python3.12/site-packages/odc/stac/model.py\", line 116, in resolve_bands\n",
" return {\n",
" ^\n",
" File \"/env/lib/python3.12/site-packages/odc/stac/model.py\", line 118, in <genexpr>\n",
" for band, k in ((band, self.band_key(band)) for band in bands)\n",
" ^^^^^^^^^^^^^^^^^^^\n",
" File \"/env/lib/python3.12/site-packages/odc/stac/model.py\", line 144, in band_key\n",
" raise ValueError(f\"No such band/alias: {band}\")\n",
"ValueError: No such band/alias: nir08\n"
2025-12-21 14:34:18 +07:00
]
}
],
"source": [
"%%time\n",
2026-03-03 16:24:35 +07:00
"# 💾 LOAD SENTINEL-2 DATA VIA MICROSOFT PLANETARY COMPUTER\n",
2025-12-21 14:34:18 +07:00
"print(\"=\"*70)\n",
2026-03-03 16:24:35 +07:00
"print(\"LOADING SENTINEL-2 DATA VIA PLANETARY COMPUTER (cloud-filtered)\")\n",
2025-12-21 14:34:18 +07:00
"print(\"=\"*70)\n",
"\n",
2026-03-03 15:35:48 +07:00
"import xarray as xr\n",
"import numpy as np\n",
2026-03-03 15:45:26 +07:00
"import pandas as pd\n",
2026-03-03 15:35:48 +07:00
"import os\n",
"\n",
2026-03-03 16:24:35 +07:00
"MAX_SCENES = 40 # Lấy tối đa 40 cảnh ít mây nhất\n",
"CLOUD_MAX = 30 # Bỏ cảnh có >30% mây\n",
"CLOUD_VALS = [3, 8, 9, 10] # SCL: shadow, med cloud, high cloud, cirrus\n",
2026-03-03 16:05:31 +07:00
"\n",
2026-03-03 15:35:48 +07:00
"try:\n",
2026-03-03 16:24:35 +07:00
" # ===== CHECK CACHE =====\n",
2026-03-03 15:10:49 +07:00
" if use_cache and 'data' in dir() and data is not None:\n",
" print(f\"\\n✅ Using cached dataset - skipping download!\")\n",
" print(f\" Variables: {len(data.data_vars)}\")\n",
" print(f\" Shape: {data.dims}\")\n",
2026-02-26 15:33:36 +07:00
" display(data)\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 15:10:49 +07:00
" else:\n",
2026-03-03 16:24:35 +07:00
" # ===== STEP 1: IMPORT PLANETARY COMPUTER LIBRARIES =====\n",
" print(f\"\\n[1] Importing Planetary Computer libraries...\")\n",
" import pystac_client\n",
" import planetary_computer\n",
" from odc.stac import load as odc_load\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" print(f\" ✅ pystac_client, planetary_computer, odc.stac ready\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" # ===== STEP 2: SEARCH SENTINEL-2 ON PLANETARY COMPUTER =====\n",
" print(f\"\\n[2] Searching Sentinel-2 L2A on Planetary Computer...\")\n",
" catalog = pystac_client.Client.open(\n",
" \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n",
" modifier=planetary_computer.sign_inplace,\n",
" )\n",
2026-03-03 15:51:43 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" bbox = [longtitude_range[0], latitude_range[0],\n",
" longtitude_range[1], latitude_range[1]]\n",
2026-03-03 15:51:43 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" search = catalog.search(\n",
" collections=[\"sentinel-2-l2a\"],\n",
" bbox=bbox,\n",
" datetime=f\"{date_range[0]}/{date_range[1]}\",\n",
" query={\"eo:cloud_cover\": {\"lt\": CLOUD_MAX}},\n",
" )\n",
2026-03-03 15:51:43 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" items = list(search.items())\n",
" print(f\" Found {len(items)} scenes with cloud ≤ {CLOUD_MAX}%\")\n",
2026-03-03 15:51:43 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" if len(items) == 0:\n",
" # Relax threshold\n",
2026-03-03 15:51:43 +07:00
" CLOUD_MAX2 = 60\n",
2026-03-03 16:24:35 +07:00
" search2 = catalog.search(\n",
" collections=[\"sentinel-2-l2a\"],\n",
" bbox=bbox,\n",
" datetime=f\"{date_range[0]}/{date_range[1]}\",\n",
" query={\"eo:cloud_cover\": {\"lt\": CLOUD_MAX2}},\n",
" )\n",
" items = list(search2.items())\n",
" print(f\" ⚠️ Relaxed to ≤{CLOUD_MAX2}%: {len(items)} scenes found\")\n",
2026-03-03 16:05:31 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" # Sort by cloud cover, take MAX_SCENES least-cloudy\n",
" items.sort(key=lambda x: x.properties.get(\"eo:cloud_cover\", 100))\n",
" selected_items = items[:MAX_SCENES]\n",
" # Sort chronologically for loading\n",
" selected_items.sort(key=lambda x: x.datetime)\n",
2026-03-03 15:51:43 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" print(f\"\\n Selected {len(selected_items)} least-cloudy scenes:\")\n",
" for item in selected_items[:5]:\n",
" cc = item.properties.get(\"eo:cloud_cover\", \"?\")\n",
" print(f\" {str(item.datetime)[:10]} cloud={cc:.1f}%\")\n",
" if len(selected_items) > 5:\n",
" print(f\" ... ({len(selected_items)-5} more)\")\n",
2026-03-03 15:51:43 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" # ===== STEP 3: LOAD DATA VIA ODC.STAC =====\n",
2026-03-03 19:22:26 +07:00
" # PC Sentinel-2 L2A asset keys:\n",
" # Common names: blue, green, red, nir (B08), swir16, swir22, coastal\n",
" # Asset keys for red-edge (no standard common name): B05, B06, B07\n",
" # SCL asset key: SCL (uppercase)\n",
" bands_to_load = ['blue', 'green', 'red', 'nir', 'swir16', 'swir22',\n",
" 'coastal', 'B05', 'B06', 'B07', 'SCL']\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" print(f\"\\n[3] Loading {len(selected_items)} scenes via odc.stac.load()...\")\n",
2026-03-03 19:22:26 +07:00
" print(f\" Bands: {bands_to_load}\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" data_lazy = odc_load(\n",
" selected_items,\n",
2026-03-03 19:22:26 +07:00
" bands=bands_to_load,\n",
2026-03-03 16:24:35 +07:00
" resolution=10,\n",
" crs=\"EPSG:32648\",\n",
" bbox=bbox,\n",
" groupby=\"solar_day\",\n",
" chunks={\"x\": 1024, \"y\": 1024},\n",
" )\n",
2026-03-03 15:35:48 +07:00
" print(f\" Lazy dataset: {dict(data_lazy.dims)}\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 19:22:26 +07:00
" # Rename asset keys → friendly names for downstream code\n",
" rename_map = {}\n",
" if 'B05' in data_lazy: rename_map['B05'] = 'rededge1'\n",
" if 'B06' in data_lazy: rename_map['B06'] = 'rededge2'\n",
" if 'B07' in data_lazy: rename_map['B07'] = 'rededge3'\n",
" if 'SCL' in data_lazy: rename_map['SCL'] = 'scl'\n",
" if rename_map:\n",
" data_lazy = data_lazy.rename(rename_map)\n",
2026-03-03 16:24:35 +07:00
"\n",
2026-03-03 15:51:43 +07:00
" # Size estimate & guard\n",
2026-03-03 15:45:26 +07:00
" n_time = len(data_lazy.time)\n",
" ny = data_lazy.dims.get('y', 0)\n",
" nx = data_lazy.dims.get('x', 0)\n",
2026-03-03 19:22:26 +07:00
" n_bands = len([b for b in bands_to_load if b not in ('SCL',)])\n",
2026-03-03 15:35:48 +07:00
" size_gb = n_time * ny * nx * n_bands * 2 / 1e9\n",
2026-03-03 15:51:43 +07:00
" print(f\" Estimated size: {size_gb:.1f} GB ({n_time} × {ny}×{nx} × {n_bands} bands)\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 15:35:48 +07:00
" if size_gb > 40:\n",
2026-03-03 15:45:26 +07:00
" keep = max(1, int(40 / (size_gb / n_time)))\n",
2026-03-03 15:51:43 +07:00
" print(f\"\\n ⚠️ Too large → reducing to {keep} scenes...\")\n",
2026-03-03 15:45:26 +07:00
" data_lazy = data_lazy.isel(time=slice(0, keep))\n",
" n_time = len(data_lazy.time)\n",
2026-03-03 15:35:48 +07:00
" size_gb = n_time * ny * nx * n_bands * 2 / 1e9\n",
2026-03-03 15:45:26 +07:00
" print(f\" Reduced: {size_gb:.1f} GB ({n_time} scenes)\")\n",
2026-03-03 15:35:48 +07:00
"\n",
2026-03-03 15:51:43 +07:00
" # ===== STEP 4: PIXEL-LEVEL CLOUD MASK (SCL) =====\n",
" print(f\"\\n[4] Applying SCL pixel-level cloud mask...\")\n",
2026-03-03 15:45:26 +07:00
" scl_band = data_lazy['scl']\n",
" good_pixels = ~scl_band.isin(CLOUD_VALS)\n",
2026-03-03 15:35:48 +07:00
" data_masked = data_lazy.where(good_pixels)\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 16:24:35 +07:00
" print(f\"\\n[5] Computing (downloading — may take several minutes)...\")\n",
2026-03-03 15:35:48 +07:00
" data = data_masked.compute()\n",
" print(f\"✅ Data computed! {dict(data.dims)}\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 15:51:43 +07:00
" # ===== STEP 5: SPECTRAL INDICES =====\n",
" print(f\"\\n[6] Calculating spectral indices...\")\n",
2026-03-03 16:05:31 +07:00
" new_vars = {}\n",
2026-02-26 15:54:55 +07:00
"\n",
" if 'nir' in data and 'red' in data:\n",
2026-03-03 15:35:48 +07:00
" nir = data['nir'].astype(float)\n",
" red = data['red'].astype(float)\n",
2026-03-03 16:05:31 +07:00
" new_vars['ndvi'] = ((nir - red) / (nir + red + 1e-8)).astype(np.float32)\n",
2026-03-03 15:10:49 +07:00
" print(f\" ✅ NDVI\")\n",
2026-02-26 15:54:55 +07:00
"\n",
" if 'swir16' in data and 'nir' in data:\n",
" swir = data['swir16'].astype(float)\n",
" nir = data['nir'].astype(float)\n",
2026-03-03 16:05:31 +07:00
" new_vars['ndbi'] = ((swir - nir) / (swir + nir + 1e-8)).astype(np.float32)\n",
" new_vars['ndwi'] = ((nir - swir) / (nir + swir + 1e-8)).astype(np.float32)\n",
2026-03-03 15:35:48 +07:00
" print(f\" ✅ NDBI, NDWI\")\n",
2026-03-03 15:10:49 +07:00
"\n",
2026-02-26 15:54:55 +07:00
" if 'nir' in data and 'red' in data and 'blue' in data:\n",
" nir = data['nir'].astype(float)\n",
" red = data['red'].astype(float)\n",
" blue = data['blue'].astype(float)\n",
2026-03-03 16:05:31 +07:00
" new_vars['evi'] = (2.5 * (nir - red) / (nir + 6*red - 7.5*blue + 1)).astype(np.float32)\n",
2026-03-03 15:10:49 +07:00
" print(f\" ✅ EVI\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 15:51:43 +07:00
" # ===== STEP 6: TEMPORAL AGGREGATES =====\n",
" print(f\"\\n[7] Computing temporal aggregate features...\")\n",
2026-02-26 15:54:55 +07:00
" added = 0\n",
2026-03-03 16:05:31 +07:00
" if 'ndvi' in new_vars:\n",
" ts = new_vars['ndvi']\n",
" new_vars['ndvi_min'] = ts.min(dim='time'); added += 1\n",
" new_vars['ndvi_max'] = ts.max(dim='time'); added += 1\n",
" new_vars['ndvi_mean'] = ts.mean(dim='time'); added += 1\n",
" new_vars['ndvi_std'] = ts.std(dim='time'); added += 1\n",
" new_vars['ndvi_range'] = new_vars['ndvi_max'] - new_vars['ndvi_min']; added += 1\n",
2026-03-03 15:10:49 +07:00
" for idx_name in ['ndbi', 'ndwi', 'evi']:\n",
2026-03-03 16:05:31 +07:00
" if idx_name in new_vars:\n",
" new_vars[f'{idx_name}_mean'] = new_vars[idx_name].mean(dim='time')\n",
" added += 1\n",
2026-02-26 15:54:55 +07:00
" print(f\" ✅ {added} aggregate features added\")\n",
"\n",
2026-03-03 16:24:35 +07:00
" # Merge into dataset\n",
2026-03-03 16:05:31 +07:00
" data = data.assign(new_vars)\n",
"\n",
2026-03-03 15:51:43 +07:00
" # ===== STEP 7: SAVE CACHE =====\n",
" print(f\"\\n[8] Saving cache: {cache_file}\")\n",
2025-12-21 14:34:18 +07:00
" try:\n",
2026-02-26 15:15:37 +07:00
" os.makedirs(cache_dir, exist_ok=True)\n",
2025-12-21 14:34:18 +07:00
" data.to_netcdf(cache_file, engine='netcdf4')\n",
2026-03-03 15:35:48 +07:00
" size_saved = os.path.getsize(cache_file) / 1e9\n",
" print(f\" ✅ Cached ({size_saved:.2f} GB)\")\n",
2025-12-21 14:34:18 +07:00
" except Exception as e:\n",
2026-02-26 15:54:55 +07:00
" print(f\" ⚠️ Cache save failed: {e}\")\n",
"\n",
" print(f\"\\n{'='*70}\")\n",
" print(f\"✅ Dataset ready!\")\n",
2026-03-03 16:24:35 +07:00
" print(f\" Source : Microsoft Planetary Computer\")\n",
2026-02-26 15:54:55 +07:00
" print(f\" Scenes : {len(data.time)}\")\n",
" print(f\" Variables : {len(data.data_vars)}\")\n",
" print(f\" Spatial : {data.dims.get('x',0)} × {data.dims.get('y',0)} px @ 10m\")\n",
" print(f\"{'='*70}\")\n",
2025-12-21 14:34:18 +07:00
" display(data)\n",
2026-02-26 15:54:55 +07:00
"\n",
2026-03-03 15:35:48 +07:00
" # ===== EXTRACT NDVI =====\n",
2025-12-21 14:34:18 +07:00
" if 'ndvi_mean' in data.data_vars:\n",
" ndvi = data['ndvi_mean']\n",
2026-03-03 15:10:49 +07:00
" print(f\"\\n✅ NDVI (mean) shape: {ndvi.shape}\")\n",
2025-12-21 14:34:18 +07:00
" elif 'ndvi' in data.data_vars:\n",
" ndvi = data['ndvi'].isel(time=0)\n",
2026-03-03 15:10:49 +07:00
" print(f\"\\n✅ NDVI (t=0) shape: {ndvi.shape}\")\n",
2025-12-21 14:34:18 +07:00
" else:\n",
" ndvi = None\n",
2026-03-03 15:10:49 +07:00
" print(f\"\\n❌ NDVI not found in dataset\")\n",
2026-02-26 15:54:55 +07:00
"\n",
2025-12-21 14:34:18 +07:00
"except Exception as e:\n",
" print(f\"❌ Error: {e}\")\n",
" import traceback\n",
" traceback.print_exc()\n",
" data = None\n",
" ndvi = None\n",
"\n",
2026-02-26 15:33:36 +07:00
"print(\"=\"*70)\n"
2025-12-21 14:34:18 +07:00
]
},
{
"cell_type": "code",
2026-03-03 09:32:11 +00:00
"execution_count": 6,
2025-12-21 14:34:18 +07:00
"id": "d2585562-88aa-4c7d-bf70-1f6affcf65d4",
"metadata": {
2026-03-03 08:30:10 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
2026-03-03 09:32:11 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"TRAINING DATA SETUP\n",
"======================================================================\n",
"\n",
2026-03-03 20:36:24 +07:00
"[1] Loading training data: train/ST_training_data_updated_1130points_new.shp\n",
" ❌ Error: train/ST_training_data_updated_1130points_new.shp: No such file or directory\n",
2026-03-03 09:32:11 +00:00
"\n",
"[2] Label mapping:\n",
" 0: Lua tom\n",
" 1: Lua\n",
" 2: CHN\n",
" 3: CLN\n",
" 4: TS\n",
" 5: Song\n",
" 6: Dat xay dung\n",
" 7: Rung\n",
"\n",
"======================================================================\n"
]
}
],
2025-12-21 14:34:18 +07:00
"source": [
"# 🎯 LOAD TRAINING DATA & EXTRACT FEATURES\n",
"print(\"=\"*70)\n",
"print(\"TRAINING DATA SETUP\")\n",
"print(\"=\"*70)\n",
"\n",
"# Load training points\n",
2026-03-03 20:36:24 +07:00
"train_path = \"train/ST_training_data_updated_1130points_new.shp\"\n",
2025-12-21 14:34:18 +07:00
"print(f\"\\n[1] Loading training data: {train_path}\")\n",
"\n",
"try:\n",
" train = load_train_data(train_path)\n",
" print(f\" ✅ Loaded {len(train)} training points\")\n",
" print(f\" Columns: {list(train.columns)}\")\n",
" train.head()\n",
"except Exception as e:\n",
" print(f\" ❌ Error: {e}\")\n",
" train = None\n",
"\n",
"# Label mapping\n",
"label_mapping = {\n",
" \"Lua tom\": \"0\",\n",
" \"Lua\": \"1\",\n",
" \"CHN\": \"2\",\n",
" \"CLN\": \"3\",\n",
" \"TS\": \"4\",\n",
" \"Song\": \"5\",\n",
" \"Dat xay dung\": \"6\",\n",
" \"Rung\": \"7\",\n",
"}\n",
"\n",
"print(f\"\\n[2] Label mapping:\")\n",
"for label, code in label_mapping.items():\n",
" print(f\" {code}: {label}\")\n",
"\n",
"print(\"\\n\" + \"=\"*70)"
]
},
{
"cell_type": "code",
2026-03-03 09:32:11 +00:00
"execution_count": 7,
2025-12-21 14:34:18 +07:00
"id": "2e955884-d4af-422d-a8e6-d436199540e0",
"metadata": {
2026-03-03 08:58:45 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
2026-03-03 09:32:11 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"LAND USE CLASSIFICATION TRAINING\n",
"======================================================================\n",
"\n",
"🎯 Mục tiêu: Dự đoán phân loại sử dụng đất (8 lớp)\n",
" - NDVI/NDWI/NDBI/EVI là INPUT FEATURES\n",
" - Sau khi predict xong → có thể hiển thị NDVI map như chỉ số phụ\n",
"======================================================================\n",
"❌ Missing training data or satellite data\n",
"\n",
"======================================================================\n",
"📝 NOTE: Model này dự đoán PHÂN LOẠI SỬ DỤNG ĐẤT (8 lớp)\n",
" NDVI là một trong các features đầu vào, không phải mục tiêu dự đoán\n",
" Sau khi predict → có thể hiển thị NDVI map như chỉ số phụ\n",
"======================================================================\n",
"CPU times: user 850 μs, sys: 109 μs, total: 959 μs\n",
"Wall time: 1.12 ms\n"
]
}
],
2025-12-21 14:34:18 +07:00
"source": [
"%%time\n",
"# 🤖 LAND USE CLASSIFICATION MODEL TRAINING (MỤC TIÊU CHÍNH)\n",
2025-12-21 14:34:18 +07:00
"print(\"=\"*70)\n",
"print(\"LAND USE CLASSIFICATION TRAINING\")\n",
"print(\"=\"*70)\n",
"print(\"\\n🎯 Mục tiêu: Dự đoán phân loại sử dụng đất (8 lớp)\")\n",
"print(\" - NDVI/NDWI/NDBI/EVI là INPUT FEATURES\")\n",
"print(\" - Sau khi predict xong → có thể hiển thị NDVI map như chỉ số phụ\")\n",
2025-12-21 14:34:18 +07:00
"print(\"=\"*70)\n",
"\n",
"if train is not None and data is not None:\n",
" print(\"\\n[1] Extracting MULTIPLE features from satellite data...\")\n",
" print(\" (Sử dụng nhiều spectral indices để cải thiện accuracy)\")\n",
" \n",
2025-12-21 14:34:18 +07:00
" try:\n",
" # Extract features at training point locations\n",
2025-12-21 14:34:18 +07:00
" X = []\n",
" y = []\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Available features from data\n",
" available_features = ['ndvi_mean', 'ndvi_min', 'ndvi_max', 'ndvi_std', 'ndvi_range',\n",
" 'ndwi_mean', 'ndbi_mean', 'evi_mean']\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Check which features are actually available\n",
" features_to_use = [f for f in available_features if f in data.data_vars]\n",
2026-03-03 15:10:49 +07:00
" \n",
" if not features_to_use:\n",
" print(\" ❌ No spectral features found in dataset!\")\n",
" print(\" Available variables:\", list(data.data_vars))\n",
2025-12-21 14:34:18 +07:00
" model = None\n",
" else:\n",
" print(f\" Using {len(features_to_use)} features: {features_to_use}\")\n",
2026-03-03 15:10:49 +07:00
" \n",
" for idx, point in train.iterrows():\n",
" try:\n",
" # Extract all available features at this point\n",
" feature_vec = []\n",
" for feat_name in features_to_use:\n",
" feat_val = float(data[feat_name].sel(\n",
2026-03-03 15:10:49 +07:00
" x=point.geometry.x, \n",
" y=point.geometry.y, \n",
" method='nearest'\n",
" ).values)\n",
" feature_vec.append(feat_val)\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Get label\n",
2026-03-03 15:10:49 +07:00
" label = label_mapping[point.Hientrang]\n",
" \n",
" # Only add if no NaN values\n",
" if not np.isnan(feature_vec).any():\n",
" X.append(feature_vec)\n",
" y.append(int(label))\n",
" except Exception as e:\n",
2026-03-03 15:10:49 +07:00
" # Skip points with errors\n",
" continue\n",
2026-03-03 15:10:49 +07:00
" \n",
" if len(X) > 0:\n",
" X = np.array(X)\n",
" y = np.array(y)\n",
2026-03-03 15:10:49 +07:00
" print(f\" ✅ Extracted {len(X)} samples with {X.shape[1]} features each\")\n",
" \n",
" # Show feature statistics\n",
" print(f\"\\n Feature statistics:\")\n",
" for i, feat_name in enumerate(features_to_use):\n",
" print(f\" {feat_name:15s}: mean={X[:,i].mean():.3f}, std={X[:,i].std():.3f}\")\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Split data\n",
" print(f\"\\n[2] Splitting data (80-20)...\")\n",
" from sklearn.model_selection import train_test_split\n",
" X_train, X_test, y_train, y_test = train_test_split(\n",
" X, y, test_size=0.2, random_state=42, stratify=y\n",
" )\n",
" print(f\" Train: {len(X_train)}, Test: {len(X_test)}\")\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Show class distribution\n",
" unique, counts = np.unique(y_train, return_counts=True)\n",
" print(f\"\\n Class distribution in training set:\")\n",
" for cls, count in zip(unique, counts):\n",
" cls_name = [k for k, v in label_mapping.items() if v == str(cls)][0]\n",
" print(f\" {cls}: {cls_name:15s} - {count:4d} samples ({count/len(y_train)*100:.1f}%)\")\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Train model\n",
" print(f\"\\n[3] Training Random Forest for LAND USE CLASSIFICATION...\")\n",
" from sklearn.ensemble import RandomForestClassifier\n",
" from sklearn.metrics import accuracy_score, classification_report\n",
2026-03-03 15:10:49 +07:00
" \n",
" model = RandomForestClassifier(\n",
2026-03-03 15:10:49 +07:00
" n_estimators=200, # More trees for better accuracy\n",
" max_depth=30,\n",
" min_samples_split=5,\n",
" random_state=42,\n",
" n_jobs=-1,\n",
" verbose=1\n",
" )\n",
" model.fit(X_train, y_train)\n",
2026-03-03 15:10:49 +07:00
" \n",
" # Evaluate\n",
" y_pred = model.predict(X_test)\n",
" accuracy = accuracy_score(y_test, y_pred)\n",
2026-03-03 15:10:49 +07:00
" \n",
" print(f\"\\n ✅ Model trained!\")\n",
" print(f\" Training accuracy: {model.score(X_train, y_train)*100:.2f}%\")\n",
2026-03-03 15:10:49 +07:00
" print(f\" Testing accuracy: {accuracy*100:.2f}%\")\n",
" \n",
" # Show feature importance\n",
" print(f\"\\n Feature importance:\")\n",
" importances = model.feature_importances_\n",
" indices = np.argsort(importances)[::-1]\n",
2026-03-03 15:10:49 +07:00
" for i, idx in enumerate(indices):\n",
" print(f\" {i+1}. {features_to_use[idx]:15s}: {importances[idx]:.4f}\")\n",
" \n",
" # Classification report\n",
" print(f\"\\n[4] Classification Report:\")\n",
" class_names = [k for k, v in sorted(label_mapping.items(), key=lambda x: x[1])]\n",
" print(classification_report(y_test, y_pred, target_names=class_names, zero_division=0))\n",
2026-03-03 15:10:49 +07:00
" \n",
" else:\n",
2026-03-03 15:10:49 +07:00
" print(f\" ❌ No samples extracted\")\n",
" model = None\n",
2026-03-03 15:10:49 +07:00
" \n",
2025-12-21 14:34:18 +07:00
" except Exception as e:\n",
" print(f\" ❌ Error: {e}\")\n",
" import traceback\n",
" traceback.print_exc()\n",
" model = None\n",
"else:\n",
" print(\"❌ Missing training data or satellite data\")\n",
2025-12-21 14:34:18 +07:00
" model = None\n",
"\n",
"print(\"\\n\" + \"=\"*70)\n",
"print(\"📝 NOTE: Model này dự đoán PHÂN LOẠI SỬ DỤNG ĐẤT (8 lớp)\")\n",
"print(\" NDVI là một trong các features đầu vào, không phải mục tiêu dự đoán\")\n",
"print(\" Sau khi predict → có thể hiển thị NDVI map như chỉ số phụ\")\n",
2026-03-03 15:10:49 +07:00
"print(\"=\"*70)"
2025-12-21 14:34:18 +07:00
]
},
{
"cell_type": "code",
2026-03-03 09:32:11 +00:00
"execution_count": 8,
2025-12-21 14:34:18 +07:00
"id": "f1a14379-ed6e-4897-9ca4-2669743fab40",
"metadata": {
2026-03-03 08:30:10 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
2026-03-03 09:32:11 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"MODEL SAVING\n",
"======================================================================\n",
"❌ No model to save\n",
"======================================================================\n"
]
}
],
2025-12-21 14:34:18 +07:00
"source": [
"# 💾 SAVE MODEL WITH METADATA\n",
2025-12-21 14:34:18 +07:00
"print(\"=\"*70)\n",
"print(\"MODEL SAVING\")\n",
"print(\"=\"*70)\n",
"\n",
"if model is not None:\n",
" print(\"\\n🔄 Saving trained LAND USE CLASSIFICATION model with metadata...\")\n",
2025-12-21 14:34:18 +07:00
" try:\n",
" from datetime import datetime\n",
" \n",
" # Prepare metadata for ModelManager\n",
" metadata = {\n",
" \"timestamp\": datetime.now().isoformat(),\n",
" \"data_source\": \"Local S3 ODC (Open Data Cube)\",\n",
" \"collections\": [\"sentinel-2-l2a\"],\n",
" \"features\": features_to_use, # All features used\n",
" \"feature_mode\": \"extended\", # Using extended aggregate features\n",
" \"training_samples\": len(X_train),\n",
" \"testing_samples\": len(X_test),\n",
" \"test_size\": 0.2,\n",
" \"train_accuracy\": float(model.score(X_train, y_train)),\n",
" \"test_accuracy\": float(accuracy),\n",
" \"model_type\": \"random_forest\",\n",
" \"device\": \"cpu\",\n",
" \"n_estimators\": 200,\n",
" \"max_depth\": 30,\n",
" \"learning_rate\": None,\n",
" \"cnn_epochs\": None,\n",
" \"n_features\": X_train.shape[1],\n",
" \"n_classes\": len(np.unique(y)),\n",
" \"class_names\": list(label_mapping.keys()),\n",
" \"classification_report\": classification_report(y_test, y_pred, \n",
" target_names=class_names, \n",
" output_dict=True,\n",
" zero_division=0),\n",
" \"bbox\": None,\n",
" \"time_range\": f\"{date_range[0]}/{date_range[1]}\",\n",
" \"resolution\": 10,\n",
" \"notes\": \"LAND USE CLASSIFICATION model trained from 01.train_ODC.ipynb. Predicts 8 land use classes using multiple spectral indices. NDVI is one of the input features, not the prediction target.\"\n",
" }\n",
" \n",
" # Save model with metadata using updated save_model function\n",
" save_model(\"model_land_use_odc.joblib\", model, metadata=metadata, label_encoder=None)\n",
" \n",
" print(\"✅ Model saved to model_train/model_land_use_odc.joblib\")\n",
" print(f\" - Purpose: Land Use Classification (8 classes)\")\n",
" print(f\" - Features: {len(features_to_use)} ({', '.join(features_to_use[:3])}...)\")\n",
" print(f\" - Train Accuracy: {metadata['train_accuracy']*100:.2f}%\")\n",
" print(f\" - Test Accuracy: {metadata['test_accuracy']*100:.2f}%\")\n",
" print(f\" - Classes: {metadata['n_classes']}\")\n",
" print(f\"\\n📝 NDVI là một trong các features, không phải prediction target\")\n",
" print(f\" Sau khi predict → có thể tính NDVI map riêng để hiển thị\")\n",
2025-12-21 14:34:18 +07:00
" except Exception as e:\n",
" print(f\"❌ Error saving model: {e}\")\n",
" import traceback\n",
" traceback.print_exc()\n",
2025-12-21 14:34:18 +07:00
"else:\n",
" print(\"❌ No model to save\")\n",
"\n",
"print(\"=\"*70)"
]
},
{
"cell_type": "markdown",
"id": "4a8579f4",
2026-03-03 08:43:35 +00:00
"metadata": {},
"source": [
"# 📖 Hướng dẫn sử dụng Model\n",
"\n",
"## Mục đích của Model\n",
"\n",
"Model này được train để **DỰ ĐOÁN PHÂN LOẠI SỬ DỤNG ĐẤT** với 8 lớp:\n",
"\n",
"1. **Lua tom** (0) - Lúa tôm\n",
"2. **Lua** (1) - Lúa\n",
"3. **CHN** (2) - Cây hàng năm\n",
"4. **CLN** (3) - Cây lâu năm \n",
"5. **TS** (4) - Thủy sản\n",
"6. **Song** (5) - Sông\n",
"7. **Dat xay dung** (6) - Đất xây dựng\n",
"8. **Rung** (7) - Rừng\n",
"\n",
"## Features đầu vào\n",
"\n",
"Model sử dụng **nhiều spectral indices** làm features:\n",
"- NDVI (mean, min, max, std, range)\n",
"- NDWI (mean)\n",
"- NDBI (mean)\n",
"- EVI (mean)\n",
"\n",
"## NDVI là gì trong hệ thống này?\n",
"\n",
"⚠️ **QUAN TRỌNG**: NDVI **KHÔNG PHẢI** là mục tiêu dự đoán!\n",
"\n",
"- **NDVI là INPUT FEATURE**: Một trong các chỉ số dùng để train model\n",
"- **Mục tiêu dự đoán**: Phân loại sử dụng đất (8 lớp)\n",
"- **NDVI map**: Có thể hiển thị NDVI map như chỉ số phụ sau khi predict xong\n",
"\n",
"## Workflow Prediction\n",
"\n",
"```python\n",
"# 1. Load model\n",
"model, label_encoder, metadata = model_manager.load_model(\"model_land_use_odc.joblib\")\n",
"\n",
"# 2. Extract features từ satellite data\n",
"features = extract_features(satellite_data) # NDVI, NDWI, NDBI, EVI\n",
"\n",
"# 3. Predict land use classification\n",
"land_use_prediction = model.predict(features)\n",
"# → Kết quả: Mảng với giá trị 0-7 (8 lớp sử dụng đất)\n",
"\n",
"# 4. (Optional) Tính NDVI map riêng để hiển thị\n",
"ndvi_map = (NIR - Red) / (NIR + Red)\n",
"# → NDVI map chỉ để visualize, không phải prediction target\n",
"```\n",
"\n",
"## So sánh với approach cũ\n",
"\n",
"| Approach | Features | Target | NDVI Role |\n",
"|----------|----------|--------|-----------|\n",
"| ❌ Cũ (sai) | Chỉ NDVI | 8 lớp đất | Input duy nhất |\n",
"| ✅ Mới (đúng) | NDVI + NDWI + NDBI + EVI | 8 lớp đất | Một trong nhiều features |\n",
"\n",
"## Test Model\n",
"\n",
"```python\n",
"# Test với website\n",
"# 1. Upload model_land_use_odc.joblib lên server\n",
"# 2. Chọn model trong prediction interface\n",
"# 3. Chọn vùng và thời gian\n",
"# 4. System sẽ tự động:\n",
"# - Extract features (NDVI, NDWI, NDBI, EVI)\n",
"# - Predict land use classification\n",
"# - (Optional) Generate NDVI visualization map\n",
"```"
]
},
2025-12-21 14:34:18 +07:00
{
"cell_type": "code",
2026-03-03 09:32:11 +00:00
"execution_count": 9,
2025-12-21 14:34:18 +07:00
"id": "33dd516d-9824-499e-96b9-5cd9224c194c",
"metadata": {
2026-03-03 08:30:10 +00:00
"jupyter": {
"source_hidden": true
},
2025-12-21 14:34:18 +07:00
"tags": []
},
2026-03-03 09:32:11 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"CLEANUP\n",
"======================================================================\n",
"\n",
"🔄 Closing Dask client and cluster...\n",
"✅ Cleanup complete\n",
"\n",
"======================================================================\n",
"✅ PIPELINE COMPLETE\n",
"======================================================================\n"
]
}
],
2025-12-21 14:34:18 +07:00
"source": [
"# 🛑 CLEANUP\n",
"print(\"=\"*70)\n",
"print(\"CLEANUP\")\n",
"print(\"=\"*70)\n",
"\n",
"print(\"\\n🔄 Closing Dask client and cluster...\")\n",
"try:\n",
" client.close()\n",
" cluster.close()\n",
" print(\"✅ Cleanup complete\")\n",
"except Exception as e:\n",
" print(f\"⚠️ Error during cleanup: {e}\")\n",
"\n",
"print(\"\\n\" + \"=\"*70)\n",
"print(\"✅ PIPELINE COMPLETE\")\n",
"print(\"=\"*70)"
]
2026-03-03 08:58:45 +00:00
},
{
"cell_type": "code",
"execution_count": null,
2026-03-03 09:19:27 +00:00
"id": "34779828-e8d6-4ead-9b33-c5f1be05056d",
2026-03-03 08:58:45 +00:00
"metadata": {},
"outputs": [],
"source": []
2025-12-21 14:34:18 +07:00
}
],
"metadata": {
"kernelspec": {
2026-03-03 15:10:49 +07:00
"display_name": "Python 3 (ipykernel)",
2025-12-21 14:34:18 +07:00
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
2026-03-03 15:10:49 +07:00
"version": "3.12.3"
2025-12-21 14:34:18 +07:00
}
},
"nbformat": 4,
"nbformat_minor": 5
}