{"id":50735481,"url":"https://github.com/martanto/mounts-project","last_synced_at":"2026-06-11T14:01:13.458Z","repository":{"id":362724079,"uuid":"1260154212","full_name":"martanto/mounts-project","owner":"martanto","description":"Unofficial package for MOUNTS | Monitoring Unrest From Space. http://www.mounts-project.com","archived":false,"fork":false,"pushed_at":"2026-06-10T10:40:20.000Z","size":3384,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T13:22:54.139Z","etag":null,"topics":["eruption","monitoring","remote-sensing","so2","sulfur","thermal","volcano","volcanology"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/mounts-project/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/martanto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T07:55:33.000Z","updated_at":"2026-06-10T10:41:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/martanto/mounts-project","commit_stats":null,"previous_names":["martanto/mounts"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/martanto/mounts-project","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martanto%2Fmounts-project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martanto%2Fmounts-project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martanto%2Fmounts-project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martanto%2Fmounts-project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/martanto","download_url":"https://codeload.github.com/martanto/mounts-project/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martanto%2Fmounts-project/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34201842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eruption","monitoring","remote-sensing","so2","sulfur","thermal","volcano","volcanology"],"created_at":"2026-06-10T13:01:38.723Z","updated_at":"2026-06-11T14:01:13.389Z","avatar_url":"https://github.com/martanto.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mounts-project\n\n![Version](https://img.shields.io/badge/version-0.2.4-blue)\n![Python](https://img.shields.io/badge/python-3.11%2B-blue)\n![License](https://img.shields.io/badge/license-MIT-green)\n![Status](https://img.shields.io/badge/status-active%20development-orange)\n![PyPI](https://img.shields.io/pypi/v/mounts-project?label=pypi)\n![Downloads](https://img.shields.io/pypi/dm/mounts-project?label=downloads)\n\nUnofficial Python package\nfor [MOUNTS — Monitoring Unrest From Space](http://www.mounts-project.com).\nScrapes SO2 and thermal timeseries from the public MOUNTS pages and exposes them as pandas\nDataFrames, ready to be written to CSV or XLSX.\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/martanto/mounts-project/main/assets/dashboard-1.jpg\" alt=\"Dashboard overview\" /\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/martanto/mounts-project/main/assets/dashboard-2.jpg\" alt=\"Dashboard detail\" /\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/martanto/mounts-project/main/assets/dashboard-3.jpg\" alt=\"Thermal daily max calendar\" /\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/martanto/mounts-project/main/assets/anomaly-overlay.jpg\" alt=\"Anomaly overlay on SO2 vs Thermal\" /\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/martanto/mounts-project/main/assets/image-gallery-1.jpg\" alt=\"Anomaly overlay on SO2 vs Thermal\" /\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/martanto/mounts-project/main/assets/image-gallery-2.jpg\" alt=\"Anomaly overlay on SO2 vs Thermal\" /\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Disclaimer\n\nThis is an **unofficial** client and is not affiliated with the MOUNTS project. The information\npresented within the MOUNTS website is provided \"as is\" and users bear all responsibility and\nliability for their use of data and images, and for any indirect, incidental or consequential\ndamages arising out of any use of, or inability to use, the data.\n\n## Table of Contents\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Command-line interface](#command-line-interface)\n- [Dashboard](#dashboard)\n- [Quick Start](#quick-start)\n- [About the project](#about-the-project)\n- [Publications](#publications)\n- [Credits \u0026 Acknowledgements](#credits--acknowledgements)\n- [Use of the data](#use-of-the-data)\n- [API Reference](#api-reference)\n    - [`MountsProject`](#mountsprojectfilter_values01-output_dirnone-overwritefalse-verbosefalse)\n    - [Image downloads](#image-downloads)\n    - [Utility functions](#utility-functions)\n    - [Logging helpers](#logging-helpers)\n\n## Requirements\n\n- **Python** `\u003e=3.11`\n- **uv** — Python package\n  manager ([installation guide](https://docs.astral.sh/uv/getting-started/installation/))\n\n## Installation\n\nInstall from PyPI:\n\n```bash\nuv add mounts-project[dashboard]\n```\n\nOr with pip:\n\n```bash\npip install mounts-project[dashboard]\n```\n\nOr install the latest from GitHub:\n\n```bash\nuv add git+https://github.com/martanto/mounts-project\n```\n\nOr, to work on the source:\n\n```bash\ngit clone https://github.com/martanto/mounts-project\ncd mounts-project\nuv sync\n```\n\n## Command-line interface\n\nInstalling the package registers a `mounts` console script:\n\n```bash\nuv run mounts --help\nuv run mounts save --help\n```\n\n### `mounts save`\n\nExtract every volcano in the default catalog and write the result.\n\n```bash\nuv run mounts save --type csv                       # → ./output/csv/*.csv + all-volcanoes.csv\nuv run mounts save --type xlsx --output-dir data    # → ./data/xlsx/*.xlsx + all-volcanoes.xlsx\nuv run mounts save --overwrite -v                   # force re-fetch + verbose logs\nuv run mounts save --extract-image --max-workers 16 # also download SO2/thermal images\n```\n\n| Option              | Default    | Description                                                              |\n|---------------------|------------|--------------------------------------------------------------------------|\n| `--type`            | `csv`      | Output format (`csv` or `xlsx`).                                         |\n| `--output-dir`      | `./output` | Override the output directory.                                           |\n| `--overwrite`       | off        | Re-fetch from MOUNTS even when cached JSON exists.                       |\n| `--verbose`         | off        | Emit per-volcano info logs during extraction.                            |\n| `--extract-image`   | off        | Also download SO2 and thermal images into `\u003coutput_dir\u003e/images/`.        |\n| `--max-workers`     | `8`        | Thread pool size for image downloads (only used with `--extract-image`). |\n\n### `mounts dashboard`\n\nLaunch the Streamlit dashboard. Any extra arguments are forwarded to\n`streamlit run`:\n\n```bash\nuv run mounts dashboard\nuv run mounts dashboard --server.port 9000 --server.headless true\n```\n\n## Dashboard\n\n`mounts dashboard` opens a Streamlit app that groups the extracted data by\nvolcano and by data type (SO2 / Thermal). Install the extras first:\n\n```bash\nuv sync --extra dashboard\nuv run mounts dashboard\n```\n\nThe dashboard reads `output/all-volcanoes.csv` from the current working\ndirectory. If it does not exist yet, click **Refresh data** in the sidebar\nor run `uv run mounts save --type csv` once to populate it.\n\nThe **Volcano detail** page also includes an image gallery (since `0.2.1`)\nshowing the SO2 and thermal snapshots previously fetched via\n`save(extract_image=True)`. Images live under\n`output/images/\u003cslug\u003e/{so2,thermal}/` and are matched to the active volcano,\ndata type, and date range from the existing selectors — no extra index file\nis required. The gallery renders as a 10-column grid with a per-tab\n**Per page** dropdown (50 / 100 / 200) and a page selector, both kept in a\nnarrow control group above the thumbnails.\n\n## Quick Start\n\nMinimal example (see [`main.py`](main.py)):\n\n```python\nfrom mounts_project import MountsProject\n\n\ndef main():\n    mounts = MountsProject(verbose=True)\n\n    # Scrape every volcano in the built-in catalog\n    mounts.extract()\n\n    # Access the per-volcano DataFrames\n    data = mounts.data\n\n    # Export to ./output/xlsx/\u003cvolcano\u003e.xlsx + ./output/all-volcanoes.xlsx\n    mounts.save(filetype=\"xlsx\")\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\nRun it:\n\n```bash\nuv run python main.py\n```\n\nThe full pipeline is chainable:\n\n```python\nMountsProject(verbose=True).extract().save(filetype=\"csv\")\n```\n\nOutputs land under `./output/`:\n\n```\noutput/\n├── all-volcanoes.csv\n├── csv/\n│   ├── lewotobi-laki-laki.csv\n│   ├── marapi.csv\n│   └── ...\n└── json/                # cached raw scrape; reused on subsequent runs\n    ├── lewotobi-laki-laki-264180.json\n    └── ...\n```\n\nTo monitor your own list of volcanoes instead of the built-in Indonesian catalog, pass them to\n`extract()`:\n\n```python\nvolcanoes = [\n    {\"name\": \"Etna\", \"code\": \"211060\"},\n    {\"name\": \"Stromboli\", \"code\": \"211040\"},\n]\nMountsProject().extract(volcanoes=volcanoes).save()\n```\n\nTo also download the SO2 and thermal images served by MOUNTS, set `extract_image=True`\non `save()`. Images land under `\u003coutput_dir\u003e/images/\u003cslug\u003e/{so2,thermal}/`, and a\n`figures.json` index is written next to the JSON cache by `extract()`:\n\n```python\nMountsProject(verbose=True).extract().save(extract_image=True, max_workers=8)\n```\n\n## About the project\n\nMOUNTS is a project conceptualized and led by Sébastien Valade since April 2017. Its aim is\nto develop an operational monitoring system for volcanoes worldwide using satellite imagery.\nIt currently focuses on processing of Sentinel-1 (SAR), Sentinel-2 (SWIR), and Sentinel-5P (TROPOMI)\ndata.\nArtificial intelligence \"plugins\" are developed and implemented in the processing chain to assist\nmonitoring tasks.\n\nThe project was from April 2017 to October 2019 funded by GEO.X and carried at TU-Berlin\n(Computer Vision \u0026 Remote Sensing group, Prof. O. Hellwich) and GFZ\n(Physics of Earthquakes and Volcanoes section, Priv. Doz. T. Walter).\nSince March 2020, the project is carried at UNAM (Instituto de Geofísica, Mexico City).\nThe server running both the system and website is however still hosted at CV TU-Berlin,\nwith the kind agreement of Prof. Hellwich.\n\nMOUNTS is strongly inspired by the operating MIROVA system,\nwith which tight collaborations are ongoing.\n\n## Publications\n\n### System description and recent eruptive events\n\n- Valade, S., Ley, A., Massimetti, F., D'Hondt, O., Laiolo, M., Coppola, D., Loibl, D., Hellwich,\n  O., Walter, T.R., Towards Global Volcano Monitoring Using Multisensor Sentinel Missions and\n  Artificial Intelligence: The MOUNTS Monitoring System, *Remote Sens.*, 2019, 11, 1528\n\n### Algorithm used to analyze Sentinel-2 images\n\n- Massimetti, F., Coppola, D., Laiolo, M., Valade, S., Cigolini, C., Ripepe M., Volcanic Hot-Spot\n  Detection Using SENTINEL-2: A Comparison with MODIS–MIROVA Thermal Data Series, *Remote Sens.*,\n  2020, 12(5), 820\n\n### Algorithm used to filter speckle from Sentinel-1 images\n\n- Davis, T., Jain, V., Ley, A., D'Hondt, O., Valade, S., Hellwich, O., Reference-free despeckling of\n  Synthetic-Aperture Radar images using a deep convolutional network, IGARSS 2020\n\n### Algorithms developed to improve analysis of Sentinel-5P images\n\n- Markus, B., Valade, S., Wöllhaf, M., Hellwich, O., Automatic retrieval of volcanic SO2 emission\n  source from TROPOMI products, *Front. Earth Sci.*, 2023, 10\n\n### Volcanological studies using data and analysis from MOUNTS (selection)\n\n- Valade S., Coppola D., Campion R., Ley A., Boulesteix T., Taquet N., Legrand D., Laiolo M., Walter\n  T. R. and De la Cruz-Reyna S. Lava dome cycles reveal rise and fall of magma column at\n  Popocatépetl volcano, *Nature Communications*, 2023\n- Coppola D., Valade S., Masias P., Laiolo M., Massimetti F., Campus A., Aguilar R., Anccasi R.,\n  Apaza F., Ccallata B., Cigolini C., Cruz L. F., Finizola A., Gonzales K., Macedo O., Miranda R.,\n  Ortega M., Paxi R., Taipe E., and Valdivia D. Shallow magma convection evidenced by excess\n  degassing and thermal radiation during the dome-forming sabancaya eruption (2012–2020), *Bulletin\n  of Volcanology*, 2022\n- Burgi P.-Y., Valade, S., Coppola D., Boudoire G., Mavonga G., Rufino F., and Tedesco D.,\n  Unconventional filling dynamics of a pit crater, *EPSL*, 2021\n\n## Credits \u0026 Acknowledgements\n\n### Funding sources\n\n- 2017-2019: GEO.X 2-year postdoc fundings for the bottom-up project MOUNTS\n- 2019: GEO.X Seed Funding 6-months postdoc for the project MOUNTS-AI dedicated to investigating\n  Artificial Intelligence strategies for volcano monitoring.\n- 2021-2023: PAPIIT project IA102221, 2-year project with part of the fundings dedicated to the\n  purchase of new hardware for MOUNTS.\n\n### TU-Berlin\n\n- Andreas Ley developed and trained the convolutional neural network used by MOUNTS to detect ground\n  deformation from Sentinel-1 interferograms.\n- Olivier D'Hondt developed the NDSAR toolkit for SAR speckle filtering used in Valade et al. (\n  2019).\n- Timothy Davis \u0026 Vinit Jain, under the supervision of Andreas Ley \u0026 Sébastien Valade, developed and\n  trained the convolutional neural network used by MOUNTS to despeckle Sentinel-1 SAR amplitude\n  images: Davis et al. 2020 (IGARSS).\n- Manuel Wöllhaf is contributing to the development of the new backend architecture of MOUNTS. He\n  co-supervised Balazs Markus, whose research project focused on the automatic retrieval of volcanic\n  SO2 emission source from TROPOMI products (Markus et al. 2023).\n- MIROVA: members of MIROVA developed the algorithm used to detect hot pixels within the Sentinel-2\n  SWIR bands (Massimetti et al., 2020). MIROVA is a collaborative project between the Universities\n  of Turin and Firenze (Italy). Developments are underway to increase the interactivity between\n  MOUNTS and MIROVA.\n- LGS (University of Firenze): many thanks to friends and former colleagues of the Laboratorio di\n  Geofisica Sperimentale (LGS), from whom much was learnt. This website is inspired by the unique\n  interaction of research and monitoring that is achieved in this group.\n- Sentinel data are freely available through ESA's Copernicus Open Access Hub, and are partially\n  processed with the free SNAP toolboxes. Earthquake catalogs are provided by GEOFON (GFZ Potsdam)\n  and USGS, and interrogated using the Pyrocko Toolbox.\n\n## Use of the data\n\nThe products available on the MOUNTS website are value-added products created from freely available\nSentinel data provided by ESA. The products are released under the following conditions: permission\nto freely copy, share and quote for non-commercial purposes, with attribution to MOUNTS and ESA as\nthe original source. If used for academic purposes, contacting Sébastien Valade (\nvalade@igeofisica.unam.mx) and citing the above-mentioned publication (Valade et al. 2019, *Remote\nSensing*) is kindly appreciated.\n\n## API Reference\n\n### `MountsProject(filter_values=0.1, output_dir=None, overwrite=False, verbose=False)`\n\nOrchestrator that holds the scraped data and drives the\n`extract() → save()` pipeline.\n\n**Constructor parameters**\n\n| Parameter       | Type            | Default        | Description                                                                                                                            |\n|-----------------|-----------------|----------------|----------------------------------------------------------------------------------------------------------------------------------------|\n| `filter_values` | `float \\| None` | `0.1`          | Lower bound applied to the `value` column after extraction. Rows with `value \u003c= filter_values` are dropped. `None` disables filtering. |\n| `output_dir`    | `str \\| None`   | `\u003ccwd\u003e/output` | Root directory for cached JSON and exported CSV/XLSX files.                                                                            |\n| `overwrite`     | `bool`          | `False`        | Force re-fetching from MOUNTS even when a cached JSON file exists.                                                                     |\n| `verbose`       | `bool`          | `False`        | Emit per-volcano info logs during fetch.                                                                                               |\n\n**Attributes**\n\n| Attribute  | Type                          | Description                                                                                                    |\n|------------|-------------------------------|----------------------------------------------------------------------------------------------------------------|\n| `data`     | `dict[str, pandas.DataFrame]` | Per-volcano DataFrames keyed by volcano name. Populated by `extract()`.                                        |\n| `catalogs` | `list[dict[str, Any]]`        | Per-volcano metadata: `name`, `code`, `updated_at`. Populated by `extract()`.                                  |\n| `figures`  | `list[dict[str, Any]]`        | Per-volcano figure index: `name`, `code`, `index`, `so2`, `thermal` image URL lists. Populated by `extract()`. |\n| `files`    | `list[str]`                   | Paths of files written by `save()`.                                                                            |\n\n**Methods**\n\n#### `extract(volcanoes=None) -\u003e Self`\n\nFetch timeseries for a list of volcanoes and populate `self.data`, `self.catalogs`, and\n`self.figures`. Also writes `\u003coutput_dir\u003e/figures.json` (the figure index used by\n`download_images_from_json`).\n\n| Parameter   | Type                           | Default          | Description                                                                                                |\n|-------------|--------------------------------|------------------|------------------------------------------------------------------------------------------------------------|\n| `volcanoes` | `list[dict[str, str]] \\| None` | built-in catalog | List of `{\"name\": ..., \"code\": ...}` entries. When `None`, uses the bundled 12-volcano Indonesian catalog. |\n\nReturns `self` for chaining.\n\n#### `extract_single_volcano(name, code) -\u003e pandas.DataFrame`\n\nFetch and assemble the combined SO2 + thermal DataFrame for one volcano. Used internally by\n`extract()`; call it directly if you want a single DataFrame without populating `self.data`.\n\n| Parameter | Type  | Description                                                   |\n|-----------|-------|---------------------------------------------------------------|\n| `name`    | `str` | Volcano name (used for the `name` column and cache filename). |\n| `code`    | `str` | MOUNTS volcano code (used in the URL and the `code` column).  |\n\nReturns a DataFrame indexed by `datetime`, with columns `value`, `graph`, `type` (`\"SO2\"` or\n`\"Thermal\"`), `date`, `time`, `code`, `name`.\n\n#### `save(filetype=\"csv\", extract_image=False, max_workers=8) -\u003e Self`\n\nWrite per-volcano files plus a merged `all-volcanoes` export. Calls `extract()` automatically when\n`self.data` is empty. When `extract_image=True`, also downloads the SO2 and thermal images\nreferenced by `self.figures` after the data files are written.\n\n| Parameter       | Type                     | Default | Description                                                                            |\n|-----------------|--------------------------|---------|----------------------------------------------------------------------------------------|\n| `filetype`      | `Literal[\"csv\", \"xlsx\"]` | `\"csv\"` | Output format.                                                                         |\n| `extract_image` | `bool`                   | `False` | Also download SO2 and thermal images into `\u003coutput_dir\u003e/images/\u003cslug\u003e/{so2,thermal}/`. |\n| `max_workers`   | `int`                    | `8`     | Maximum concurrent download threads when `extract_image=True`.                         |\n\nWrites:\n\n- `\u003coutput_dir\u003e/\u003cfiletype\u003e/\u003cslug\u003e.\u003cfiletype\u003e` per volcano\n- `\u003coutput_dir\u003e/all-volcanoes.\u003cfiletype\u003e` (concatenated)\n\nReturns `self` for chaining.\n\n### Image downloads\n\nFrom `mounts_project.download`. All downloaders run in parallel via a\n`ThreadPoolExecutor` that shares a single `requests.Session` (pooled connections, reused\nTLS handshakes). Individual URL failures are logged and skipped so a bad URL does not\nabort the batch. URLs are resolved against the MOUNTS static asset host.\n\n| Function                                                                                                  | Description                                                                                                             |\n|-----------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|\n| `download_image(image_url, output_dir=None, overwrite=False, verbose=False, session=None)`                | Download a single image. Skips re-downloading when the basename already exists unless `overwrite=True`.                 |\n| `download_images(image_urls, output_dir=None, overwrite=False, verbose=False, max_workers=8)`             | Bulk download a flat list of image URLs into `output_dir` (defaults to `\u003ccwd\u003e/output/images`).                          |\n| `download_images_from_dict(figures_dict, output_dir=None, overwrite=False, verbose=False, max_workers=8)` | For each `{\"name\", \"so2\", \"thermal\"}` entry, download both image lists into `\u003coutput_dir\u003e/\u003cslug(name)\u003e/{so2,thermal}/`. |\n| `download_images_from_json(figures_json, output_dir=None, overwrite=False, verbose=False, max_workers=8)` | Same as above, but reads the figure list from a JSON file (such as the `figures.json` written by `extract()`).          |\n\nStandalone use, re-using the `figures.json` written by a previous `extract()` call:\n\n```python\nfrom mounts_project.download import download_images_from_json\n\ndownload_images_from_json(\n    \"output/figures.json\",\n    output_dir=\"output/images\",\n    verbose=True,\n)\n```\n\n### Utility functions\n\nFrom `mounts_project.utils`:\n\n| Function                             | Description                                                                                                                                             |\n|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `get_so2_values(graph_json)`         | Extract the SO2 timeseries from a MOUNTS Plotly graph payload (`data[2]`). Returns a DataFrame with `datetime`, `value`, `graph`, `type=\"SO2\"`.         |\n| `get_thermal_values(graph_json)`     | Extract the thermal timeseries from a MOUNTS Plotly graph payload (`data[0]`). Returns a DataFrame with `datetime`, `value`, `graph`, `type=\"Thermal\"`. |\n| `get_json_from_javascript(response)` | Regex-extract and parse the `var graph = {...}` JavaScript blob from a MOUNTS HTTP response. Raises `ValueError` if not found.                          |\n| `slugify(text, hyphen=\"-\")`          | Convert arbitrary text into a safe filename slug.                                                                                                       |\n| `ensure_dir(path)`                   | Create a directory (and any missing parents) and return it as a `pathlib.Path`.                                                                         |\n\n### Logging helpers\n\nThe package configures [loguru](https://loguru.readthedocs.io/) on import, writing a console stream\nplus daily-rotated `logs/mounts_YYYY-MM-DD.log` and `logs/errors_YYYY-MM-DD.log` files in the\ncurrent working directory.\n\nFrom `mounts_project.logger`:\n\n| Function                     | Description                                                                               |\n|------------------------------|-------------------------------------------------------------------------------------------|\n| `get_logger()`               | Return the package-wide loguru `logger` instance.                                         |\n| `set_log_level(level)`       | Change the console log level (`\"DEBUG\"`, `\"INFO\"`, `\"WARNING\"`, `\"ERROR\"`, `\"CRITICAL\"`). |\n| `set_log_directory(log_dir)` | Change where log files are written.                                                       |\n| `disable_logging()`          | Remove all handlers.                                                                      |\n| `enable_logging()`           | Restore handlers after `disable_logging()`.                                               |\n\nSet the environment variable `DISABLE_LOGGING=1` before import to skip handler setup entirely (\nuseful for subprocess workers).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartanto%2Fmounts-project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartanto%2Fmounts-project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartanto%2Fmounts-project/lists"}