├── .gitignore ├── CHANGES.txt ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── notebooks ├── cc-tutorial1_clustering.ipynb ├── cluster_gridsearch.ipynb ├── ethiopia-rf.ipynb ├── final_clusters.ipynb ├── fit_LSTM.ipynb ├── fit_LSTM_labeled.ipynb ├── fit_lstm_test.ipynb ├── rukwa-classified.ipynb └── satts-tutorial1_clustering.ipynb ├── requirements.txt ├── rukwa-mask.py ├── satts-tutorial1_clustering.ipynb ├── satts ├── __init__.py ├── tsclust.py ├── tsmask.py ├── tspredict.py ├── tstrain.py └── version.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.1.0: 2 | - initial release 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM developmentseed/geolambda:latest 2 | 3 | RUN \ 4 | yum makecache fast; 5 | 6 | RUN pip3 install --upgrade pip 7 | RUN pip3 install cython 8 | RUN pip3 install pyyaml h5py 9 | 10 | ENV \ 11 | PYCURL_SSL_LIBRARY=nss 12 | 13 | # install requirements 14 | WORKDIR /build 15 | COPY requirements*txt /build/ 16 | RUN \ 17 | pip3 install -r requirements.txt; 18 | #pip3 install -r requirements-dev.txt 19 | 20 | # Jupyter and Tensorboard ports 21 | EXPOSE 8888 6006 22 | 23 | # Store notebooks in this mounted directory 24 | VOLUME /notebooks 25 | 26 | CMD ["/run_jupyter.sh"] 27 | 28 | # install app 29 | COPY . /build 30 | RUN \ 31 | pip3 install . -v; \ 32 | rm -rf /build/*; 33 | 34 | WORKDIR /home/geolambda 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Development Seed 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pixel-level clustering and classification of multi-spectral, multi-temporal earth observation data 2 | 3 | This library contains classes and functions to generate datasets corresponding to spatial features from a time-series of satellite images. The impetus for this project was to develop an easy to use, high-level interface to numerous Python modules for the clustering and classification of land cover/land use (LULC) types, with an initial focus on classifying individual crop types in challenging geographies using a time-series of multi-spectral earth observatoin (EO) images. The use of a time-series of EO images better captures the dynamic nature of the appearance of crops and other LULC classes through a growing season, enabling more accurate model predictions. The functions and methods provided in this library can be used to generate EO reflectance time-series datasets and models for arbitraty vector data, e.g. points or polygons. 4 | 5 | ## Using this library 6 | The library is divided in to several components: 7 | 8 | 9 | 1. `tsmask`: provides functions to create a masked numpy arrays corresponding to areas of interest, as well as a `BandTimeSeries` object initialized using the maked array. Specific functions and objects include: 10 | 11 | - `raserize` utilizes the `osgeo` library and the underlying `gdal` functionaility to rasterize vector features from a shapefile and output a .tif file sharing the relevant metadata and dimensions as the reference image from which it was created. A `check_rasterize` function is also provided to confirm that the features were correclty "buned" into the raster layer. The resulting image can be characterized as a land cover "mask". 12 | 13 | - `mask_to_array` generates a 3D numpy array from the output of `rasterize`. Each element of the 3D array is a 2D array representing band reflectance values for a given date. Values in the 3D array that are not no-data values correspond to a land cover class burned in using `rasterize`. 14 | 15 | - `BandTimeSeries` objects contain information about time-series' of reflectance values for samples in a given land cover class, and methods to operate on and format the reflectance time-series. `BandTimeSeries` objects are initialized using an output from the `mask_to_array` function, along with arguments specifying the land cover class of the object, and the variable (band) name of the reflectance time-series. The `time_series_data_frame` method allows for interpolation of the time-series. 16 | 17 | 18 | 2. `tsclust`: provides a `TimeSeriesSample` class that is useful for generating a dataset from all or a subset of data contained in a `BandTimeSeries` and formating it for direct use in the functions and classes provided in the [`tslearn`](https://tslearn.readthedocs.io/en/latest/) library. 19 | 20 | - `TimeSeriesSample` take n_samples of the data in a `BandTimeSeries` and optionally smooth the time-series' using a Savgol signal smoothing. The `ts_dataset` method generates an object that can be used directly in the time series clustering and classification algorithms provided in the `tslearn` library. 21 | 22 | - `cluster_time_series` performs either `GlobalAlignmentKernelKMeans` or `TimeSeriesKMeans` (both from the `tslearn` library) on a `TimeSeriesSample` object. The user specifies the number of clusters as well as the distance metric used if the clustering algorithm is `TimeSeriesKMeans` (dynamic time warping or soft dynamic time warping). Sillhouette scores computed on the resulting clusters can optionally be returned. Alternative sets of hyperparamters for `cluster_times_series` can be tested using the `cluster_grid_search` function. 23 | 24 | - `cluster_mean_quantiles` and `plot_clusters` provide methods for inspecting and visualizing cluster results. 25 | 26 | 3. `tstrain` provides functions for extracting training datasets comprising time-series' of band reflectance values at known locations (x,y numpy array indices) from satelite scenes. 27 | 28 | - `random_ts_samples` takes n_samples from .csv files containging reflectance time-series data for a given land cover class. 29 | 30 | - `get_training_data` reads satellite scenes, e.g. scense corresponding to an areo of interest specified with [`sat-search`](https://github.com/sat-utils/sat-search) and download and saved using the default direcorty structure of`sat-search load`, into numpy arrays using functionaility from [`gippy`](https://gippy.readthedocs.io/en/latest/). The output is a long-form `pandas` dataframe with colums for date, feature (band-value), band reflectance value, the 2d array index, and a label corresponding to a samples land cover class. 31 | 32 | - `format_training_data` takes the ouput of `get_training_data` and reshapes it into a 3D numpy array of shape (n_samples, n_timesteps, n_features) suitable for use in a `Keras` Sequential model. Both x and y (optionally one-hot encoded) are returned. 33 | 34 | ## Examples 35 | 36 | Coming soon: Two jupyter notebook tutorials showcasing the functionality in this library 37 | 38 | 39 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | base: 6 | build: 7 | context: . 8 | image: 'temporal-crop-classification:latest' 9 | entrypoint: /bin/bash 10 | #env_file: .env 11 | volumes: 12 | - '.:/home/geolambda/work' 13 | 14 | test: 15 | image: 'developmentseed/temporal-crop-classification:latest' 16 | entrypoint: bash -c 'pytest test/' 17 | #env_file: .env 18 | volumes: 19 | - './test:/home/geolambda/test' 20 | -------------------------------------------------------------------------------- /notebooks/cluster_gridsearch.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 7, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import tsclust\n", 10 | "import pandas as pd" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 8, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "cropdf = pd.read_csv('/Users/jameysmith/Documents/sentinel2_tanz/aoiTS/lc_ndvi_ts/crop_ndvi_interp.csv')\n", 20 | "cropdf = cropdf.rename(columns={\"array_ind\": \"array_index\"})" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 13, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "# Number of unique pixels (time-series) is 83,403. This is the max 'n_samples' value\n", 30 | "pg = {\n", 31 | " 'time_seriesdf': [cropdf],\n", 32 | " 'n_samples': [10],\n", 33 | " 'cluster_alg': ['GAKM', 'TSKM'],\n", 34 | " 'n_clusters': list(range(2, 8)),\n", 35 | " 'smooth': [True],\n", 36 | " 'ts_var': ['ndvi'],\n", 37 | " 'window': [7],\n", 38 | " 'poly': [3],\n", 39 | " 'cluster_metric': ['dtw', 'softdtw'],\n", 40 | " 'score': [True]\n", 41 | "}" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 14, 47 | "metadata": { 48 | "scrolled": true 49 | }, 50 | "outputs": [ 51 | { 52 | "name": "stdout", 53 | "output_type": "stream", 54 | "text": [ 55 | "11.779 --> 10.628 --> 10.628 --> \n", 56 | "11.779 --> 10.628 --> 10.628 --> \n", 57 | "11.128 --> 9.907 --> 9.907 --> \n", 58 | "11.128 --> 9.907 --> 9.907 --> \n", 59 | "8.157 --> 7.027 --> 7.027 --> \n", 60 | "8.157 --> 7.027 --> 7.027 --> \n", 61 | "6.856 --> 5.577 --> 5.577 --> \n", 62 | "6.856 --> 5.577 --> 5.577 --> \n", 63 | "Resumed because of empty cluster\n", 64 | "Resumed because of empty cluster\n", 65 | "6.134 --> 4.177 --> 3.291 --> 3.291 --> \n", 66 | "Resumed because of empty cluster\n", 67 | "Resumed because of empty cluster\n", 68 | "6.134 --> 4.177 --> 3.291 --> 3.291 --> \n", 69 | "Resumed because of empty cluster\n", 70 | "Resumed because of empty cluster\n", 71 | "Resumed because of empty cluster\n", 72 | "Resumed because of empty cluster\n", 73 | "Resumed because of empty cluster\n", 74 | "Resumed because of empty cluster\n", 75 | "Resumed because of empty cluster\n", 76 | "Resumed because of empty cluster\n", 77 | "Resumed because of empty cluster\n", 78 | "Resumed because of empty cluster\n", 79 | "Resumed because of empty cluster\n", 80 | "Resumed because of empty cluster\n", 81 | "Resumed because of empty cluster\n", 82 | "Resumed because of empty cluster\n", 83 | "Resumed because of empty cluster\n", 84 | "Resumed because of empty cluster\n", 85 | "Resumed because of empty cluster\n", 86 | "Resumed because of empty cluster\n", 87 | "Resumed because of empty cluster\n", 88 | "Resumed because of empty cluster\n", 89 | "0.211 --> 0.064 --> 0.063 --> 0.063 --> \n", 90 | "17261.967 --> 17411.115 --> 17411.132 --> 17411.132 --> 17411.132 --> \n", 91 | "0.078 --> 0.038 --> 0.038 --> \n", 92 | "17385.340 --> 17438.902 --> 17439.120 --> 17439.126 --> 17439.127 --> 17439.127 --> 17439.127 --> \n", 93 | "0.076 --> 0.027 --> 0.022 --> 0.022 --> \n", 94 | "17443.264 --> 17487.108 --> 17487.176 --> 17487.178 --> 17487.178 --> \n", 95 | "0.056 --> 0.023 --> 0.018 --> 0.018 --> \n", 96 | "17468.368 --> 17507.731 --> 17507.799 --> 17507.800 --> 17507.800 --> \n", 97 | "0.030 --> 0.016 --> 0.016 --> \n", 98 | "17486.779 --> 17516.053 --> 17516.110 --> 17516.111 --> 17516.111 --> \n", 99 | "0.018 --> 0.007 --> 0.007 --> \n", 100 | "17497.761 --> 17525.153 --> 17525.212 --> 17525.213 --> 17525.213 --> \n" 101 | ] 102 | } 103 | ], 104 | "source": [ 105 | "# Grid search on crop land cover class\n", 106 | " " 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 17, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "# Get cluster dataframe corresponding to parameter combination with largest silhouette score\n", 116 | "lowscore = pg_dict['clusters'][pg_df['sil_score'].idxmax()]" 117 | ] 118 | } 119 | ], 120 | "metadata": { 121 | "kernelspec": { 122 | "display_name": "Python 3", 123 | "language": "python", 124 | "name": "python3" 125 | }, 126 | "language_info": { 127 | "codemirror_mode": { 128 | "name": "ipython", 129 | "version": 3 130 | }, 131 | "file_extension": ".py", 132 | "mimetype": "text/x-python", 133 | "name": "python", 134 | "nbconvert_exporter": "python", 135 | "pygments_lexer": "ipython3", 136 | "version": "3.6.5" 137 | } 138 | }, 139 | "nbformat": 4, 140 | "nbformat_minor": 2 141 | } 142 | -------------------------------------------------------------------------------- /notebooks/final_clusters.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is markdown" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import tsclust\n", 17 | "import pandas as pd" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "cropdf = pd.read_csv('/home/ec2-user/crop_ndvi_interp.csv')\n", 27 | "cropdf = cropdf.rename(columns={\"array_ind\": \"array_index\"})" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 3, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "cropts = tsclust.TimeSeriesSample(cropdf, n_samples=10000, ts_var='ndvi', seed=0).smooth()" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 4, 42 | "metadata": {}, 43 | "outputs": [ 44 | { 45 | "name": "stdout", 46 | "output_type": "stream", 47 | "text": [ 48 | "0.148 --> 0.057 --> 0.055 --> 0.054 --> 0.053 --> 0.053 --> 0.053 --> 0.053 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> 0.052 --> \n", 49 | "0.134 --> 0.053 --> 0.049 --> 0.048 --> 0.048 --> 0.048 --> 0.048 --> 0.048 --> 0.048 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> 0.047 --> \n", 50 | "0.093 --> 0.047 --> 0.046 --> 0.045 --> 0.044 --> 0.044 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> 0.043 --> \n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "clust4 = tsclust.cluster_time_series(cropts, cluster_alg='TSKM', n_clusters=4, cluster_metric='dtw')\n", 56 | "clust5 = tsclust.cluster_time_series(cropts, cluster_alg='TSKM', n_clusters=5, cluster_metric='dtw')\n", 57 | "clust6 = tsclust.cluster_time_series(cropts, cluster_alg='TSKM', n_clusters=6, cluster_metric='dtw')" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 5, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "clust4.to_csv('/home/ec2-user/final_clusters/4_clusters.csv', index=False)\n", 67 | "clust5.to_csv('/home/ec2-user/final_clusters/5_clusters.csv', index=False)\n", 68 | "clust6.to_csv('/home/ec2-user/final_clusters/6_clusters.csv', index=False)" 69 | ] 70 | } 71 | ], 72 | "metadata": { 73 | "kernelspec": { 74 | "display_name": "Python 3", 75 | "language": "python", 76 | "name": "python3" 77 | }, 78 | "language_info": { 79 | "codemirror_mode": { 80 | "name": "ipython", 81 | "version": 3 82 | }, 83 | "file_extension": ".py", 84 | "mimetype": "text/x-python", 85 | "name": "python", 86 | "nbconvert_exporter": "python", 87 | "pygments_lexer": "ipython3", 88 | "version": "3.6.5" 89 | } 90 | }, 91 | "nbformat": 4, 92 | "nbformat_minor": 2 93 | } 94 | -------------------------------------------------------------------------------- /notebooks/fit_LSTM.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Using TensorFlow backend.\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import tstrain\n", 18 | "import tsclust\n", 19 | "import pandas as pd\n", 20 | "from keras.models import Sequential\n", 21 | "from keras.layers import Dense\n", 22 | "from keras.layers import LSTM" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 3, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "### Create ndvi bands for each date in Sentinel-2 asset dataset ###\n", 32 | "\n", 33 | "# File path containing Sentinel-2 bands intersecting with AOI, organized by date \n", 34 | "#fp = '/home/ec2-user/Sentinel-2A' # <- all bands all dates\n", 35 | "fp = '/home/ec2-user/prediction_scenes/Sentinel-2A' # <- First 6 dates only; NDVI, green and blue bands \n", 36 | "\n", 37 | "# Band dictionary to match asset names with variables\n", 38 | "asset_dict = {'B02': 'blue',\n", 39 | " 'B03': 'green',\n", 40 | " 'B04': 'red',\n", 41 | " 'B08': 'nir'}\n", 42 | "\n", 43 | "# For now, create ndvi index only\n", 44 | "#indices = ['ndvi']" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "# # Perform calculation and save images to appropriate directory\n", 54 | "# tstrain.calulate_indices(fp, asset_dict, indices)" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 4, 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/plain": [ 65 | "(10000, 82)" 66 | ] 67 | }, 68 | "execution_count": 4, 69 | "metadata": {}, 70 | "output_type": "execute_result" 71 | } 72 | ], 73 | "source": [ 74 | "# Clustered NDVI time-series data for cropped area; 5 clusters\n", 75 | "clust5 = pd.read_csv('/home/ec2-user/sample/5_clusters.csv')\n", 76 | "clust5.shape" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 5, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "name": "stderr", 86 | "output_type": "stream", 87 | "text": [ 88 | "/home/ec2-user/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:15: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version\n", 89 | "of pandas will change to not sort by default.\n", 90 | "\n", 91 | "To accept the future behavior, pass 'sort=True'.\n", 92 | "\n", 93 | "To retain the current behavior and silence the warning, pass sort=False\n", 94 | "\n", 95 | " from ipykernel import kernelapp as app\n" 96 | ] 97 | } 98 | ], 99 | "source": [ 100 | "# Combine samples from clustered cropped area, and other land cover classes into single dataset\n", 101 | "# for model fitting. `lcts` is file path to .csv files containing NDVI time-series' from vegetation,\n", 102 | "# urban, and water land cover classes.\n", 103 | "lcts = '/home/ec2-user/sample/land_cover_samples'\n", 104 | "\n", 105 | "# Take n_samples of each non-crop land cover class\n", 106 | "noncrop_samples = tstrain.random_ts_samples(lcts, n_samples=10000, seed=0)\n", 107 | "\n", 108 | "# Rename and drop columns to allow concatination of crop and non-crop samples\n", 109 | "clust5 = clust5.rename(columns={'cluster': 'label'})\n", 110 | "clust5 = clust5.drop(['lc'], axis=1)\n", 111 | "\n", 112 | "# Combine datasets\n", 113 | "dlist = [clust5, noncrop_samples]\n", 114 | "allsamples = pd.concat(dlist, ignore_index=True)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 6, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "# Using raster index locations from `allsamples` (Step 1), extract band reflectance values from a time-series of \n", 124 | "# scenes contained in a directory generated using the default sat-search directory structure\n", 125 | "\n", 126 | "# Extract training data from Sentinel-2 time-series\n", 127 | "training_data = tstrain.get_training_data(fp, asset_dict, allsamples, standardize=False)" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 7, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "data": { 137 | "text/html": [ 138 | "
\n", 139 | "\n", 152 | "\n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | "
valuelabel
dateindfeature
2016-11-16(2428, 4479)blue0.1081veg
green0.1010veg
ndvi0.2294veg
2017-02-14(2428, 4479)blue0.0854veg
green0.0870veg
ndvi0.6892veg
2017-04-05(2428, 4479)blue0.0766veg
green0.0841veg
ndvi0.7618veg
2017-05-05(2428, 4479)blue0.0815veg
green0.0804veg
ndvi0.7272veg
2017-05-15(2428, 4479)blue0.0772veg
green0.0740veg
ndvi0.7265veg
2017-05-25(2428, 4479)blue0.0760veg
green0.0706veg
ndvi0.6695veg
2016-11-16(2429, 4441)blue0.1048veg
green0.0963veg
\n", 286 | "
" 287 | ], 288 | "text/plain": [ 289 | " value label\n", 290 | "date ind feature \n", 291 | "2016-11-16 (2428, 4479) blue 0.1081 veg\n", 292 | " green 0.1010 veg\n", 293 | " ndvi 0.2294 veg\n", 294 | "2017-02-14 (2428, 4479) blue 0.0854 veg\n", 295 | " green 0.0870 veg\n", 296 | " ndvi 0.6892 veg\n", 297 | "2017-04-05 (2428, 4479) blue 0.0766 veg\n", 298 | " green 0.0841 veg\n", 299 | " ndvi 0.7618 veg\n", 300 | "2017-05-05 (2428, 4479) blue 0.0815 veg\n", 301 | " green 0.0804 veg\n", 302 | " ndvi 0.7272 veg\n", 303 | "2017-05-15 (2428, 4479) blue 0.0772 veg\n", 304 | " green 0.0740 veg\n", 305 | " ndvi 0.7265 veg\n", 306 | "2017-05-25 (2428, 4479) blue 0.0760 veg\n", 307 | " green 0.0706 veg\n", 308 | " ndvi 0.6695 veg\n", 309 | "2016-11-16 (2429, 4441) blue 0.1048 veg\n", 310 | " green 0.0963 veg" 311 | ] 312 | }, 313 | "execution_count": 7, 314 | "metadata": {}, 315 | "output_type": "execute_result" 316 | } 317 | ], 318 | "source": [ 319 | "i = training_data.set_index(['date', 'ind', 'feature'])\n", 320 | "i.head(20)" 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": 11, 326 | "metadata": {}, 327 | "outputs": [], 328 | "source": [ 329 | "#training_data.to_csv('/home/ec2-user/training_data.csv')\n", 330 | "#training_data.to_csv('/home/ec2-user/training_data_few_bands.csv')" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 8, 336 | "metadata": {}, 337 | "outputs": [], 338 | "source": [ 339 | "# Use first 7 dates in time series (Nov 16, 2016 through June 6, 2017)\n", 340 | "dates = training_data.date.unique()\n", 341 | "datesub = dates[0:6]\n", 342 | "trainsub = training_data[training_data['date'].isin(datesub)]" 343 | ] 344 | }, 345 | { 346 | "cell_type": "code", 347 | "execution_count": 9, 348 | "metadata": {}, 349 | "outputs": [ 350 | { 351 | "data": { 352 | "text/plain": [ 353 | "array(['2016-11-16', '2017-02-14', '2017-04-05', '2017-05-05',\n", 354 | " '2017-05-15', '2017-05-25'], dtype=object)" 355 | ] 356 | }, 357 | "execution_count": 9, 358 | "metadata": {}, 359 | "output_type": "execute_result" 360 | } 361 | ], 362 | "source": [ 363 | "datesub" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 10, 369 | "metadata": {}, 370 | "outputs": [], 371 | "source": [ 372 | "# Fit a LSTM recurrent neural network. In this 'toy' example, a total of 25,000 samples are used to fit a model.\n", 373 | "# including 10,000 from the clustered \"cropped\" class, and 5,000 from each of the \"water\", \"urban\" and\n", 374 | "# \"vegetation\" classes. The bands (features) include red, blue, green, and nir. Y labels are numerically\n", 375 | "# encoded, and converted to \"one-hot\" vectors.\n", 376 | "\n", 377 | "# Format training data into correct 3D array of shape (n_samples, n_timesetps, n_features) required to fit a\n", 378 | "# Keras LSTM model. N_features corresponds to number of bands included in training data\n", 379 | "\n", 380 | "class_codes, x, y = tstrain.format_training_data(trainsub)" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 12, 386 | "metadata": {}, 387 | "outputs": [ 388 | { 389 | "data": { 390 | "text/plain": [ 391 | "{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 'urban', 6: 'veg', 7: 'water'}" 392 | ] 393 | }, 394 | "execution_count": 12, 395 | "metadata": {}, 396 | "output_type": "execute_result" 397 | } 398 | ], 399 | "source": [ 400 | "class_codes" 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 14, 406 | "metadata": {}, 407 | "outputs": [], 408 | "source": [ 409 | "# Split training and test data\n", 410 | "x_train, x_test, y_train, y_test = tstrain.split_train_test(x, y, seed=0)" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": 16, 416 | "metadata": {}, 417 | "outputs": [], 418 | "source": [ 419 | "# def standardize_features(x_train, x_test):\n", 420 | "# '''Standardize features of 3D array formated for keras sequential model'''\n", 421 | "\n", 422 | "# mu = x_train.mean(axis=(0, 1))\n", 423 | "# sd = x_train.std(axis=(0, 1))\n", 424 | "\n", 425 | "# x_train_norm = (x_train - mu) / sd\n", 426 | "# x_test_norm = (x_test - mu) / sd\n", 427 | "\n", 428 | "# return mu, sd, x_train_norm, x_test_norm" 429 | ] 430 | }, 431 | { 432 | "cell_type": "code", 433 | "execution_count": 15, 434 | "metadata": {}, 435 | "outputs": [], 436 | "source": [ 437 | "# Standardize features\n", 438 | "mu, sd, x_train_norm, x_test_norm = tstrain.standardize_features(x_train, x_test)" 439 | ] 440 | }, 441 | { 442 | "cell_type": "code", 443 | "execution_count": 17, 444 | "metadata": {}, 445 | "outputs": [ 446 | { 447 | "name": "stdout", 448 | "output_type": "stream", 449 | "text": [ 450 | "Epoch 1/50\n", 451 | " - 24s - loss: 0.6634 - categorical_accuracy: 0.7589\n", 452 | "Epoch 2/50\n", 453 | " - 21s - loss: 0.4108 - categorical_accuracy: 0.8467\n", 454 | "Epoch 3/50\n", 455 | " - 21s - loss: 0.3545 - categorical_accuracy: 0.8662\n", 456 | "Epoch 4/50\n", 457 | " - 21s - loss: 0.3263 - categorical_accuracy: 0.8755\n", 458 | "Epoch 5/50\n", 459 | " - 21s - loss: 0.3030 - categorical_accuracy: 0.8853\n", 460 | "Epoch 6/50\n", 461 | " - 21s - loss: 0.2863 - categorical_accuracy: 0.8896\n", 462 | "Epoch 7/50\n", 463 | " - 21s - loss: 0.2685 - categorical_accuracy: 0.8964\n", 464 | "Epoch 8/50\n", 465 | " - 21s - loss: 0.2539 - categorical_accuracy: 0.9024\n", 466 | "Epoch 9/50\n", 467 | " - 21s - loss: 0.2416 - categorical_accuracy: 0.9057\n", 468 | "Epoch 10/50\n", 469 | " - 21s - loss: 0.2365 - categorical_accuracy: 0.9074\n", 470 | "Epoch 11/50\n", 471 | " - 21s - loss: 0.2256 - categorical_accuracy: 0.9119\n", 472 | "Epoch 12/50\n", 473 | " - 21s - loss: 0.2214 - categorical_accuracy: 0.9129\n", 474 | "Epoch 13/50\n", 475 | " - 21s - loss: 0.2125 - categorical_accuracy: 0.9166\n", 476 | "Epoch 14/50\n", 477 | " - 21s - loss: 0.2071 - categorical_accuracy: 0.9188\n", 478 | "Epoch 15/50\n", 479 | " - 21s - loss: 0.2015 - categorical_accuracy: 0.9212\n", 480 | "Epoch 16/50\n", 481 | " - 21s - loss: 0.1958 - categorical_accuracy: 0.9220\n", 482 | "Epoch 17/50\n", 483 | " - 21s - loss: 0.1936 - categorical_accuracy: 0.9231\n", 484 | "Epoch 18/50\n", 485 | " - 21s - loss: 0.1893 - categorical_accuracy: 0.9243\n", 486 | "Epoch 19/50\n", 487 | " - 21s - loss: 0.1835 - categorical_accuracy: 0.9271\n", 488 | "Epoch 20/50\n", 489 | " - 21s - loss: 0.1827 - categorical_accuracy: 0.9270\n", 490 | "Epoch 21/50\n", 491 | " - 21s - loss: 0.1773 - categorical_accuracy: 0.9295\n", 492 | "Epoch 22/50\n", 493 | " - 21s - loss: 0.1738 - categorical_accuracy: 0.9303\n", 494 | "Epoch 23/50\n", 495 | " - 21s - loss: 0.1774 - categorical_accuracy: 0.9299\n", 496 | "Epoch 24/50\n", 497 | " - 21s - loss: 0.1691 - categorical_accuracy: 0.9340\n", 498 | "Epoch 25/50\n", 499 | " - 21s - loss: 0.1660 - categorical_accuracy: 0.9329\n", 500 | "Epoch 26/50\n", 501 | " - 21s - loss: 0.1639 - categorical_accuracy: 0.9330\n", 502 | "Epoch 27/50\n", 503 | " - 21s - loss: 0.1719 - categorical_accuracy: 0.9301\n", 504 | "Epoch 28/50\n", 505 | " - 21s - loss: 0.1601 - categorical_accuracy: 0.9371\n", 506 | "Epoch 29/50\n", 507 | " - 21s - loss: 0.1558 - categorical_accuracy: 0.9364\n", 508 | "Epoch 30/50\n", 509 | " - 21s - loss: 0.1558 - categorical_accuracy: 0.9363\n", 510 | "Epoch 31/50\n", 511 | " - 21s - loss: 0.1561 - categorical_accuracy: 0.9366\n", 512 | "Epoch 32/50\n", 513 | " - 21s - loss: 0.1528 - categorical_accuracy: 0.9371\n", 514 | "Epoch 33/50\n", 515 | " - 21s - loss: 0.1497 - categorical_accuracy: 0.9393\n", 516 | "Epoch 34/50\n", 517 | " - 21s - loss: 0.1492 - categorical_accuracy: 0.9388\n", 518 | "Epoch 35/50\n", 519 | " - 21s - loss: 0.1444 - categorical_accuracy: 0.9398\n", 520 | "Epoch 36/50\n", 521 | " - 21s - loss: 0.1485 - categorical_accuracy: 0.9386\n", 522 | "Epoch 37/50\n", 523 | " - 21s - loss: 0.1478 - categorical_accuracy: 0.9393\n", 524 | "Epoch 38/50\n", 525 | " - 21s - loss: 0.1517 - categorical_accuracy: 0.9381\n", 526 | "Epoch 39/50\n", 527 | " - 21s - loss: 0.1417 - categorical_accuracy: 0.9408\n", 528 | "Epoch 40/50\n", 529 | " - 21s - loss: 0.1401 - categorical_accuracy: 0.9429\n", 530 | "Epoch 41/50\n", 531 | " - 21s - loss: 0.1365 - categorical_accuracy: 0.9454\n", 532 | "Epoch 42/50\n", 533 | " - 21s - loss: 0.1389 - categorical_accuracy: 0.9440\n", 534 | "Epoch 43/50\n", 535 | " - 21s - loss: 0.1371 - categorical_accuracy: 0.9435\n", 536 | "Epoch 44/50\n", 537 | " - 21s - loss: 0.1337 - categorical_accuracy: 0.9449\n", 538 | "Epoch 45/50\n", 539 | " - 21s - loss: 0.1329 - categorical_accuracy: 0.9466\n", 540 | "Epoch 46/50\n", 541 | " - 21s - loss: 0.1362 - categorical_accuracy: 0.9444\n", 542 | "Epoch 47/50\n", 543 | " - 21s - loss: 0.1365 - categorical_accuracy: 0.9424\n", 544 | "Epoch 48/50\n", 545 | " - 21s - loss: 0.1294 - categorical_accuracy: 0.9474\n", 546 | "Epoch 49/50\n", 547 | " - 21s - loss: 0.1316 - categorical_accuracy: 0.9459\n", 548 | "Epoch 50/50\n", 549 | " - 21s - loss: 0.1272 - categorical_accuracy: 0.9464\n" 550 | ] 551 | }, 552 | { 553 | "data": { 554 | "text/plain": [ 555 | "" 556 | ] 557 | }, 558 | "execution_count": 17, 559 | "metadata": {}, 560 | "output_type": "execute_result" 561 | } 562 | ], 563 | "source": [ 564 | "# Train LSTM model\n", 565 | "n_timesteps = len(trainsub['date'].unique())\n", 566 | "n_features = len(trainsub['feature'].unique())\n", 567 | "\n", 568 | "model = Sequential()\n", 569 | "model.add(LSTM(32, activation='relu', return_sequences=True, input_shape=(n_timesteps, n_features)))\n", 570 | "model.add(LSTM(32, activation='relu', return_sequences=True))\n", 571 | "model.add(LSTM(32, activation='relu', return_sequences=True))\n", 572 | "model.add(LSTM(32, activation='relu', return_sequences=True))\n", 573 | "model.add(LSTM(32))\n", 574 | "model.add(Dense(activation='softmax', units=y.shape[1]))\n", 575 | "model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['categorical_accuracy'])\n", 576 | "model.fit(x_train_norm, y_train, epochs=50, batch_size=32, verbose=2)" 577 | ] 578 | }, 579 | { 580 | "cell_type": "code", 581 | "execution_count": 18, 582 | "metadata": {}, 583 | "outputs": [ 584 | { 585 | "name": "stdout", 586 | "output_type": "stream", 587 | "text": [ 588 | "7191/7191 [==============================] - 2s 215us/step\n" 589 | ] 590 | }, 591 | { 592 | "data": { 593 | "text/plain": [ 594 | "0.9413155333055208" 595 | ] 596 | }, 597 | "execution_count": 18, 598 | "metadata": {}, 599 | "output_type": "execute_result" 600 | } 601 | ], 602 | "source": [ 603 | "# Model accuracy\n", 604 | "_, accuracy = model.evaluate(x_test_norm, y_test, batch_size=32)\n", 605 | "accuracy" 606 | ] 607 | }, 608 | { 609 | "cell_type": "code", 610 | "execution_count": 19, 611 | "metadata": {}, 612 | "outputs": [ 613 | { 614 | "data": { 615 | "text/html": [ 616 | "
\n", 617 | "\n", 630 | "\n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | " \n", 714 | " \n", 715 | " \n", 716 | " \n", 717 | " \n", 718 | " \n", 719 | " \n", 720 | " \n", 721 | " \n", 722 | " \n", 723 | " \n", 724 | " \n", 725 | " \n", 726 | " \n", 727 | " \n", 728 | " \n", 729 | " \n", 730 | " \n", 731 | " \n", 732 | " \n", 733 | " \n", 734 | " \n", 735 | " \n", 736 | " \n", 737 | " \n", 738 | " \n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | "
01234urbanvegwaterrecall
0377333106200.893365
172325166100200.754060
218254531631200.804618
35815700000.802817
41113101235001200.715746
urban730001179100.990756
veg521081198500.991508
water000000020231.000000
\n", 744 | "
" 745 | ], 746 | "text/plain": [ 747 | " 0 1 2 3 4 urban veg water recall\n", 748 | "0 377 33 3 1 0 6 2 0 0.893365\n", 749 | "1 72 325 16 6 10 0 2 0 0.754060\n", 750 | "2 18 25 453 1 63 1 2 0 0.804618\n", 751 | "3 5 8 1 57 0 0 0 0 0.802817\n", 752 | "4 11 13 101 2 350 0 12 0 0.715746\n", 753 | "urban 7 3 0 0 0 1179 1 0 0.990756\n", 754 | "veg 5 2 1 0 8 1 1985 0 0.991508\n", 755 | "water 0 0 0 0 0 0 0 2023 1.000000" 756 | ] 757 | }, 758 | "execution_count": 19, 759 | "metadata": {}, 760 | "output_type": "execute_result" 761 | } 762 | ], 763 | "source": [ 764 | "# Confusion matrix\n", 765 | "tstrain.conf_mat(x_test_norm, y_test, model, class_codes)" 766 | ] 767 | }, 768 | { 769 | "cell_type": "code", 770 | "execution_count": 20, 771 | "metadata": {}, 772 | "outputs": [], 773 | "source": [ 774 | "# serialize model to JSON\n", 775 | "model_json = model.to_json()\n", 776 | "with open(\"/home/ec2-user/model_improved.json\", \"w\") as json_file:\n", 777 | " json_file.write(model_json)" 778 | ] 779 | }, 780 | { 781 | "cell_type": "code", 782 | "execution_count": 21, 783 | "metadata": {}, 784 | "outputs": [ 785 | { 786 | "name": "stdout", 787 | "output_type": "stream", 788 | "text": [ 789 | "Saved model to disk\n" 790 | ] 791 | } 792 | ], 793 | "source": [ 794 | "# serialize weights to HDF5\n", 795 | "model.save_weights(\"/home/ec2-user/model.h5\")\n", 796 | "print(\"Saved model to disk\")" 797 | ] 798 | }, 799 | { 800 | "cell_type": "code", 801 | "execution_count": 22, 802 | "metadata": { 803 | "scrolled": true 804 | }, 805 | "outputs": [ 806 | { 807 | "name": "stderr", 808 | "output_type": "stream", 809 | "text": [ 810 | "/home/ec2-user/anaconda3/lib/python3.6/site-packages/matplotlib/cbook/deprecation.py:107: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.\n", 811 | " warnings.warn(message, mplDeprecation, stacklevel=1)\n" 812 | ] 813 | }, 814 | { 815 | "data": { 816 | "image/png": "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\n", 817 | "text/plain": [ 818 | "
" 819 | ] 820 | }, 821 | "metadata": {}, 822 | "output_type": "display_data" 823 | } 824 | ], 825 | "source": [ 826 | "cropclusts = clust5.rename(columns={'label': 'cluster'})\n", 827 | "tsclust.plot_clusters(cropclusts, fill=False)" 828 | ] 829 | }, 830 | { 831 | "cell_type": "code", 832 | "execution_count": null, 833 | "metadata": {}, 834 | "outputs": [], 835 | "source": [] 836 | } 837 | ], 838 | "metadata": { 839 | "kernelspec": { 840 | "display_name": "Python 3", 841 | "language": "python", 842 | "name": "python3" 843 | }, 844 | "language_info": { 845 | "codemirror_mode": { 846 | "name": "ipython", 847 | "version": 3 848 | }, 849 | "file_extension": ".py", 850 | "mimetype": "text/x-python", 851 | "name": "python", 852 | "nbconvert_exporter": "python", 853 | "pygments_lexer": "ipython3", 854 | "version": "3.6.5" 855 | } 856 | }, 857 | "nbformat": 4, 858 | "nbformat_minor": 2 859 | } 860 | -------------------------------------------------------------------------------- /notebooks/fit_LSTM_labeled.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Using TensorFlow backend.\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import tstrain\n", 18 | "import tsclust\n", 19 | "import pandas as pd\n", 20 | "from keras.models import Sequential\n", 21 | "from keras.layers import Dense\n", 22 | "from keras.layers import LSTM" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 3, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "# Training data - no crop labels\n", 32 | "#td = pd.read_csv('/home/ec2-user/training_data_large.csv')\n", 33 | "td = pd.read_csv('/home/ec2-user/training_data_few_bands.csv') # <- only ndvi, green and blue bands" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 4, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "td = td.drop(['Unnamed: 0'], axis=1)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 5, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "# Weird unexpexted strings\n", 52 | "td.loc[td['label'] == '0', 'label'] = 0\n", 53 | "td.loc[td['label'] == '1', 'label'] = 1\n", 54 | "td.loc[td['label'] == '2', 'label'] = 2\n", 55 | "td.loc[td['label'] == '3', 'label'] = 3\n", 56 | "td.loc[td['label'] == '4', 'label'] = 4" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 6, 62 | "metadata": {}, 63 | "outputs": [ 64 | { 65 | "data": { 66 | "text/plain": [ 67 | "array(['veg', 'water', 4, 2, 0, 1, 3, 'urban'], dtype=object)" 68 | ] 69 | }, 70 | "execution_count": 6, 71 | "metadata": {}, 72 | "output_type": "execute_result" 73 | } 74 | ], 75 | "source": [ 76 | "td.label.unique()" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 7, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "# Group clusters 2 and 4 as \"maize\"; group clusters 0 and 1 as \"crop_2\"; leave cluster 3 alone, call it \"crop_3\"\n", 86 | "td.loc[td['label'] == 2, 'label'] = \"maize\"\n", 87 | "td.loc[td['label'] == 4, 'label'] = \"maize\"\n", 88 | "td.loc[td['label'] == 0, 'label'] = \"crop_2\"\n", 89 | "td.loc[td['label'] == 1, 'label'] = \"crop_2\"\n", 90 | "td.loc[td['label'] == 3, 'label'] = \"crop_3\"" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 8, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "data": { 100 | "text/plain": [ 101 | "array(['veg', 'water', 'maize', 'crop_2', 'crop_3', 'urban'], dtype=object)" 102 | ] 103 | }, 104 | "execution_count": 8, 105 | "metadata": {}, 106 | "output_type": "execute_result" 107 | } 108 | ], 109 | "source": [ 110 | "td.label.unique()" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 9, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "# Use first 6 dates in time series (Nov 16, 2016 through May 25, 2017)\n", 120 | "dates = td.date.unique()\n", 121 | "datesub = dates[0:6]\n", 122 | "trainsub = td[td['date'].isin(datesub)]" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 13, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "# Fit a LSTM recurrent neural network. In this 'toy' example, a total of 25,000 samples are used to fit a model.\n", 132 | "# including 10,000 from the clustered \"cropped\" class, and 5,000 from each of the \"water\", \"urban\" and\n", 133 | "# \"vegetation\" classes. The bands (features) include red, blue, green, and nir. Y labels are numerically\n", 134 | "# encoded, and converted to \"one-hot\" vectors.\n", 135 | "\n", 136 | "# Format training data into correct 3D array of shape (n_samples, n_timesetps, n_features) required to fit a\n", 137 | "# Keras LSTM model. N_features corresponds to number of bands included in training data\n", 138 | "\n", 139 | "class_codes, x, y = tstrain.format_training_data(trainsub)" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 15, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "# Split training and test data\n", 149 | "x_train, x_test, y_train, y_test = tstrain.split_train_test(x, y, seed=0)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 16, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "# Standardize features\n", 159 | "mu, sd, x_train_norm, x_test_norm = tstrain.standardize_features(x_train, x_test)" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 17, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "import numpy as np\n", 169 | "np.save('/home/ec2-user/mu.npy', mu)\n", 170 | "np.save('/home/ec2-user/sd.npy', sd)" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 19, 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "name": "stdout", 180 | "output_type": "stream", 181 | "text": [ 182 | "Epoch 1/50\n", 183 | " - 25s - loss: 0.5378 - categorical_accuracy: 0.7764\n", 184 | "Epoch 2/50\n", 185 | " - 22s - loss: 0.3194 - categorical_accuracy: 0.8797\n", 186 | "Epoch 3/50\n", 187 | " - 22s - loss: 0.2674 - categorical_accuracy: 0.8996\n", 188 | "Epoch 4/50\n", 189 | " - 22s - loss: 0.2329 - categorical_accuracy: 0.9137\n", 190 | "Epoch 5/50\n", 191 | " - 22s - loss: 0.2193 - categorical_accuracy: 0.9179\n", 192 | "Epoch 6/50\n", 193 | " - 22s - loss: 0.1960 - categorical_accuracy: 0.9267\n", 194 | "Epoch 7/50\n", 195 | " - 22s - loss: 0.1796 - categorical_accuracy: 0.9318\n", 196 | "Epoch 8/50\n", 197 | " - 22s - loss: 0.1682 - categorical_accuracy: 0.9360\n", 198 | "Epoch 9/50\n", 199 | " - 22s - loss: 0.1584 - categorical_accuracy: 0.9412\n", 200 | "Epoch 10/50\n", 201 | " - 22s - loss: 0.1488 - categorical_accuracy: 0.9443\n", 202 | "Epoch 11/50\n", 203 | " - 22s - loss: 0.1448 - categorical_accuracy: 0.9466\n", 204 | "Epoch 12/50\n", 205 | " - 22s - loss: 0.1380 - categorical_accuracy: 0.9479\n", 206 | "Epoch 13/50\n", 207 | " - 22s - loss: 0.1282 - categorical_accuracy: 0.9517\n", 208 | "Epoch 14/50\n", 209 | " - 22s - loss: 0.1251 - categorical_accuracy: 0.9535\n", 210 | "Epoch 15/50\n", 211 | " - 22s - loss: 0.1187 - categorical_accuracy: 0.9553\n", 212 | "Epoch 16/50\n", 213 | " - 22s - loss: 0.1170 - categorical_accuracy: 0.9573\n", 214 | "Epoch 17/50\n", 215 | " - 22s - loss: 0.1099 - categorical_accuracy: 0.9582\n", 216 | "Epoch 18/50\n", 217 | " - 22s - loss: 0.1082 - categorical_accuracy: 0.9604\n", 218 | "Epoch 19/50\n", 219 | " - 22s - loss: 0.1025 - categorical_accuracy: 0.9614\n", 220 | "Epoch 20/50\n", 221 | " - 22s - loss: 0.1019 - categorical_accuracy: 0.9612\n", 222 | "Epoch 21/50\n", 223 | " - 22s - loss: 0.0971 - categorical_accuracy: 0.9631\n", 224 | "Epoch 22/50\n", 225 | " - 22s - loss: 0.0940 - categorical_accuracy: 0.9640\n", 226 | "Epoch 23/50\n", 227 | " - 22s - loss: 0.0950 - categorical_accuracy: 0.9652\n", 228 | "Epoch 24/50\n", 229 | " - 22s - loss: 0.0906 - categorical_accuracy: 0.9654\n", 230 | "Epoch 25/50\n", 231 | " - 22s - loss: 0.0910 - categorical_accuracy: 0.9656\n", 232 | "Epoch 26/50\n", 233 | " - 22s - loss: 0.0865 - categorical_accuracy: 0.9669\n", 234 | "Epoch 27/50\n", 235 | " - 22s - loss: 0.0843 - categorical_accuracy: 0.9679\n", 236 | "Epoch 28/50\n", 237 | " - 22s - loss: 0.0843 - categorical_accuracy: 0.9681\n", 238 | "Epoch 29/50\n", 239 | " - 22s - loss: 0.0842 - categorical_accuracy: 0.9676\n", 240 | "Epoch 30/50\n", 241 | " - 22s - loss: 0.0774 - categorical_accuracy: 0.9704\n", 242 | "Epoch 31/50\n", 243 | " - 22s - loss: 0.0802 - categorical_accuracy: 0.9699\n", 244 | "Epoch 32/50\n", 245 | " - 22s - loss: 0.0759 - categorical_accuracy: 0.9712\n", 246 | "Epoch 33/50\n", 247 | " - 22s - loss: 0.0779 - categorical_accuracy: 0.9707\n", 248 | "Epoch 34/50\n", 249 | " - 21s - loss: 0.0770 - categorical_accuracy: 0.9706\n", 250 | "Epoch 35/50\n", 251 | " - 22s - loss: 0.0753 - categorical_accuracy: 0.9717\n", 252 | "Epoch 36/50\n", 253 | " - 22s - loss: 0.0717 - categorical_accuracy: 0.9719\n", 254 | "Epoch 37/50\n", 255 | " - 22s - loss: 0.0724 - categorical_accuracy: 0.9734\n", 256 | "Epoch 38/50\n", 257 | " - 22s - loss: 0.0705 - categorical_accuracy: 0.9732\n", 258 | "Epoch 39/50\n", 259 | " - 22s - loss: 0.0685 - categorical_accuracy: 0.9736\n", 260 | "Epoch 40/50\n", 261 | " - 22s - loss: 0.0693 - categorical_accuracy: 0.9738\n", 262 | "Epoch 41/50\n", 263 | " - 22s - loss: 0.0665 - categorical_accuracy: 0.9736\n", 264 | "Epoch 42/50\n", 265 | " - 22s - loss: 0.0675 - categorical_accuracy: 0.9737\n", 266 | "Epoch 43/50\n", 267 | " - 22s - loss: 0.0636 - categorical_accuracy: 0.9757\n", 268 | "Epoch 44/50\n", 269 | " - 22s - loss: 0.0669 - categorical_accuracy: 0.9738\n", 270 | "Epoch 45/50\n", 271 | " - 22s - loss: 0.0622 - categorical_accuracy: 0.9759\n", 272 | "Epoch 46/50\n", 273 | " - 21s - loss: 0.0643 - categorical_accuracy: 0.9750\n", 274 | "Epoch 47/50\n", 275 | " - 21s - loss: 0.0648 - categorical_accuracy: 0.9756\n", 276 | "Epoch 48/50\n", 277 | " - 21s - loss: 0.0638 - categorical_accuracy: 0.9755\n", 278 | "Epoch 49/50\n", 279 | " - 22s - loss: 0.0647 - categorical_accuracy: 0.9753\n", 280 | "Epoch 50/50\n", 281 | " - 22s - loss: 0.0578 - categorical_accuracy: 0.9774\n" 282 | ] 283 | }, 284 | { 285 | "data": { 286 | "text/plain": [ 287 | "" 288 | ] 289 | }, 290 | "execution_count": 19, 291 | "metadata": {}, 292 | "output_type": "execute_result" 293 | } 294 | ], 295 | "source": [ 296 | "# Train LSTM model\n", 297 | "n_timesteps = len(trainsub['date'].unique())\n", 298 | "n_features = len(trainsub['feature'].unique())\n", 299 | "\n", 300 | "model = Sequential()\n", 301 | "model.add(LSTM(32, activation='relu', return_sequences=True, input_shape=(n_timesteps, n_features)))\n", 302 | "model.add(LSTM(32, activation='relu', return_sequences=True))\n", 303 | "model.add(LSTM(32, activation='relu', return_sequences=True))\n", 304 | "model.add(LSTM(32, activation='relu', return_sequences=True))\n", 305 | "model.add(LSTM(32))\n", 306 | "model.add(Dense(activation='softmax', units=y.shape[1]))\n", 307 | "model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['categorical_accuracy'])\n", 308 | "model.fit(x_train_norm, y_train, epochs=50, batch_size=32, verbose=2)" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": 20, 314 | "metadata": {}, 315 | "outputs": [ 316 | { 317 | "name": "stdout", 318 | "output_type": "stream", 319 | "text": [ 320 | "7191/7191 [==============================] - 2s 217us/step\n" 321 | ] 322 | }, 323 | { 324 | "data": { 325 | "text/plain": [ 326 | "0.9732999582811848" 327 | ] 328 | }, 329 | "execution_count": 20, 330 | "metadata": {}, 331 | "output_type": "execute_result" 332 | } 333 | ], 334 | "source": [ 335 | "# Model accuracy\n", 336 | "_, accuracy = model.evaluate(x_test_norm, y_test, batch_size=32)\n", 337 | "accuracy" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": 21, 343 | "metadata": {}, 344 | "outputs": [ 345 | { 346 | "data": { 347 | "text/html": [ 348 | "
\n", 349 | "\n", 362 | "\n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | "
crop_2crop_3maizeurbanvegwaterrecall
crop_279873161100.935522
crop_3115910000.830986
maize51597801800.929658
urban11011177100.989076
veg150212196400.981019
water0000020231.000000
\n", 438 | "
" 439 | ], 440 | "text/plain": [ 441 | " crop_2 crop_3 maize urban veg water recall\n", 442 | "crop_2 798 7 31 6 11 0 0.935522\n", 443 | "crop_3 11 59 1 0 0 0 0.830986\n", 444 | "maize 51 5 978 0 18 0 0.929658\n", 445 | "urban 11 0 1 1177 1 0 0.989076\n", 446 | "veg 15 0 21 2 1964 0 0.981019\n", 447 | "water 0 0 0 0 0 2023 1.000000" 448 | ] 449 | }, 450 | "execution_count": 21, 451 | "metadata": {}, 452 | "output_type": "execute_result" 453 | } 454 | ], 455 | "source": [ 456 | "# Confusion matrix\n", 457 | "tstrain.conf_mat(x_test_norm, y_test, model, class_codes)" 458 | ] 459 | }, 460 | { 461 | "cell_type": "code", 462 | "execution_count": 22, 463 | "metadata": {}, 464 | "outputs": [], 465 | "source": [ 466 | "# serialize model to JSON\n", 467 | "model_json = model.to_json()\n", 468 | "with open(\"/home/ec2-user/model_labeled.json\", \"w\") as json_file:\n", 469 | " json_file.write(model_json)" 470 | ] 471 | }, 472 | { 473 | "cell_type": "code", 474 | "execution_count": 23, 475 | "metadata": {}, 476 | "outputs": [ 477 | { 478 | "name": "stdout", 479 | "output_type": "stream", 480 | "text": [ 481 | "Saved model to disk\n" 482 | ] 483 | } 484 | ], 485 | "source": [ 486 | "# serialize weights to HDF5\n", 487 | "model.save_weights(\"/home/ec2-user/model_labeled.h5\")\n", 488 | "print(\"Saved model to disk\")" 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": null, 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [] 497 | } 498 | ], 499 | "metadata": { 500 | "kernelspec": { 501 | "display_name": "Python 3", 502 | "language": "python", 503 | "name": "python3" 504 | }, 505 | "language_info": { 506 | "codemirror_mode": { 507 | "name": "ipython", 508 | "version": 3 509 | }, 510 | "file_extension": ".py", 511 | "mimetype": "text/x-python", 512 | "name": "python", 513 | "nbconvert_exporter": "python", 514 | "pygments_lexer": "ipython3", 515 | "version": "3.6.5" 516 | } 517 | }, 518 | "nbformat": 4, 519 | "nbformat_minor": 2 520 | } 521 | -------------------------------------------------------------------------------- /notebooks/rukwa-classified.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Using TensorFlow backend.\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import tspredict\n", 18 | "import pandas as pd\n", 19 | "import numpy as np\n", 20 | "import os\n", 21 | "import time\n", 22 | "from keras.models import Sequential\n", 23 | "from keras.models import model_from_json" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "Load trained LSTM model. Model was trained on the first 6 dates in the 2017 growing season with <15% cloud cover. Features included blue, green, NDVI bands. " 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# load json and create model\n", 40 | "json_file = open(\"/home/ec2-user/model_labeled.json\", 'r')\n", 41 | "loaded_model_json = json_file.read()\n", 42 | "json_file.close()\n", 43 | "model = model_from_json(loaded_model_json)" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "name": "stdout", 53 | "output_type": "stream", 54 | "text": [ 55 | "Loaded model from disk\n" 56 | ] 57 | } 58 | ], 59 | "source": [ 60 | "# load weights into model\n", 61 | "model.load_weights(\"/home/ec2-user/model_labeled.h5\")\n", 62 | "print(\"Loaded model from disk\")" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 4, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "# mean and standard deviation of features from training data - used to standardize features for model prediction\n", 72 | "mu = np.load('/home/ec2-user/mu.npy')\n", 73 | "sd = np.load('/home/ec2-user/sd.npy')" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 5, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# File paths to Sentinel-2 tiles to predict (intersecting the Rukwa region)\n", 83 | "fp = '/home/ec2-user/sent-scenes-s3'\n", 84 | "tiles = [fp + '/' + t for t in os.listdir(fp)]" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 6, 90 | "metadata": {}, 91 | "outputs": [ 92 | { 93 | "name": "stdout", 94 | "output_type": "stream", 95 | "text": [ 96 | "/home/ec2-user/sent-scenes-s3/T36MTS\n", 97 | "/home/ec2-user/sent-scenes-s3/T35LRL\n", 98 | "/home/ec2-user/sent-scenes-s3/T35MRM\n", 99 | "/home/ec2-user/sent-scenes-s3/T36MUT\n", 100 | "/home/ec2-user/sent-scenes-s3/T36LTR\n", 101 | "/home/ec2-user/sent-scenes-s3/T36MTT\n", 102 | "/home/ec2-user/sent-scenes-s3/T35MRN\n", 103 | "/home/ec2-user/sent-scenes-s3/T36MVS\n", 104 | "/home/ec2-user/sent-scenes-s3/T36MUS\n", 105 | "/home/ec2-user/sent-scenes-s3/T36LUR\n" 106 | ] 107 | } 108 | ], 109 | "source": [ 110 | "for tile in tiles:\n", 111 | " print(tile)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [ 119 | { 120 | "name": "stdout", 121 | "output_type": "stream", 122 | "text": [ 123 | "/home/ec2-user/sent-scenes-s3/T36MTS formatted\n", 124 | "/home/ec2-user/sent-scenes-s3/T36MTS predicted\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "start_time = time.time()\n", 130 | "\n", 131 | "for tile in tiles:\n", 132 | " # Reshape bands in each scene to match input shape required by Keras sequential model\n", 133 | " formatted_scene = tspredict.format_scene(tile, mu, sd)\n", 134 | " \n", 135 | " print(tile + ' formatted')\n", 136 | " \n", 137 | " # refimg can be any band from the same Sentinel-2 tile, outimg for writing pred. scene to disk\n", 138 | " band_paths = []\n", 139 | " for path, subdirs, files in os.walk(tile):\n", 140 | " for name in files:\n", 141 | " band_paths.append(os.path.join(path, name))\n", 142 | " \n", 143 | " refimg = band_paths[0]\n", 144 | " outimg = tile + '/tile_predicted.tif'\n", 145 | " \n", 146 | " # Predict the full tile\n", 147 | " predicted_tile = tspredict.classify_scene(formatted_scene=formatted_scene, model=model, \n", 148 | " refimg=refimg, outimg=outimg)\n", 149 | " print(tile + ' predicted')\n", 150 | "\n", 151 | "print(\"--- %s seconds ---\" % (time.time() - start_time))" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "# from joblib import Parallel, delayed\n", 161 | "# import multiprocessing\n", 162 | "\n", 163 | "# def predict_tile(tile):\n", 164 | "# # Reshape bands in each scene to match input shape required by Keras sequential model\n", 165 | "# formatted_scene = tspredict.format_scene(tile, mu, sd)\n", 166 | " \n", 167 | "# # refimg can be any band from the same Sentinel-2 tile, outimg for writing pred. scene to disk\n", 168 | "# band_paths = []\n", 169 | "# for path, subdirs, files in os.walk(tile):\n", 170 | "# for name in files:\n", 171 | "# band_paths.append(os.path.join(path, name))\n", 172 | " \n", 173 | "# refimg = band_paths[0]\n", 174 | "# outimg = tile + '/tile_predicted.tif'\n", 175 | " \n", 176 | "# # Predict the full tile\n", 177 | "# predicted_tile = tspredict.classify_scene(formatted_scene=formatted_scene, model=model, \n", 178 | "# refimg=refimg, outimg=outimg)\n", 179 | "\n", 180 | "# # Perform computation in parallel\n", 181 | "# #Parallel(n_jobs=-1, backend=\"multiprocessing\")(map(delayed(predict_tile), tiles))\n", 182 | "# Parallel(n_jobs=-1)(delayed(predict_tile)(tile) for tile in tiles)" 183 | ] 184 | } 185 | ], 186 | "metadata": { 187 | "kernelspec": { 188 | "display_name": "Python 3", 189 | "language": "python", 190 | "name": "python3" 191 | }, 192 | "language_info": { 193 | "codemirror_mode": { 194 | "name": "ipython", 195 | "version": 3 196 | }, 197 | "file_extension": ".py", 198 | "mimetype": "text/x-python", 199 | "name": "python", 200 | "nbconvert_exporter": "python", 201 | "pygments_lexer": "ipython3", 202 | "version": "3.6.5" 203 | } 204 | }, 205 | "nbformat": 4, 206 | "nbformat_minor": 2 207 | } 208 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gippy~=1.0.1b1 2 | pandas~=0.23.1 3 | numpy~=1.14.5 4 | scipy~=1.1.0 5 | matplotlib~=2.2.2 6 | Cython~=0.28.2 7 | tslearn~=0.1.18.3 8 | scikit-learn~=0.19.1 9 | tensorflow 10 | keras-preprocessing 11 | keras-applications 12 | keras 13 | jupyter 14 | -------------------------------------------------------------------------------- /rukwa-mask.py: -------------------------------------------------------------------------------- 1 | from satts import tspredict 2 | from importlib import reload 3 | import fiona 4 | import rasterio.mask 5 | import matplotlib.pyplot as plt 6 | import os 7 | import numpy as np 8 | import pandas as pd 9 | 10 | 11 | fp = '/Users/jameysmith/Documents/sentinel2_tanz/LSTM-predictions-rukwa/reproj/' 12 | 13 | images = [fp + img for img in os.listdir(fp) if not img.startswith('.')] 14 | 15 | poly = "/Users/jameysmith/Documents/sentinel2_tanz/rukwa_polygon/rukwa.geojson" 16 | 17 | with fiona.open(poly, 'r') as json: 18 | features = [feature["geometry"] for feature in json] 19 | 20 | with rasterio.open(img) as src: 21 | out_image, out_transform = rasterio.mask.mask(src, features, crop=True) 22 | out_meta = src.meta.copy() 23 | 24 | t = out_image[out_image == 2] 25 | 26 | 27 | def get_area(polygon, images, label_num): 28 | 29 | with fiona.open(polygon, 'r') as json: 30 | features = [feature["geometry"] for feature in json] 31 | 32 | pixel_count = np.empty((0, len(images)), int) 33 | 34 | for image in images: 35 | 36 | with rasterio.open(image) as src: 37 | out_image, out_transform = rasterio.mask.mask(src, features, crop=True) 38 | out_meta = src.meta.copy() 39 | 40 | count = len(out_image[out_image == label_num]) 41 | pixel_count = np.append(pixel_count, count) 42 | 43 | return pixel_count 44 | 45 | test = get_area(polygon=poly, images=images, label_num=2) 46 | 47 | 48 | img = '/Users/jameysmith/Documents/sentinel2_tanz/LSTM-predictions-rukwa/reproj/ruk-clipped.tif' 49 | 50 | dataset = rasterio.open(img) 51 | preds = dataset.read() 52 | 53 | 54 | u, c = np.unique(preds, return_counts=True) 55 | 56 | classes = { 57 | 0: "crop_2", 58 | 1: "cop_3", 59 | 2: "maize", 60 | 3: "urban", 61 | 4: "veg", 62 | 5: "water" 63 | } 64 | 65 | df = pd.DataFrame(np.asarray((u, c)).T, columns=['lc_class', 'pix_count']) 66 | df['area_ha'] = df['pix_count'] * 100 67 | df['area_ha'] = df['area_ha'] / 10000 68 | 69 | df = df.replace({"lc_class": classes}) 70 | df = df.drop([6]) -------------------------------------------------------------------------------- /satts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/satTS/931efe3fb8e61bfb99d1f0962365f658f0f7877b/satts/__init__.py -------------------------------------------------------------------------------- /satts/tsclust.py: -------------------------------------------------------------------------------- 1 | from tslearn.utils import to_time_series_dataset 2 | from tslearn.clustering import silhouette_score 3 | import tslearn.clustering as clust 4 | from scipy import signal 5 | import itertools 6 | import pandas as pd 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | from gippy import GeoImage 10 | import gippy.algorithms as alg 11 | import re 12 | from os import listdir, walk 13 | 14 | 15 | def calulate_indices(filepath, asset_dict, indices): 16 | ''' Create image files for indices 17 | 18 | :param filepath (str): Full path to directory containing satellite scenes in default structure created 19 | by sat-search load --download 20 | :param asset_dict (dict): Keys = asset (band) names in scene files (e.g. 'B01', 'B02'); Values = value names 21 | corresponding to keys (e.g. 'red', 'nir') 22 | :param indices (list): Which indices to generate? Options include any index included in gippy.alg.indices 23 | 24 | :return: None (writes files to disk) 25 | ''' 26 | 27 | subdirs = [x[0] for x in walk(filepath)] 28 | subdirs = subdirs[1:len(subdirs)] 29 | 30 | for folder in subdirs: 31 | 32 | # Filepath points to folder of geotiffs of Sentinel 2 time-series of bands 4 (red) and 8 (nir) 33 | files = [folder + '/' + f for f in listdir(folder) if not f.startswith('.')] 34 | 35 | # Asset (band) names 36 | pattern = '[^_.]+(?=\.[^_.]*$)' 37 | bands = [re.search(pattern, f).group(0) for f in files] 38 | 39 | # Match band names 40 | bands = [asset_dict.get(band, band) for band in bands] 41 | 42 | img = GeoImage.open(filenames=files, bandnames=bands, nodata=0) 43 | 44 | for ind in indices: 45 | alg.indices(img, products=[ind], filename=folder + '/index_' + ind + '.tif') 46 | 47 | img = None 48 | 49 | 50 | def apply_savgol(x, value, window, poly): 51 | """ Perform Savgol signal smoothing on time-series in dataframe group object (x) 52 | 53 | Parameters 54 | ---------- 55 | x: (pd.DataFrame.groupby) Grouped dataframe object 56 | window (int): smoothing window - pass to signal.savgol_filter 'window_length' param 57 | poly (int): polynomial order used to fit samples - pass to signal.savgol_filter 'polyorder' param 58 | value (str): Name of value (variable) to smooth 59 | 60 | Returns 61 | ------- 62 | x: "Smoothed" time-series 63 | """ 64 | 65 | x[value] = signal.savgol_filter(x[value], window_length=window, polyorder=poly) 66 | 67 | return x 68 | 69 | 70 | class TimeSeriesSample: 71 | 72 | def __init__(self, time_series_df, n_samples, ts_var, seed): 73 | # Take random `n_samples of pixels from time-series dataframe 74 | self.ts_var = ts_var 75 | self.group = time_series_df.groupby(['lc', 'pixel', 'array_index']) 76 | self.arranged_group = np.arange(self.group.ngroups) 77 | 78 | # Ensure same pixels are sampled each time function is run when same `n_samples` parameter is supplied 79 | np.random.seed(seed) 80 | np.random.shuffle(self.arranged_group) 81 | 82 | # Take the random sample 83 | self.sample = time_series_df[self.group.ngroup().isin(self.arranged_group[:n_samples])] 84 | 85 | if self.sample['date'].dtype != 'O': 86 | self.sample['date'] = self.sample['date'].dt.strftime('%Y-%m-%d') 87 | 88 | self.sample_dates = self.sample['date'].unique() 89 | self.tslist = self.sample.groupby(['lc', 'pixel', 'array_index'])[self.ts_var].apply(list) 90 | self.dataset = None 91 | 92 | def smooth(self, window=7, poly=3): 93 | # Perform Savgol signal smoothing to each time-series 94 | self.sample = self.sample.groupby(['lc', 'pixel', 'array_index']).apply(apply_savgol, self.ts_var, window, poly) 95 | self.tslist = self.sample.groupby(['lc', 'pixel', 'array_index'])[self.ts_var].apply(list) 96 | return self 97 | 98 | @ property 99 | def ts_dataset(self): 100 | #tslist = self.sample.groupby(['lc', 'pixel', 'array_index'])[self.ts_var].apply(list) 101 | self.dataset = to_time_series_dataset(self.tslist) 102 | return self.dataset 103 | 104 | 105 | def cluster_time_series(ts_sample, cluster_alg, n_clusters, cluster_metric, score=False): 106 | 107 | # Dataframe to store cluster results 108 | clust_df = pd.DataFrame(ts_sample.tslist.tolist(), index=ts_sample.tslist.index).reset_index() 109 | clust_df.columns.values[3:] = ts_sample.sample_dates 110 | 111 | # Fit model 112 | if cluster_alg == "GAKM": 113 | km = clust.GlobalAlignmentKernelKMeans(n_clusters=n_clusters) 114 | 115 | if cluster_alg == "TSKM": 116 | km = clust.TimeSeriesKMeans(n_clusters=n_clusters, metric=cluster_metric) 117 | 118 | # Add predicted cluster labels to cluster results dataframe 119 | labels = km.fit_predict(ts_sample.ts_dataset) 120 | clust_df['cluster'] = labels 121 | 122 | if score: 123 | s = silhouette_score(ts_sample.ts_dataset, labels) 124 | return clust_df, s 125 | 126 | return clust_df 127 | 128 | 129 | def cluster_grid_search(parameter_grid): 130 | ''' Perform grid search on cluster_ndvi_ts parameters 131 | 132 | :param parameter_grid: (dict) parameter grid containing all parameter values to explore 133 | 134 | :return: 1) dictionary with cluster labels and silhouette scores 2) dataframe with parameter combinations 135 | and corresponding silhouette score 136 | ''' 137 | 138 | # List of all possible parameter combinations 139 | d = [] 140 | for vals in itertools.product(*parameter_grid.values()): 141 | d.append(dict(zip(parameter_grid, vals))) 142 | 143 | # Convert to data frame; use to store silhouette scores 144 | df = pd.DataFrame(d) 145 | df = df.drop(['ts_sample'], axis=1) 146 | 147 | # Perform grid search 148 | output = {'clusters': [], 'scores': []} 149 | for values in itertools.product(*parameter_grid.values()): 150 | # Run clustering function on all combinations of parameters in parameter grid 151 | clusters, score = cluster_time_series(**dict(zip(parameter_grid, values))) 152 | 153 | # 'clusters' = dataframes with cluster results; scores = silhouette scores of corresponding cluster results 154 | output['clusters'].append(clusters) 155 | output['scores'].append(score) 156 | 157 | # Add silhouette scores to dataframe 158 | df['sil_score'] = output['scores'] 159 | 160 | return output, df 161 | 162 | 163 | def cluster_mean_quantiles(df): 164 | '''Calculate mean and 10th, 90th percentile for each cluster at all dates in time series 165 | 166 | :param df: dataframe output from `cluster_ndvi_ts` 167 | 168 | :return: two dataframes: one for mean time-series per-cluster, one for quantile time-series per-cluster 169 | ''' 170 | 171 | # Columns with ndvi values 172 | cols = df.columns[3:-1] 173 | 174 | # Cluster means at each time-step 175 | m = df.groupby('cluster', as_index=False)[cols].mean().T.reset_index() 176 | m = m.iloc[1:] 177 | m.rename(columns={'index':'date'}, inplace=True) 178 | m.set_index('date', drop=True, inplace=True) 179 | m.index = pd.to_datetime(m.index) 180 | 181 | # Cluster 10th and 90th percentile at each time-step 182 | q = df.groupby('cluster', as_index=False)[cols].quantile([.1, 0.9]).T.reset_index() 183 | q.rename(columns={'index':'date'}, inplace=True) 184 | q.set_index('date', drop=True, inplace=True) 185 | q.index = pd.to_datetime(q.index) 186 | 187 | return m, q 188 | 189 | 190 | def plot_clusters(obj, index=None, fill=True, title=None, save=False, filename=None): 191 | 192 | if type(obj) is dict: 193 | cluster_df = obj['clusters'][index] 194 | else: 195 | cluster_df = obj 196 | 197 | # Get cluster means and 10th, 90th quantiles 198 | m, q = cluster_mean_quantiles(cluster_df) 199 | 200 | # Plot cluster results 201 | nclusts = len(cluster_df.cluster.unique()) 202 | color = iter(plt.cm.Set2(np.linspace(0, 1, nclusts))) 203 | 204 | fig = plt.figure(figsize=(10, 8)) 205 | cnt = 0 206 | for i in range(0, nclusts): 207 | # Plot mean time-series for each cluster 208 | c = next(color) 209 | plt.plot(m.index, m[i], 'k', color=c) 210 | 211 | # Fill 10th and 90th quantile time-series of each cluster 212 | if fill: 213 | plt.fill_between(m.index, q.iloc[:, [cnt]].values.flatten(), q.iloc[:, [cnt+1]].values.flatten(), 214 | alpha=0.5, edgecolor=c, facecolor=c) 215 | cnt += 2 216 | 217 | # Legend and title 218 | plt.legend(loc='upper left') 219 | plt.title(title) 220 | 221 | # Axis labels 222 | ax = fig.add_subplot(111) 223 | ax.set_xlabel('Date') 224 | ax.set_ylabel('NDVI') 225 | 226 | if save: 227 | pattern = '.png' 228 | if not pattern in filename: 229 | raise ValueError('File type should be .png') 230 | fig.savefig(filename) 231 | 232 | -------------------------------------------------------------------------------- /satts/tsmask.py: -------------------------------------------------------------------------------- 1 | from osgeo import ogr, gdal 2 | import gippy 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | 8 | def rasterize(shapefile, outimg, refimg, attribute): 9 | ''' Rasterize a shapefile containing land cover polygons. Shapefile should have an attribute 10 | called 'id' corresponding to unique land cover class or other label 11 | 12 | :param shapefile (str): file path to shapefile to be rasterized 13 | :param outimg (str): file path to rasterized image 14 | :param refimg (str): file path to a reference image. Used to fetch dimensions and other metadata for rasterized img 15 | :param attribute (str): name of attribute in `shapefile` to burn into raster layer 16 | 17 | :return: None (saves image to file) 18 | ''' 19 | 20 | # Open reference raster image to grab projection info and metadata 21 | img = gdal.Open(refimg, gdal.GA_ReadOnly) 22 | 23 | # Fetch dimensions of reference raster 24 | ncol = img.RasterXSize 25 | nrow = img.RasterYSize 26 | 27 | # Projection and extent of raster reference 28 | proj = img.GetProjectionRef() 29 | ext = img.GetGeoTransform() 30 | 31 | # Close reference image 32 | img = None 33 | 34 | # Create raster mask 35 | memdrive = gdal.GetDriverByName('GTiff') 36 | outrast = memdrive.Create(outimg, ncol, nrow, 1, gdal.GDT_Byte) 37 | 38 | # Set rasterized image's projection and extent to input raster's projection and extent 39 | outrast.SetProjection(proj) 40 | outrast.SetGeoTransform(ext) 41 | 42 | # Fill output band with the 0 blank (no class) label 43 | b = outrast.GetRasterBand(1) 44 | b.Fill(0) 45 | 46 | # Open the shapefile 47 | polys = ogr.Open(shapefile) 48 | layer = polys.GetLayerByIndex(0) 49 | 50 | # Rasterize the shapefile layer to new dataset 51 | status = gdal.RasterizeLayer(outrast, [1], layer, None, None, [0], ['ALL_TOUCHED=TRUE', 'ATTRIBUTE=' + attribute]) 52 | 53 | # Close rasterized dataset 54 | outrast = None 55 | 56 | 57 | def check_rasterize(rasterized_file, plot=True): 58 | '''Checks how many pixels are in each class of a rasterized image 59 | 60 | :param rasterized_file (str): File path to a rasterized image 61 | :param plot (bool): Should the result of the rasterized layer be plotted? 62 | 63 | :return: None 64 | ''' 65 | 66 | # Read rasterized image 67 | roi_ds = gdal.Open(rasterized_file, gdal.GA_ReadOnly) 68 | roi = roi_ds.GetRasterBand(1).ReadAsArray() 69 | 70 | # How many pixels are in each class? 71 | classes = np.unique(roi) 72 | 73 | # Iterate over all class labels in the ROI image, print num pixels/class 74 | for c in classes: 75 | print('Class {c} contains {n} pixels'.format(c=c, n=(roi == c).sum())) 76 | 77 | if plot: 78 | plt.imshow(roi) 79 | 80 | 81 | def mask_to_array(files, dates, mask, class_num, gain, missing_vals=None): 82 | ''' Generate a 3d array of values corresponding to a time-series of image masks for a land cover class 83 | 84 | :param files (list): List of files containing the image time-series (e.g. a stack of NDVI images) 85 | :param dates (list): List of dates corresponding in the image time-series 86 | :param mask (str): File path to a land cover mask 87 | :param class_num (int): ID number of land cover class in `mask` 88 | 89 | :return: 3d array of time-series mask for a specified land cover class 90 | ''' 91 | 92 | # Grab dimensions to set empty array 93 | ts = gippy.GeoImage.open(filenames=files, bandnames=dates, nodata=0, gain=gain) 94 | 95 | nbands = ts.nbands() 96 | nrows = ts.ysize() 97 | ncols = ts.xsize() 98 | 99 | # Close connection 100 | ts = None 101 | 102 | arr = np.empty((nbands, nrows, ncols)) 103 | 104 | for band in range(0, nbands): 105 | # Open image time-series 106 | ndvi_ts = gippy.GeoImage.open(filenames=files, bandnames=dates, nodata=0, gain=gain) 107 | 108 | # Open rasterized landcover 109 | land_cover = gippy.GeoImage.open(filenames=[mask], bandnames=(['land_cover']), nodata=0) 110 | 111 | # Create land cover mask 112 | lc_mask = ndvi_ts.add_mask(land_cover['land_cover'] == class_num) 113 | 114 | # Read mask for time-step[band] into np.array 115 | lc_mask = lc_mask[band].read() 116 | 117 | # Deal with no-data values 118 | if missing_vals is not None: 119 | lc_mask[lc_mask == missing_vals] = np.nan 120 | 121 | # Append water mask np.array 122 | arr[band] = lc_mask 123 | 124 | # Close image connections 125 | ndvi_ts = None 126 | land_cover = None 127 | 128 | return arr 129 | 130 | 131 | class BandTimeSeries: 132 | """Time-series of image band values for a (masked) land cover class""" 133 | 134 | def __init__(self, mask, lc_class, ts_var, dates): 135 | """ 136 | :param mask (numpy array): 3D numpy array corresponding to masked time-series for an image band or index 137 | :param lc_class (str): name of land cover class 138 | :param ts_var (str): name of variable contained in masked time-series (e.g. 'red', 'ndvi') 139 | :param dates (list): list of dates corresponding to time-series 140 | """ 141 | self.land_cover_class = lc_class 142 | self.mask = mask 143 | self.ts_var = ts_var 144 | if len(dates) == len(mask): 145 | self.ts_dates = dates 146 | else: 147 | raise ValueError('length of dates must match number of time-steps in mask') 148 | 149 | # 2D time-series array of shape (num_timesteps, num_non-nan-pixels) 150 | mask_vals = self.mask[np.logical_not(np.isnan(self.mask))] 151 | self.ts_matrix = mask_vals.reshape((len(self.mask), int(mask_vals.shape[0] / len(self.mask)))) 152 | self.num_timesteps = self.ts_matrix.shape[0] 153 | self.num_timeseries = self.ts_matrix.shape[1] 154 | 155 | def mask_indices(self): 156 | """Get the indices of non-nan values in crop mask 157 | :return: list of length #non-nan cells with each element a tuple: (rowindex, colindex) 158 | """ 159 | w = np.argwhere(np.logical_not(np.isnan(self.mask))) 160 | wdf = pd.DataFrame(w) 161 | wsub = wdf.loc[wdf[0] == 0, [1, 2]] 162 | ind = list(zip(wsub[1], wsub[2])) 163 | 164 | return ind 165 | 166 | def time_series_dataframe(self, frequency, interpolate=True): 167 | """Create dataframe with band-value time-series for each pixel in land cover class 168 | :param interpolate (bool): Should time-series be interpolated? 169 | :param frequency (str): interpolation frequency, e.g. '1d' for daily, '5d' for 5 days 170 | :return: Dataframe with band-value time-series per-pixel/land cover class 171 | """ 172 | 173 | # Array indices (from original image) of non-nan values 174 | lc_ind = self.mask_indices() 175 | 176 | # Transpose time-series matrix of dim (# time steps, # non-nan pixels) 177 | mat_transpose = self.ts_matrix.T 178 | 179 | # Convert to dataframe, change col names to dates 180 | ts_df = pd.DataFrame(mat_transpose) 181 | ts_df.columns = self.ts_dates 182 | 183 | # append array indices as column 184 | ts_df['array_index'] = lc_ind 185 | 186 | # Create land cover value and pixel value columns 187 | ts_df['lc'] = self.land_cover_class 188 | ts_df['pixel'] = ts_df.index 189 | 190 | # Convert to long-format and sort 191 | ts_df = pd.melt(ts_df, id_vars=['lc', 'pixel', 'array_index'], var_name='date', value_name=self.ts_var) 192 | ts_df = ts_df.sort_values(['lc', 'pixel', 'date']) 193 | 194 | # Convert date column to datetime object (can be used as datetime index for interpolation) 195 | ts_df['date'] = pd.to_datetime(ts_df['date'], format="%Y-%m-%d") 196 | 197 | if interpolate: 198 | ts_df = ts_df.set_index('date').groupby(['lc', 'pixel', 'array_index']) 199 | ts_df = ts_df.resample(frequency)[self.ts_var].asfreq().interpolate(method='linear').reset_index() 200 | 201 | return ts_df -------------------------------------------------------------------------------- /satts/tspredict.py: -------------------------------------------------------------------------------- 1 | import os 2 | import gippy 3 | import numpy as np 4 | from osgeo import gdal 5 | 6 | 7 | def format_scene(file_path, mu, sd): 8 | 9 | # Folders containing band values for a given date 10 | scenes = [file_path + '/' + f for f in os.listdir(file_path) if not f.startswith('.')] 11 | scenes.sort() 12 | 13 | # Sorted to ensure the 2D arrays are placed in same order as features in the trained model 14 | all_dates = [] 15 | for s in scenes: 16 | bands = [s + '/' + b for b in os.listdir(s) if not b.startswith('.')] 17 | bands.sort() 18 | all_dates.append(bands) 19 | 20 | # Get dimensions for the final 3D input array for Keras model 21 | get_shape = gippy.GeoImage.open(filenames=[all_dates[0][0]]) 22 | 23 | n_samples = get_shape.xsize() * get_shape.ysize() 24 | n_timesteps = len(scenes) 25 | n_features = len(all_dates[0]) 26 | 27 | # Close image 28 | get_shape = None 29 | 30 | # All band values for all dates in time-series 31 | full_scene = np.empty([n_samples, n_timesteps, n_features]) 32 | for date in range(0, len(all_dates)): 33 | geoimg = gippy.GeoImage.open(filenames=all_dates[date], nodata=0, gain=0.0001) 34 | 35 | scene_vals = np.empty([n_samples, n_features]) 36 | for i in range(0, geoimg.nbands()): 37 | arr = geoimg[i].read() 38 | flat = arr.flatten() 39 | scene_vals[:, i] = flat 40 | 41 | geoimg = None 42 | 43 | full_scene[:, date, :] = scene_vals 44 | 45 | # Normalize data with mu and sd from model training data 46 | full_norm = (full_scene - mu) / sd 47 | 48 | return full_norm 49 | 50 | 51 | def classify_scene(formatted_scene, model, refimg, outimg): 52 | '''Predict land cover for full Sentinel-2 scene 53 | 54 | -> Use a band (not an index) for reference image 55 | ''' 56 | 57 | img = gdal.Open(refimg, gdal.GA_ReadOnly) 58 | 59 | # For masking no-data values 60 | arr = np.array(img.GetRasterBand(1).ReadAsArray()) 61 | 62 | # Fetch dimensions of reference raster 63 | ncol = img.RasterXSize 64 | nrow = img.RasterYSize 65 | 66 | # Projection and extent of raster reference 67 | proj = img.GetProjectionRef() 68 | ext = img.GetGeoTransform() 69 | 70 | # Close reference image 71 | img = None 72 | 73 | # Allocate memory for prediction image 74 | memdrive = gdal.GetDriverByName('GTiff') 75 | outrast = memdrive.Create(outimg, ncol, nrow, 1, gdal.GDT_Int16) 76 | 77 | # Set prediction image's projection and extent to input image projection and extent 78 | outrast.SetProjection(proj) 79 | outrast.SetGeoTransform(ext) 80 | 81 | # Model predictions 82 | preds = model.predict(formatted_scene) 83 | pred_bool = (preds > 0.5) 84 | pred_class = pred_bool.argmax(axis=1) 85 | 86 | # Reshape to match 2D image array 87 | pred_mat = pred_class.reshape(nrow, ncol) 88 | 89 | # Mask no-data values 90 | pred_mat[arr == 0.] = 9999 91 | 92 | # Fill output image with the predicted class values 93 | b = outrast.GetRasterBand(1) 94 | b.WriteArray(pred_mat) 95 | 96 | outrast = None 97 | 98 | -------------------------------------------------------------------------------- /satts/tstrain.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import gippy 3 | from gippy import GeoImage 4 | import gippy.algorithms as alg 5 | import os 6 | import re 7 | import numpy as np 8 | from keras.utils.np_utils import to_categorical 9 | from sklearn.utils import shuffle 10 | from sklearn import preprocessing 11 | from sklearn.metrics import confusion_matrix 12 | 13 | 14 | def random_ts_samples(file_path, n_samples, seed=None): 15 | ''' Sample of locations for each land cover class included in file_path 16 | 17 | :param file_path (str): Full path to directory containing .csv files with location data for land class samples 18 | :param n_samples (int): Number of samples to select from full dataset 19 | :param seed (int): Set a seed to generate same dataset repeatedly 20 | 21 | :return: pd.DataFrame with locations for n_samples of each land cover class 22 | ''' 23 | 24 | # CSV files containing time-series' to be samples 25 | ts_files = [file_path + '/' + file for file in os.listdir(file_path) if not file.startswith('.')] 26 | 27 | np.random.seed(seed) 28 | 29 | # Sample each dataframe corresponding to a land cover class, store in list 30 | dfs = [] 31 | for file in ts_files: 32 | df = pd.read_csv(file) 33 | g = df.groupby('pixel') 34 | a = np.arange(g.ngroups) 35 | np.random.shuffle(a) 36 | s = df[g.ngroup().isin(a[:n_samples])] 37 | 38 | dfs.append(s) 39 | 40 | # Convert list to single sample dataframe, convert to same shape as cluster results 41 | lc_samples = pd.concat(dfs) 42 | lc_samples = lc_samples.rename(columns={'lc': 'label', 'array_ind': 'array_index'}) 43 | lc_samples = lc_samples.pivot_table(index=['array_index', 'label', 'pixel'], columns='date', values='ndvi').reset_index() 44 | 45 | return lc_samples 46 | 47 | 48 | def calulate_indices(filepath, asset_dict, indices): 49 | ''' Create image files for indices 50 | 51 | :param filepath (str): Full path to directory containing satellite scenes in default structure created 52 | by sat-search load --download 53 | :param asset_dict (dict): Keys = asset (band) names in scene files (e.g. 'B01', 'B02'); Values = value names 54 | corresponding to keys (e.g. 'red', 'nir') 55 | :param indices (list): Which indices to generate? Options include any index included in gippy.alg.indices 56 | 57 | :return: None (writes files to disk) 58 | ''' 59 | 60 | subdirs = [x[0] for x in os.walk(filepath)] 61 | subdirs = subdirs[1:len(subdirs)] 62 | 63 | for folder in subdirs: 64 | 65 | # Filepath points to folder of geotiffs of Sentinel 2 time-series of bands 4 (red) and 8 (nir) 66 | files = [folder + '/' + f for f in os.listdir(folder) if not f.startswith('.')] 67 | 68 | # Asset (band) names 69 | pattern = '[^_.]+(?=\.[^_.]*$)' 70 | bands = [re.search(pattern, f).group(0) for f in files] 71 | 72 | # Match band names 73 | bands = [asset_dict.get(band, band) for band in bands] 74 | 75 | img = GeoImage.open(filenames=files, bandnames=bands, nodata=0) 76 | 77 | for ind in indices: 78 | alg.indices(img, products=[ind], filename=folder + '/index_' + ind + '.tif') 79 | 80 | img = None 81 | 82 | 83 | def get_training_data(asset_dir, asset_dict, samples_df, standardize=True): 84 | ''' Create a dataset of n_features (bands) at each samples location for n_timeseteps 85 | 86 | :param asset_dir (str): File path to directory containing satellite scenes downloaded using the default 87 | output of sat-search load 88 | :param asset_dict (dict): Keys = asset (band) names in scene files (e.g. 'B01', 'B02'); Values = value names 89 | corresponding to keys (e.g. 'red', 'nir') 90 | :param samples_df (pd.DataFrame): pd.DataFrame with samples locations for each land cover class 91 | :param scale (bool): Scale features using sklearn.preprecessing.MinMaxScaler() 92 | 93 | :return: pd.DataFrame with time-series of n_features (band reflectance values) for each sample location 94 | ''' 95 | 96 | # Array indices corresponding to sample locations 97 | ind = list(samples_df.array_index) 98 | ind = [elem.strip('()').split(',') for elem in ind] 99 | ind = [list(map(int, elem)) for elem in ind] 100 | sample_ind = np.array([*ind]) 101 | 102 | # Class labels 103 | labels = samples_df.label 104 | 105 | # Full file-path for every asset in `fp` (directory structure = default output of sat-search) 106 | file_paths = [] 107 | for path, subdirs, files in os.walk(asset_dir): 108 | for name in files: 109 | # Address .DS_Store file issue 110 | if not name.startswith('.'): 111 | file_paths.append(os.path.join(path, name)) 112 | 113 | # Scene dates 114 | dates = [re.findall('\d\d\d\d-\d\d-\d\d', f) for f in file_paths] 115 | dates = [date for sublist in dates for date in sublist] 116 | 117 | # Asset (band) names 118 | pattern = '[^_.]+(?=\.[^_.]*$)' 119 | bands = [re.search(pattern, f).group(0) for f in file_paths] 120 | 121 | # Match band names 122 | bands = [asset_dict.get(band, band) for band in bands] 123 | 124 | samples_list = [] 125 | for i in range(0, len(file_paths)): 126 | 127 | img = gippy.GeoImage.open(filenames=[file_paths[i]], bandnames=[bands[i]], nodata=0, gain=0.0001) 128 | bandvals = img.read() 129 | 130 | # Extract values at sample indices for band[i] in time-step[i] 131 | sample_values = bandvals[sample_ind[:, 0], sample_ind[:, 1]] 132 | 133 | # Store extracted band values as dataframe 134 | d = {'feature': bands[i], 135 | 'value': sample_values, 136 | 'date': dates[i], 137 | 'label': labels, 138 | 'ind': [*sample_ind]} 139 | 140 | # Necessary due to varying column lengths 141 | samp = pd.DataFrame(dict([(k, pd.Series(v)) for k, v in d.items()])).ffill() 142 | 143 | # Zero mean and unit variance for feature 144 | if standardize: 145 | samp['value'] = preprocessing.scale(samp['value']) 146 | 147 | samples_list.append(samp) 148 | 149 | # Combine all samples into single, long-form dataframe 150 | training = pd.concat(samples_list) 151 | 152 | # Reshape for time-series generation 153 | training['ind'] = tuple(list(training['ind'])) 154 | training = training.sort_values(by=['ind', 'date', 'feature']) 155 | 156 | return training 157 | 158 | 159 | def format_training_data(training_data, one_hot=True, seed=None): 160 | ''' Format time-series of reflectance data for fitting a Keras Sequential model 161 | 162 | :param training_data (pd.DataFrame): output of get_training_data 163 | :param one_hot (bool): Format response variable to one-hot encoded vectors? 164 | :param seed (bool): Set a seed to generate same train/test datasets repeatedly 165 | 166 | :return: X (feature) matrix , Y (response) matrix, codes (dict) for Y-labels 167 | ''' 168 | 169 | np.random.seed(seed) 170 | 171 | # Create 3D numpy array from sample values 172 | i = training_data.set_index(['date', 'ind', 'feature']) 173 | shape = list(map(len, i.index.levels)) 174 | arr = np.full(shape, np.nan) 175 | arr[i.index.labels] = i.values[:, 0].flat 176 | 177 | # Kereas LSTM shape: [n_samples, n_timesteps, n_feaures] 178 | x = arr.swapaxes(0, 1) 179 | 180 | # Data labels (Y values); first encode labels as int 181 | training_data['label'] = training_data['label'].astype('category') 182 | 183 | # Store categorical codes 184 | label_codes = dict(enumerate(training_data['label'].cat.categories)) 185 | 186 | # Convert labels to int 187 | training_data['label'] = training_data['label'].cat.codes.astype('str').astype('int') 188 | 189 | # Get Y 190 | group = training_data.groupby('ind') 191 | 192 | y = group.apply(lambda x: x['label'].unique()) 193 | y = y.apply(pd.Series) 194 | y = y[0].values 195 | 196 | if one_hot: 197 | y = to_categorical(y, num_classes=len(training_data['label'].unique())) 198 | 199 | return label_codes, x, y 200 | 201 | 202 | def split_train_test(x, y, seed=0, prop_train=0.8): 203 | ''' Generate training and test datasets for keras LSTM 204 | 205 | :param x (np.array): dataset of shape (n_samples, n_features, n_timesteps) 206 | :param y (np.array): data labels of shape (n_classes, n_samples); likely one-hot encoded vectors 207 | :param seed (int): for shuffling data 208 | :param prop_train (float): proportion of dataset to use for training; default = 0.8 for 80/20 train/test split 209 | 210 | :return: x and y matrices for train/test sets 211 | ''' 212 | x, y = shuffle(x, y, random_state=seed) 213 | 214 | x_train, x_test = x[0:int(x.shape[0] * prop_train)], x[int(x.shape[0] * prop_train):len(x)] 215 | y_train, y_test = y[0:int(y.shape[0] * prop_train)], y[int(y.shape[0] * prop_train):len(y)] 216 | 217 | return x_train, x_test, y_train, y_test 218 | 219 | 220 | def standardize_features(x_train, x_test): 221 | '''Standardize features of 3D array formated for keras sequential model''' 222 | 223 | mu = x_train.mean(axis=(0, 1)) 224 | sd = x_train.std(axis=(0, 1)) 225 | 226 | x_train_norm = (x_train - mu) / sd 227 | x_test_norm = (x_test - mu) / sd 228 | 229 | return mu, sd, x_train_norm, x_test_norm 230 | 231 | 232 | def conf_mat(x_test, y_test, model, label_dict): 233 | 234 | # Model predictions on test set 235 | predictions = model.predict(x_test) 236 | y_pred = (predictions > 0.5) 237 | 238 | # Generate confusion matrix 239 | matrix = confusion_matrix(y_test.argmax(axis=1), y_pred.argmax(axis=1)) 240 | 241 | # Add data labels and per-class recall 242 | tp = matrix.diagonal() 243 | cm = pd.DataFrame(matrix, index=list(label_dict.values()), columns=list(label_dict.values())) 244 | cm['recall'] = tp / cm.sum(axis=1) 245 | 246 | return cm -------------------------------------------------------------------------------- /satts/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | from imp import load_source 4 | from os import path 5 | import io 6 | 7 | __version__ = load_source('cropclass.version', 'cropclass/version.py').__version__ 8 | 9 | here = path.abspath(path.dirname(__file__)) 10 | 11 | # get the dependencies and installs 12 | with io.open(path.join(here, 'requirements.txt'), encoding='utf-8') as f: 13 | all_reqs = f.read().split('\n') 14 | 15 | install_requires = [x.strip() for x in all_reqs if 'git+' not in x] 16 | dependency_links = [x.strip().replace('git+', '') for x in all_reqs if 'git+' not in x] 17 | 18 | setup( 19 | name='cropclass', 20 | author='', 21 | author_email='', 22 | version=__version__, 23 | description='python-seed', 24 | url='https://github.com/', 25 | license='MIT', 26 | classifiers=[ 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3.5', 31 | 'Programming Language :: Python :: 3.6', 32 | ], 33 | keywords='', 34 | # entry_points={ 35 | # 'console_scripts': ['PACKAGENAME=PACKAGENAME.main:cli'], 36 | # }, 37 | packages=find_packages(exclude=['docs', 'tests*']), 38 | include_package_data=True, 39 | install_requires=install_requires, 40 | dependency_links=dependency_links, 41 | ) 42 | --------------------------------------------------------------------------------