# pixltsnorm
**Pixel-based Linear Time Series Normalizer**

`pixltsnorm` is a small, focused Python library that:
- **Bridges** or **harmonizes** numeric time-series data (e.g., reflectance, NDVI, etc.) across multiple sensors or sources.
- **Fits** simple linear transformations (`y = slope*x + intercept`) to map one sensor’s scale onto another.
- **Chains** transformations to handle indirect overlaps (sensor0 → sensor1 → …).
- **Filters** outliers using threshold-based filtering before fitting linear models.
Although originally inspired by NDVI normalization across different Landsat sensors, **pixltsnorm** is domain-agnostic. You can use it for any numeric time-series that needs linear alignment.
---
## Features
1. **Outlier Filtering**
- Removes large disagreements in overlapping time-series pairs, based on a simple threshold for |A - B|.
2. **Local (Pixel-Level) Linear Bridging**
- Regress one sensor’s measurements onto another’s (e.g., a single pixel’s time series).
- Produces an easy-to-apply transform function for new data.
3. **Global Bridging**
- Follows the approach of Roy et al. (2016): gather all overlapping values across the entire dataset, fit one “universal” slope/intercept.
- Useful if you need *scene-wide* or *region-wide* continuity between two or more sensors (e.g., L5 → L7 → L8).
4. **Chaining**
- Allows any number of sensors to be combined in sequence, producing a single transform from the first sensor to the last.
5. **Lightweight**
- Minimal dependencies: `numpy`, `scikit-learn`, and optionally `pandas`.
6. **Earth Engine Submodule**
- A dedicated `earth_engine` subpackage provides GEE-specific helpers (e.g., for Landsat) that you can incorporate in your Earth Engine workflows.
---
## Basic Usage
### Harmonize Two Sensors (Pixel-Level Example)
```python
import numpy as np
from pixltsnorm.harmonize import harmonize_series
# Suppose sensorA_values and sensorB_values have overlapping data
sensorA = np.array([0.0, 0.2, 0.8, 0.9])
sensorB = np.array([0.1, 0.25, 0.7, 1.0])
results = harmonize_series(sensorA, sensorB, outlier_threshold=0.2)
print("Slope:", results['coef'])
print("Intercept:", results['intercept'])
# Transform new data from sensorA scale -> sensorB scale
transform_func = results['transform']
new_data = np.array([0.3, 0.4, 0.5])
mapped = transform_func(new_data)
print("Mapped values:", mapped)
```
### Chaining Multiple Sensors
```python
from pixltsnorm.harmonize import chain_harmonization
import numpy as np
# Suppose we have 4 different sensors that partially overlap:
sensor0 = np.random.rand(10)
sensor1 = np.random.rand(10)
sensor2 = np.random.rand(10)
sensor3 = np.random.rand(10)
chain_result = chain_harmonization([sensor0, sensor1, sensor2, sensor3])
print("Pairwise transforms:", chain_result['pairwise'])
print("Overall slope (sensor0->sensor3):", chain_result['final_slope'])
print("Overall intercept (sensor0->sensor3):", chain_result['final_intercept'])
# Apply sensor0 -> sensor3 transform
sensor0_on_sensor3_scale = (chain_result['final_slope'] * sensor0
+ chain_result['final_intercept'])
print("sensor0 mapped onto sensor3 scale:", sensor0_on_sensor3_scale)
```
### Global Bridging
```python
import pandas as pd
from pixltsnorm.global_harmonize import chain_global_bridging
# Suppose we have three DataFrames: df_l5, df_l7, df_l8
# Each has row=pixels, columns=dates (plus 'lon','lat').
# The approach merges all overlapping values across the region/time:
result = chain_global_bridging(df_l5, df_l7, df_l8, outlier_thresholds=(0.2, 0.2))
# We get a single slope/intercept for L5->L7, L7->L8, plus the chain L5->L8
print("Global bridging L5->L7 =>", result["L5->L7"]["coef"], result["L5->L7"]["intercept"])
print("Global bridging L7->L8 =>", result["L7->L8"]["coef"], result["L7->L8"]["intercept"])
print("Chained L5->L8 =>", result["L5->L8"]["coef"], result["L5->L8"]["intercept"])
```
### Earth Engine Submodule
```python
from pixltsnorm.earth_engine import create_reduce_region_function, addNDVI, cloudMaskL457
# Use these GEE-based helpers inside your Earth Engine scripts
```
Please see the docs and example notebooks for more examples.
---
## Installation
1. Clone or download this repository.
2. (Optional) Create and activate a virtual environment.
3. Install in editable mode:
```bash
pip install -e .
```
Then you can do:
```python
import pixltsnorm
```
and access the library’s functionality.
---
## Acknowledgements
- **Joseph Emile Honour Percival** performed the initial research in 2021 during his PhD at **Kyoto University**, where the pixel-level time-series normalization idea was first applied to multi-sensor analysis.
- The global bridging logic is inspired by Roy et al. (2016), which outlines regression-based continuity for Landsat sensors across large areas.
Roy, David P., V. Kovalskyy, H. K. Zhang, Eric F. Vermote, L. Yan, S. S. Kumar, and A. Egorov. "Characterization of Landsat-7 to Landsat-8 reflective wavelength and normalized difference vegetation index continuity." Remote sensing of Environment 185 (2016): 57-70.
---
## License
This project is licensed under the **MIT License**. See the [LICENSE](./LICENSE) file for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "pixltsnorm",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "remote sensing, time series, normalization",
"author": null,
"author_email": "Joseph Emile Honour Percival <ipercival@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/c9/fc/b37209d01e2505b2713d36caa17a4f27e52a70401d662717c3f843932031/pixltsnorm-0.1.0.tar.gz",
"platform": null,
"description": "# pixltsnorm\n\n**Pixel-based Linear Time Series Normalizer**\n\n\n\n`pixltsnorm` is a small, focused Python library that:\n\n- **Bridges** or **harmonizes** numeric time-series data (e.g., reflectance, NDVI, etc.) across multiple sensors or sources. \n- **Fits** simple linear transformations (`y = slope*x + intercept`) to map one sensor\u2019s scale onto another. \n- **Chains** transformations to handle indirect overlaps (sensor0 \u2192 sensor1 \u2192 \u2026). \n- **Filters** outliers using threshold-based filtering before fitting linear models.\n\nAlthough originally inspired by NDVI normalization across different Landsat sensors, **pixltsnorm** is domain-agnostic. You can use it for any numeric time-series that needs linear alignment.\n\n---\n\n## Features\n\n1. **Outlier Filtering** \n - Removes large disagreements in overlapping time-series pairs, based on a simple threshold for |A - B|.\n\n2. **Local (Pixel-Level) Linear Bridging** \n - Regress one sensor\u2019s measurements onto another\u2019s (e.g., a single pixel\u2019s time series). \n - Produces an easy-to-apply transform function for new data.\n\n3. **Global Bridging** \n - Follows the approach of Roy et\u202fal. (2016): gather all overlapping values across the entire dataset, fit one \u201cuniversal\u201d slope/intercept. \n - Useful if you need *scene-wide* or *region-wide* continuity between two or more sensors (e.g., L5 \u2192 L7 \u2192 L8).\n\n4. **Chaining** \n - Allows any number of sensors to be combined in sequence, producing a single transform from the first sensor to the last.\n\n5. **Lightweight** \n - Minimal dependencies: `numpy`, `scikit-learn`, and optionally `pandas`.\n\n6. **Earth Engine Submodule** \n - A dedicated `earth_engine` subpackage provides GEE-specific helpers (e.g., for Landsat) that you can incorporate in your Earth Engine workflows.\n\n---\n\n## Basic Usage\n\n### Harmonize Two Sensors (Pixel-Level Example)\n\n```python\nimport numpy as np\nfrom pixltsnorm.harmonize import harmonize_series\n\n# Suppose sensorA_values and sensorB_values have overlapping data\nsensorA = np.array([0.0, 0.2, 0.8, 0.9])\nsensorB = np.array([0.1, 0.25, 0.7, 1.0])\n\nresults = harmonize_series(sensorA, sensorB, outlier_threshold=0.2)\nprint(\"Slope:\", results['coef'])\nprint(\"Intercept:\", results['intercept'])\n\n# Transform new data from sensorA scale -> sensorB scale\ntransform_func = results['transform']\nnew_data = np.array([0.3, 0.4, 0.5])\nmapped = transform_func(new_data)\nprint(\"Mapped values:\", mapped)\n```\n\n### Chaining Multiple Sensors\n\n```python\nfrom pixltsnorm.harmonize import chain_harmonization\nimport numpy as np\n\n# Suppose we have 4 different sensors that partially overlap:\nsensor0 = np.random.rand(10)\nsensor1 = np.random.rand(10)\nsensor2 = np.random.rand(10)\nsensor3 = np.random.rand(10)\n\nchain_result = chain_harmonization([sensor0, sensor1, sensor2, sensor3])\nprint(\"Pairwise transforms:\", chain_result['pairwise'])\nprint(\"Overall slope (sensor0->sensor3):\", chain_result['final_slope'])\nprint(\"Overall intercept (sensor0->sensor3):\", chain_result['final_intercept'])\n\n# Apply sensor0 -> sensor3 transform\nsensor0_on_sensor3_scale = (chain_result['final_slope'] * sensor0 \n + chain_result['final_intercept'])\nprint(\"sensor0 mapped onto sensor3 scale:\", sensor0_on_sensor3_scale)\n```\n\n### Global Bridging\n\n```python\nimport pandas as pd\nfrom pixltsnorm.global_harmonize import chain_global_bridging\n\n# Suppose we have three DataFrames: df_l5, df_l7, df_l8\n# Each has row=pixels, columns=dates (plus 'lon','lat').\n# The approach merges all overlapping values across the region/time:\nresult = chain_global_bridging(df_l5, df_l7, df_l8, outlier_thresholds=(0.2, 0.2))\n\n# We get a single slope/intercept for L5->L7, L7->L8, plus the chain L5->L8\nprint(\"Global bridging L5->L7 =>\", result[\"L5->L7\"][\"coef\"], result[\"L5->L7\"][\"intercept\"])\nprint(\"Global bridging L7->L8 =>\", result[\"L7->L8\"][\"coef\"], result[\"L7->L8\"][\"intercept\"])\nprint(\"Chained L5->L8 =>\", result[\"L5->L8\"][\"coef\"], result[\"L5->L8\"][\"intercept\"])\n```\n\n### Earth Engine Submodule\n\n```python\nfrom pixltsnorm.earth_engine import create_reduce_region_function, addNDVI, cloudMaskL457\n\n# Use these GEE-based helpers inside your Earth Engine scripts\n```\n\nPlease see the docs and example notebooks for more examples.\n\n---\n\n## Installation\n\n1. Clone or download this repository. \n2. (Optional) Create and activate a virtual environment. \n3. Install in editable mode:\n\n```bash\npip install -e .\n```\n\nThen you can do:\n\n```python\nimport pixltsnorm\n```\n\nand access the library\u2019s functionality.\n\n---\n\n## Acknowledgements\n\n- **Joseph Emile Honour Percival** performed the initial research in 2021 during his PhD at **Kyoto University**, where the pixel-level time-series normalization idea was first applied to multi-sensor analysis. \n- The global bridging logic is inspired by Roy et al. (2016), which outlines regression-based continuity for Landsat sensors across large areas.\n\nRoy, David P., V. Kovalskyy, H. K. Zhang, Eric F. Vermote, L. Yan, S. S. Kumar, and A. Egorov. \"Characterization of Landsat-7 to Landsat-8 reflective wavelength and normalized difference vegetation index continuity.\" Remote sensing of Environment 185 (2016): 57-70.\n\n---\n\n## License\n\nThis project is licensed under the **MIT License**. See the [LICENSE](./LICENSE) file for details.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Pixel-based Linear Time Series Normalizer",
"version": "0.1.0",
"project_urls": {
"Bug Tracker": "https://github.com/iosefa/pixltsnorm",
"Source": "https://github.com/iosefa/pixltsnorm"
},
"split_keywords": [
"remote sensing",
" time series",
" normalization"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "b98f24ee1971643c53916c1a6e710ffad06b6d4304d51987b57bc74d05d7f32e",
"md5": "c060fdad634d5e55e1374745844c669b",
"sha256": "78b95c10add671934f2c350ca636f14e9438c98ec88d66b0f648cf5a51e6fb70"
},
"downloads": -1,
"filename": "pixltsnorm-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "c060fdad634d5e55e1374745844c669b",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 22432,
"upload_time": "2025-03-16T20:53:02",
"upload_time_iso_8601": "2025-03-16T20:53:02.216348Z",
"url": "https://files.pythonhosted.org/packages/b9/8f/24ee1971643c53916c1a6e710ffad06b6d4304d51987b57bc74d05d7f32e/pixltsnorm-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c9fcb37209d01e2505b2713d36caa17a4f27e52a70401d662717c3f843932031",
"md5": "d2a83ec4c7a23426214226edc5d0d714",
"sha256": "9cb761a92b517a16516c1da8561c73a6bb504c441f2ea58f6ace9e51f7ae3805"
},
"downloads": -1,
"filename": "pixltsnorm-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "d2a83ec4c7a23426214226edc5d0d714",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 26122,
"upload_time": "2025-03-16T20:53:03",
"upload_time_iso_8601": "2025-03-16T20:53:03.844815Z",
"url": "https://files.pythonhosted.org/packages/c9/fc/b37209d01e2505b2713d36caa17a4f27e52a70401d662717c3f843932031/pixltsnorm-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-03-16 20:53:03",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "iosefa",
"github_project": "pixltsnorm",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pixltsnorm"
}