{"id":29,"date":"2021-12-01T08:00:00","date_gmt":"2021-12-01T08:00:00","guid":{"rendered":"http:\/\/wcwp.rf.gd\/2021\/12\/01\/https-landscapearchaeology-org-2021-python-tpi\/"},"modified":"2021-12-01T08:00:00","modified_gmt":"2021-12-01T08:00:00","slug":"python-tpi","status":"publish","type":"post","link":"https:\/\/landscapeanalysis.org\/?p=29","title":{"rendered":"Topographic position index (TPI): a Python implementation"},"content":{"rendered":"<p>Topographic position index (TPI) is a method of terrain classification where the altitude of each data point is evaluated against its neighbourhood. In a nutshell, for each data point, i.e. a pixel in raster DEM, we calculate its height difference from the immediate neighbourhood. A detailed explanation can be found in the <a href=\"https:\/\/landscapeanalysis.org\/2019\/tpi\/\">post on the TPI method<\/a>. Here, I present a Python implementation of the TPI, for all those who wish to venture into custom filter programming.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-medium wp-image-67\" src=\"http:\/\/landscapearchaeology.org\/wp-content\/uploads\/figures-jekyll\/TPI.png\" alt=\"tpi\" \/><br \/>\n<em>Terrain position index (concavities are in blue and convexities in red). These are animal tracks and some human paths on a Lidar derived DEM.<\/em><\/p>\n<p>The TPI method can easily be customised and experimented with in order to obtain that original result which will distinguish your work form other push-button solutions. We can play with window size or specific weight factors, for instance when window periphery should count less than the window centre. Such freedom can be very useful when fine tuning Lidar visualisations, for example.<\/p>\n<p>In what follows, I will only provide the basic architecture for a TPI filter, based on the <a href=\"https:\/\/landscapeanalysis.org\/2018\/numpy-loops\/\">sliding window routine developed for numpy<\/a>. In essence, TPI is just a kernel routine, which gathers information around each data point. At the same time it applies a weight factor, for instance to correct for the distance difference between diagonal pixels (touching by a corner) and orthogonal ones (touching by a side; below).<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-medium wp-image-67\" src=\"http:\/\/landscapearchaeology.org\/wp-content\/uploads\/figures-jekyll\/2021-12-filter_standard.jpg\" alt=\"2021-12-filter_standard.jpg\" \/><br \/>\n<em>Standard TPI kernel (moving window) with distance correction<\/em><\/p>\n<p>Below is a script that can be used in QGIS (open the Python console and just copy-paste the code) or run in pure Python.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-medium wp-image-67\" src=\"http:\/\/landscapearchaeology.org\/wp-content\/uploads\/figures-jekyll\/console.png\" alt=\"console\" \/><br \/>\n<em>Running the script in QGIS Python console<\/em><\/p>\n<div class=\"language-python highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code><span class=\"s\">&quot;&quot;&quot;\nTopographic position index for elevation models, \na mock script to be tuned according to you needs.\n\nAll comments are welcome at landscapeanalysis.org\/2019\/tpi\nby Zoran \u010cu\u010dkovi\u0107\n&quot;&quot;&quot;<\/span>\n\n<span class=\"kn\">import<\/span> <span class=\"nn\">gdal<\/span>\n<span class=\"kn\">import<\/span> <span class=\"nn\">numpy<\/span> <span class=\"k\">as<\/span> <span class=\"n\">np<\/span>\n\n<span class=\"c1\"># -------------- INPUT -----------------\n<\/span><span class=\"n\">elevation_model<\/span> <span class=\"o\">=<\/span><span class=\"s\">\"__path__to__my__dem__\"<\/span>\n<span class=\"n\">output_model<\/span> <span class=\"o\">=<\/span> <span class=\"s\">\"__path__to__my__output__file__\"<\/span>\n\n\n<span class=\"c1\"># ----------  create the moving window  ------------\n<\/span><span class=\"n\">r<\/span><span class=\"o\">=<\/span> <span class=\"mi\">5<\/span> <span class=\"c1\">#radius in pixels\n<\/span><span class=\"n\">win<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">ones<\/span><span class=\"p\">((<\/span><span class=\"mi\">2<\/span><span class=\"o\">*<\/span> <span class=\"n\">r<\/span> <span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">2<\/span><span class=\"o\">*<\/span> <span class=\"n\">r<\/span> <span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"p\">))<\/span>\n<span class=\"c1\"># ----------   or, copy paste your window matrix -------------\n# win = np.array( [    [0, 1, 1, 1, 0]\n#                      [1, 1, 1, 1, 1],\n#                      [1, 1, 0, 1, 1],\n#                      [1, 1, 1, 1, 1],\n#                      [0, 1, 1, 1, 0]  ])\n<\/span>\n<span class=\"c1\"># window radius is needed for the function,\n# deduce from window size (can be different for height and width\u2026)\n<\/span><span class=\"n\">r_y<\/span><span class=\"p\">,<\/span> <span class=\"n\">r_x<\/span>  <span class=\"o\">=<\/span> <span class=\"n\">win<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span><span class=\"o\">\/\/<\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span> <span class=\"n\">win<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span><span class=\"o\">\/\/<\/span><span class=\"mi\">2<\/span>\n<span class=\"n\">win<\/span><span class=\"p\">[<\/span><span class=\"n\">r_y<\/span><span class=\"p\">,<\/span> <span class=\"n\">r_x<\/span>  <span class=\"p\">]<\/span><span class=\"o\">=<\/span><span class=\"mi\">0<\/span>  <span class=\"c1\"># let's remove the central cell \n<\/span>\n<span class=\"k\">def<\/span> <span class=\"nf\">view<\/span> <span class=\"p\">(<\/span><span class=\"n\">offset_y<\/span><span class=\"p\">,<\/span> <span class=\"n\">offset_x<\/span><span class=\"p\">,<\/span> <span class=\"n\">shape<\/span><span class=\"p\">,<\/span> <span class=\"n\">step<\/span><span class=\"o\">=<\/span><span class=\"mi\">1<\/span><span class=\"p\">):<\/span>\n    <span class=\"s\">&quot;&quot;&quot;\n    Function returning two matching numpy views for moving window routines.\n    - &apos;offset_y&apos; and &apos;offset_x&apos; refer to the shift in relation to the analysed (central) cell \n    - &apos;shape&apos; are 2 dimensions of the data matrix (not of the window!)\n    - &apos;view_in&apos; is the shifted view and &apos;view_out&apos; is the position of central cells\n    (see on landscapeanalysis.org\/2018\/numpy-loops\/\n    &quot;&quot;&quot;<\/span>\n    <span class=\"n\">size_y<\/span><span class=\"p\">,<\/span> <span class=\"n\">size_x<\/span> <span class=\"o\">=<\/span> <span class=\"n\">shape<\/span>\n    <span class=\"n\">x<\/span><span class=\"p\">,<\/span> <span class=\"n\">y<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">abs<\/span><span class=\"p\">(<\/span><span class=\"n\">offset_x<\/span><span class=\"p\">),<\/span> <span class=\"nb\">abs<\/span><span class=\"p\">(<\/span><span class=\"n\">offset_y<\/span><span class=\"p\">)<\/span>\n    \n    <span class=\"n\">x_in<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">slice<\/span><span class=\"p\">(<\/span><span class=\"n\">x<\/span> <span class=\"p\">,<\/span> <span class=\"n\">size_x<\/span><span class=\"p\">,<\/span> <span class=\"n\">step<\/span><span class=\"p\">)<\/span> \n    <span class=\"n\">x_out<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">slice<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"n\">size_x<\/span> <span class=\"o\">-<\/span> <span class=\"n\">x<\/span><span class=\"p\">,<\/span> <span class=\"n\">step<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"n\">y_in<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">slice<\/span><span class=\"p\">(<\/span><span class=\"n\">y<\/span><span class=\"p\">,<\/span> <span class=\"n\">size_y<\/span><span class=\"p\">,<\/span> <span class=\"n\">step<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">y_out<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">slice<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"n\">size_y<\/span> <span class=\"o\">-<\/span> <span class=\"n\">y<\/span><span class=\"p\">,<\/span> <span class=\"n\">step<\/span><span class=\"p\">)<\/span>\n \n    <span class=\"c1\"># the swapping trick    \n<\/span>    <span class=\"k\">if<\/span> <span class=\"n\">offset_x<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">:<\/span> <span class=\"n\">x_in<\/span><span class=\"p\">,<\/span> <span class=\"n\">x_out<\/span> <span class=\"o\">=<\/span> <span class=\"n\">x_out<\/span><span class=\"p\">,<\/span> <span class=\"n\">x_in<\/span>                                 \n    <span class=\"k\">if<\/span> <span class=\"n\">offset_y<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">:<\/span> <span class=\"n\">y_in<\/span><span class=\"p\">,<\/span> <span class=\"n\">y_out<\/span> <span class=\"o\">=<\/span> <span class=\"n\">y_out<\/span><span class=\"p\">,<\/span> <span class=\"n\">y_in<\/span>\n \n    <span class=\"c1\"># return window view (in) and main view (out)\n<\/span>    <span class=\"k\">return<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">s_<\/span><span class=\"p\">[<\/span><span class=\"n\">y_in<\/span><span class=\"p\">,<\/span> <span class=\"n\">x_in<\/span><span class=\"p\">],<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">s_<\/span><span class=\"p\">[<\/span><span class=\"n\">y_out<\/span><span class=\"p\">,<\/span> <span class=\"n\">x_out<\/span><span class=\"p\">]<\/span>\n\n<span class=\"c1\"># ----  main routine  -------\n<\/span>\n<span class=\"n\">dem<\/span> <span class=\"o\">=<\/span> <span class=\"n\">gdal<\/span><span class=\"p\">.<\/span><span class=\"n\">Open<\/span><span class=\"p\">(<\/span><span class=\"n\">elevation_model<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">mx_z<\/span> <span class=\"o\">=<\/span> <span class=\"n\">dem<\/span><span class=\"p\">.<\/span><span class=\"n\">ReadAsArray<\/span><span class=\"p\">()<\/span>\n\n<span class=\"c1\">#matrices for temporary data\n<\/span><span class=\"n\">mx_temp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">zeros<\/span><span class=\"p\">(<\/span><span class=\"n\">mx_z<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">mx_count<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">zeros<\/span><span class=\"p\">(<\/span><span class=\"n\">mx_z<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">)<\/span>\n\n<span class=\"c1\"># loop through window and accumulate values\n<\/span><span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"n\">y<\/span><span class=\"p\">,<\/span><span class=\"n\">x<\/span><span class=\"p\">),<\/span> <span class=\"n\">weight<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">ndenumerate<\/span><span class=\"p\">(<\/span><span class=\"n\">win<\/span><span class=\"p\">):<\/span>\n    \n    <span class=\"k\">if<\/span> <span class=\"n\">weight<\/span> <span class=\"o\">==<\/span> <span class=\"mi\">0<\/span> <span class=\"p\">:<\/span> <span class=\"k\">continue<\/span>  <span class=\"c1\">#skip zero values !\n<\/span>    <span class=\"c1\"># determine views to extract data \n<\/span>    <span class=\"n\">view_in<\/span><span class=\"p\">,<\/span> <span class=\"n\">view_out<\/span> <span class=\"o\">=<\/span> <span class=\"n\">view<\/span><span class=\"p\">(<\/span><span class=\"n\">y<\/span> <span class=\"o\">-<\/span> <span class=\"n\">r_y<\/span><span class=\"p\">,<\/span> <span class=\"n\">x<\/span> <span class=\"o\">-<\/span> <span class=\"n\">r_x<\/span><span class=\"p\">,<\/span> <span class=\"n\">mx_z<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">)<\/span>\n    <span class=\"c1\"># using window weights (eg. for a Gaussian function)\n<\/span>    <span class=\"n\">mx_temp<\/span><span class=\"p\">[<\/span><span class=\"n\">view_out<\/span><span class=\"p\">]<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">mx_z<\/span><span class=\"p\">[<\/span><span class=\"n\">view_in<\/span><span class=\"p\">]<\/span>  <span class=\"o\">*<\/span> <span class=\"n\">weight<\/span>\n    \n   <span class=\"c1\"># track the number of neighbours \n<\/span>   <span class=\"c1\"># (this is used for weighted mean : \u03a3 weights*val \/ \u03a3 weights)\n<\/span>    <span class=\"n\">mx_count<\/span><span class=\"p\">[<\/span><span class=\"n\">view_out<\/span><span class=\"p\">]<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">weight<\/span>\n\n<span class=\"c1\"># this is TPI (spot height \u2013 average neighbourhood height)\n<\/span><span class=\"n\">out<\/span> <span class=\"o\">=<\/span> <span class=\"n\">mx_z<\/span> <span class=\"o\">-<\/span> <span class=\"n\">mx_temp<\/span> <span class=\"o\">\/<\/span> <span class=\"n\">mx_count<\/span>\n\n<span class=\"c1\"># writing output \n<\/span><span class=\"n\">driver<\/span> <span class=\"o\">=<\/span> <span class=\"n\">gdal<\/span><span class=\"p\">.<\/span><span class=\"n\">GetDriverByName<\/span><span class=\"p\">(<\/span><span class=\"s\">'GTiff'<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">ds<\/span> <span class=\"o\">=<\/span> <span class=\"n\">driver<\/span><span class=\"p\">.<\/span><span class=\"n\">Create<\/span><span class=\"p\">(<\/span><span class=\"n\">output_model<\/span><span class=\"p\">,<\/span> <span class=\"n\">mx_z<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">],<\/span> <span class=\"n\">mx_z<\/span><span class=\"p\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">],<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"n\">gdal<\/span><span class=\"p\">.<\/span><span class=\"n\">GDT_Float32<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">ds<\/span><span class=\"p\">.<\/span><span class=\"n\">SetProjection<\/span><span class=\"p\">(<\/span><span class=\"n\">dem<\/span><span class=\"p\">.<\/span><span class=\"n\">GetProjection<\/span><span class=\"p\">())<\/span>\n<span class=\"n\">ds<\/span><span class=\"p\">.<\/span><span class=\"n\">SetGeoTransform<\/span><span class=\"p\">(<\/span><span class=\"n\">dem<\/span><span class=\"p\">.<\/span><span class=\"n\">GetGeoTransform<\/span><span class=\"p\">())<\/span>\n<span class=\"n\">ds<\/span><span class=\"p\">.<\/span><span class=\"n\">GetRasterBand<\/span><span class=\"p\">(<\/span><span class=\"mi\">1<\/span><span class=\"p\">).<\/span><span class=\"n\">WriteArray<\/span><span class=\"p\">(<\/span><span class=\"n\">out<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">ds<\/span> <span class=\"o\">=<\/span> <span class=\"bp\">None<\/span>\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The script is using both, window geometry (square, circle etc.) and window weights which are applied to individual values. Geometry is modelled by setting unwanted window parts to zero, while weights should be non-zero values. Note that zero values are simply skipped as if non-existent, which will not happen with, say, weight = 0.00001.<\/p>\n<p>It is not difficult to create a window on the fly:<\/p>\n<div class=\"language-python highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code><span class=\"n\">r<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">5<\/span> <span class=\"c1\">#radius in pixels\n<\/span><span class=\"n\">win<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"p\">.<\/span><span class=\"n\">ones<\/span><span class=\"p\">((<\/span><span class=\"mi\">2<\/span><span class=\"o\">*<\/span><span class=\"n\">r<\/span> <span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">2<\/span><span class=\"o\">*<\/span><span class=\"n\">r<\/span> <span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"p\">))<\/span> <span class=\"c1\">#square window, all values are 1\n<\/span><span class=\"n\">win<\/span><span class=\"p\">[<\/span><span class=\"n\">r<\/span><span class=\"p\">,<\/span> <span class=\"n\">r<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span>  <span class=\"c1\">#skipping the central cell\n<\/span><\/code><\/pre>\n<\/div>\n<\/div>\n<p>For Gaussian window weights: see <a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/generated\/numpy.random.multivariate_normal.html\">numpy documentation<\/a>.<\/p>\n<p>However, I find more useful (and intuitive) to hard-code window definitions. This is very handy when comparing results on different datasets \u2013 there can be no error when the window is specified cell by cell. We can also keep a diary with window definitions that work best, like:<\/p>\n<ul>\n<li>Fine 5&#215;5 filter :\n<div class=\"language-python highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code>              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n              <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span>\n              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span> \n<\/code><\/pre>\n<\/div>\n<\/div>\n<\/li>\n<li>Aggressive 5&#215;5 filter\n<div class=\"language-python highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code>              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n              <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span>\n              <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span>\n              <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span>\n              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n<\/code><\/pre>\n<\/div>\n<\/div>\n<\/li>\n<li>A totally skewed filter for detecting linear patterns:\n<div class=\"language-python highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code>              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span>\n              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span>\n              <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n              <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span>\n              <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.5<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"mi\">0<\/span> \n<\/code><\/pre>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<p>Danger in this approach lies in the assumption that <strong>the central data point is in the geometric centre of the kernel window<\/strong>. For this to be true, dimensions of the window should be in odd numbers. For instance, in 3&#215;3 window, the central pixel is at (1,1) position. In a 4&#215;4 window, the centre falls between cells, which doesn\u2019t work! Therefore, be sure to count your pixels.<\/p>\n<p>Larger windows may become unwildely; in that case you can always use the on-the-fly method, as above.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Topographic position index (TPI) is a method of terrain classification where the altitude of each data point is evaluated against its neighbourhood. In a nutshell, for each data point, i.e. a pixel in raster DEM, we calculate its height difference from the immediate neighbourhood. A detailed explanation can be found in the post on the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[8,11],"class_list":["post-29","post","type-post","status-publish","format-standard","hentry","category-qgis-plugins","tag-code","tag-qgis"],"_links":{"self":[{"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=\/wp\/v2\/posts\/29","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=29"}],"version-history":[{"count":0,"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=\/wp\/v2\/posts\/29\/revisions"}],"wp:attachment":[{"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=29"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=29"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/landscapeanalysis.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=29"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}