[{"data":1,"prerenderedAt":1847},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fuse-rasterio-and-numpy-with-qgis-rasters":3},{"id":4,"title":5,"body":6,"description":1837,"extension":1838,"meta":1839,"navigation":223,"path":1843,"seo":1844,"stem":1845,"__hash__":1846},"docs\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fuse-rasterio-and-numpy-with-qgis-rasters\u002Findex.md","Use Rasterio and NumPy with QGIS Rasters",{"type":7,"value":8,"toc":1822},"minimark",[9,13,17,26,155,160,175,179,182,349,371,375,382,517,530,534,537,652,772,786,790,797,1045,1061,1065,1068,1122,1327,1355,1359,1362,1470,1479,1483,1486,1675,1683,1687,1690,1694,1723,1727,1733,1737,1756,1770,1776,1786,1790,1818],[10,11,5],"h1",{"id":12},"use-rasterio-and-numpy-with-qgis-rasters",[14,15,16],"p",{},"QGIS's raster calculator and Processing algorithms cover a great deal, but some raster work is simply clearer as array arithmetic: a vegetation index with conditional masking, a reclassification driven by a lookup table, a moving-window statistic, a model trained in scikit-learn applied pixel by pixel. Rasterio gives a Pythonic interface to GDAL — windows, masks, profiles — and NumPy does the arithmetic. QGIS remains the place to load, inspect and style the inputs and outputs.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002F","PyQGIS and the Python Data Stack",". It opens the file behind a QGIS raster layer with rasterio, reads masked arrays and windows, computes an index and a reclassification, writes the result with a correct profile, and loads and styles it in QGIS.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,91,97,104,109,113,116,120,126,130,133,137,143,148,151],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 242","img","A QGIS raster layer's file read by rasterio into masked arrays, computed with NumPy, written with an updated profile and loaded back into QGIS","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"QGIS layer to array and back",[40,41,42],"desc",{},"A QGIS raster layer points to a file. Rasterio opens that file and reads bands as masked NumPy arrays, respecting NoData. NumPy computes the result. Rasterio writes it with a profile copied from the input, updated for the output data type and NoData value. QGIS loads the new file as a layer and applies a style.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","242","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"rioFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","File in, arrays through, file out",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"24","96","160","90","#eef7f4","#0f766e","stroke-width:2",[68,86,90],{"x":87,"y":88,"style":89,"fill":83,"textAnchor":74},"104","132.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","QGIS layer",[68,92,96],{"x":87,"y":93,"style":94,"fill":95,"textAnchor":74},"156.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#59645f","source() → path",[98,99],"line",{"x1":100,"y1":101,"x2":102,"y2":101,"stroke":66,"style":103},"184","141","214","stroke-width:1.8;marker-end:url(#rioFlowArrow)",[44,105],{"x":106,"y":79,"width":80,"height":81,"rx":58,"fill":107,"stroke":108,"style":84},"218","#eff3ff","#2563eb",[68,110,112],{"x":111,"y":88,"style":89,"fill":108,"textAnchor":74},"298","rasterio read",[68,114,115],{"x":111,"y":93,"style":94,"fill":95,"textAnchor":74},"masked arrays",[98,117],{"x1":118,"y1":101,"x2":119,"y2":101,"stroke":66,"style":103},"378","408",[44,121],{"x":122,"y":79,"width":123,"height":81,"rx":58,"fill":124,"stroke":125,"style":84},"412","140","#fdf2e2","#b45309",[68,127,129],{"x":128,"y":88,"style":89,"fill":125,"textAnchor":74},"482","NumPy",[68,131,132],{"x":128,"y":93,"style":94,"fill":95,"textAnchor":74},"index, reclass",[98,134],{"x1":135,"y1":101,"x2":136,"y2":101,"stroke":66,"style":103},"552","582",[44,138],{"x":139,"y":79,"width":140,"height":81,"rx":58,"fill":141,"stroke":142,"style":84},"586","150","#e8efe6","#15803d",[68,144,147],{"x":145,"y":88,"style":89,"fill":146,"textAnchor":74},"661","#166534","write + load",[68,149,150],{"x":145,"y":93,"style":94,"fill":95,"textAnchor":74},"profile, NoData",[68,152,154],{"x":70,"y":153,"style":94,"fill":95,"textAnchor":74},"222","georeferencing travels in the profile — copy it, then change only what differs",[156,157,159],"h2",{"id":158},"prerequisites","Prerequisites",[161,162,163,172],"ul",{},[164,165,166,167,171],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series, with rasterio installed into QGIS's Python. On conda-based and Linux installations rasterio shares QGIS's GDAL; on Windows, install it with the OSGeo4W shell or pip as described in ",[21,168,170],{"href":169},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-python-packages-into-qgis\u002F","installing Python packages into QGIS",".",[164,173,174],{},"A raster layer backed by a file — GeoTIFF, VRT, COG. Rasters from WMS or other services have no file for rasterio to open.",[156,176,178],{"id":177},"open-the-file-behind-a-layer","Open the file behind a layer",[14,180,181],{},"A QGIS raster layer's source is usually a file path, possibly with provider options appended. Rasterio needs just the path.",[183,184,189],"pre",{"className":185,"code":186,"language":187,"meta":188,"style":188},"language-python shiki shiki-themes github-dark","import rasterio\nfrom qgis.core import QgsProject, QgsProviderRegistry\n\nlayer = QgsProject.instance().mapLayersByName(\"sentinel2_2026_06\")[0]\nparts = QgsProviderRegistry.instance().decodeUri(\"gdal\", layer.source())\npath = parts[\"path\"]\n\nwith rasterio.open(path) as src:\n    print(src.count, \"bands\", src.width, \"x\", src.height, src.dtypes[0])\n    print(src.crs, src.res, src.nodata)\n    print(src.descriptions)          # band names, if the file has them\n","python","",[190,191,192,204,218,225,250,267,283,288,303,329,337],"code",{"__ignoreMap":188},[193,194,196,200],"span",{"class":98,"line":195},1,[193,197,199],{"class":198},"snl16","import",[193,201,203],{"class":202},"s95oV"," rasterio\n",[193,205,207,210,213,215],{"class":98,"line":206},2,[193,208,209],{"class":198},"from",[193,211,212],{"class":202}," qgis.core ",[193,214,199],{"class":198},[193,216,217],{"class":202}," QgsProject, QgsProviderRegistry\n",[193,219,221],{"class":98,"line":220},3,[193,222,224],{"emptyLinePlaceholder":223},true,"\n",[193,226,228,231,234,237,241,244,247],{"class":98,"line":227},4,[193,229,230],{"class":202},"layer ",[193,232,233],{"class":198},"=",[193,235,236],{"class":202}," QgsProject.instance().mapLayersByName(",[193,238,240],{"class":239},"sU2Wk","\"sentinel2_2026_06\"",[193,242,243],{"class":202},")[",[193,245,46],{"class":246},"sDLfK",[193,248,249],{"class":202},"]\n",[193,251,253,256,258,261,264],{"class":98,"line":252},5,[193,254,255],{"class":202},"parts ",[193,257,233],{"class":198},[193,259,260],{"class":202}," QgsProviderRegistry.instance().decodeUri(",[193,262,263],{"class":239},"\"gdal\"",[193,265,266],{"class":202},", layer.source())\n",[193,268,270,273,275,278,281],{"class":98,"line":269},6,[193,271,272],{"class":202},"path ",[193,274,233],{"class":198},[193,276,277],{"class":202}," parts[",[193,279,280],{"class":239},"\"path\"",[193,282,249],{"class":202},[193,284,286],{"class":98,"line":285},7,[193,287,224],{"emptyLinePlaceholder":223},[193,289,291,294,297,300],{"class":98,"line":290},8,[193,292,293],{"class":198},"with",[193,295,296],{"class":202}," rasterio.open(path) ",[193,298,299],{"class":198},"as",[193,301,302],{"class":202}," src:\n",[193,304,306,309,312,315,318,321,324,326],{"class":98,"line":305},9,[193,307,308],{"class":246},"    print",[193,310,311],{"class":202},"(src.count, ",[193,313,314],{"class":239},"\"bands\"",[193,316,317],{"class":202},", src.width, ",[193,319,320],{"class":239},"\"x\"",[193,322,323],{"class":202},", src.height, src.dtypes[",[193,325,46],{"class":246},[193,327,328],{"class":202},"])\n",[193,330,332,334],{"class":98,"line":331},10,[193,333,308],{"class":246},[193,335,336],{"class":202},"(src.crs, src.res, src.nodata)\n",[193,338,340,342,345],{"class":98,"line":339},11,[193,341,308],{"class":246},[193,343,344],{"class":202},"(src.descriptions)          ",[193,346,348],{"class":347},"sjoCn","# band names, if the file has them\n",[14,350,351,355,356,359,360,363,364,367,368,370],{},[352,353,354],"strong",{},"Breakdown:"," ",[190,357,358],{},"decodeUri"," with the ",[190,361,362],{},"gdal"," provider key separates the file path from any layer options, which is safer than using ",[190,365,366],{},"layer.source()"," directly. The dataset object reports band count, size, data type, CRS, pixel size and NoData — compare them with QGIS's Layer Properties to confirm both see the same file. Using ",[190,369,293],{}," closes the file when done, which matters on Windows where an open handle blocks QGIS from overwriting or deleting the file.",[156,372,374],{"id":373},"read-masked-arrays","Read masked arrays",[14,376,377,378,381],{},"NoData pixels must not take part in arithmetic. Reading with ",[190,379,380],{},"masked=True"," returns NumPy masked arrays in which NoData is masked out automatically.",[183,383,385],{"className":185,"code":384,"language":187,"meta":188,"style":188},"import numpy as np\n\nwith rasterio.open(path) as src:\n    red = src.read(4, masked=True).astype(\"float32\")      # Sentinel-2 B04\n    nir = src.read(8, masked=True).astype(\"float32\")      # Sentinel-2 B08\n    profile = src.profile\n\nprint(red.mask.sum(), \"masked pixels;\", float(red.mean()), \"mean red\")\n",[190,386,387,399,403,413,450,478,488,492],{"__ignoreMap":188},[193,388,389,391,394,396],{"class":98,"line":195},[193,390,199],{"class":198},[193,392,393],{"class":202}," numpy ",[193,395,299],{"class":198},[193,397,398],{"class":202}," np\n",[193,400,401],{"class":98,"line":206},[193,402,224],{"emptyLinePlaceholder":223},[193,404,405,407,409,411],{"class":98,"line":220},[193,406,293],{"class":198},[193,408,296],{"class":202},[193,410,299],{"class":198},[193,412,302],{"class":202},[193,414,415,418,420,423,426,429,433,435,438,441,444,447],{"class":98,"line":227},[193,416,417],{"class":202},"    red ",[193,419,233],{"class":198},[193,421,422],{"class":202}," src.read(",[193,424,425],{"class":246},"4",[193,427,428],{"class":202},", ",[193,430,432],{"class":431},"s9osk","masked",[193,434,233],{"class":198},[193,436,437],{"class":246},"True",[193,439,440],{"class":202},").astype(",[193,442,443],{"class":239},"\"float32\"",[193,445,446],{"class":202},")      ",[193,448,449],{"class":347},"# Sentinel-2 B04\n",[193,451,452,455,457,459,461,463,465,467,469,471,473,475],{"class":98,"line":252},[193,453,454],{"class":202},"    nir ",[193,456,233],{"class":198},[193,458,422],{"class":202},[193,460,58],{"class":246},[193,462,428],{"class":202},[193,464,432],{"class":431},[193,466,233],{"class":198},[193,468,437],{"class":246},[193,470,440],{"class":202},[193,472,443],{"class":239},[193,474,446],{"class":202},[193,476,477],{"class":347},"# Sentinel-2 B08\n",[193,479,480,483,485],{"class":98,"line":269},[193,481,482],{"class":202},"    profile ",[193,484,233],{"class":198},[193,486,487],{"class":202}," src.profile\n",[193,489,490],{"class":98,"line":285},[193,491,224],{"emptyLinePlaceholder":223},[193,493,494,497,500,503,505,508,511,514],{"class":98,"line":290},[193,495,496],{"class":246},"print",[193,498,499],{"class":202},"(red.mask.sum(), ",[193,501,502],{"class":239},"\"masked pixels;\"",[193,504,428],{"class":202},[193,506,507],{"class":246},"float",[193,509,510],{"class":202},"(red.mean()), ",[193,512,513],{"class":239},"\"mean red\"",[193,515,516],{"class":202},")\n",[14,518,519,521,522,525,526,529],{},[352,520,354],{}," Masked arrays carry their mask through arithmetic, so a NoData pixel in either band stays masked in the result. Converting to float before computing an index avoids integer overflow and integer division on reflectance stored as ",[190,523,524],{},"uint16",". Keeping the source ",[190,527,528],{},"profile"," — CRS, transform, size, dtype, compression — is how the output inherits the input's georeferencing. Band numbers in rasterio start at 1, like in QGIS and GDAL.",[156,531,533],{"id":532},"compute-an-index","Compute an index",[14,535,536],{},"A normalised difference index is the canonical example: two bands, one formula, a few lines, with masking and division by zero handled.",[14,538,539],{},[29,540,543,546,549,552,559,562,566,570,574,578,582,586,590,596,601,605,609,613,620,623,627,633,637,641,646],{"viewBox":541,"role":32,"ariaLabel":542,"xmlns":34},"0 0 760 240","NDVI computed from NIR and red bands with zero denominators masked, and the value ranges for water, sparse and dense vegetation",[36,544,545],{},"NDVI from two bands",[40,547,548],{},"The NIR and red bands are read as masked float arrays. NDVI is computed as NIR minus red divided by NIR plus red. Pixels where the denominator is zero are masked to avoid division by zero. The result ranges from minus one to one: water and bare ground near or below zero, sparse vegetation around 0.2 to 0.4, dense vegetation above 0.6.",[44,550],{"x":46,"y":46,"width":47,"height":551,"fill":49},"240",[51,553,554],{},[54,555,557],{"id":556,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"rioNdviArrow",[63,558],{"d":65,"fill":66},[68,560,561],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"(NIR − red) \u002F (NIR + red)",[44,563],{"x":78,"y":564,"width":80,"height":565,"rx":58,"fill":107,"stroke":108,"style":84},"56","110",[68,567,569],{"x":87,"y":568,"style":89,"fill":108,"textAnchor":74},"102.78","NIR band",[68,571,573],{"x":87,"y":572,"style":94,"fill":95,"textAnchor":74},"126.78","B08 · masked",[44,575],{"x":78,"y":576,"width":80,"height":577,"rx":58,"fill":124,"stroke":125,"style":84},"176","50",[68,579,581],{"x":87,"y":580,"style":89,"fill":125,"textAnchor":74},"194.78","red band",[68,583,585],{"x":87,"y":584,"style":94,"fill":95,"textAnchor":74},"214.78","B04 · masked",[98,587],{"x1":100,"y1":123,"x2":588,"y2":123,"stroke":66,"style":589},"226","stroke-width:1.8;marker-end:url(#rioNdviArrow)",[44,591],{"x":592,"y":593,"width":594,"height":595,"rx":58,"fill":82,"stroke":83,"style":84},"230","80","200","120",[68,597,600],{"x":598,"y":599,"style":89,"fill":83,"textAnchor":74},"330","119.78","NDVI",[68,602,604],{"x":598,"y":603,"style":94,"fill":95,"textAnchor":74},"143.78","mask where",[68,606,608],{"x":598,"y":607,"style":94,"fill":95,"textAnchor":74},"167.78","NIR + red = 0",[98,610],{"x1":611,"y1":123,"x2":612,"y2":123,"stroke":66,"style":589},"430","472",[44,614],{"x":615,"y":616,"width":617,"height":618,"rx":425,"fill":124,"stroke":125,"style":619},"476","112","260","26","stroke-width:1.5",[44,621],{"x":622,"y":616,"width":593,"height":618,"rx":46,"fill":141,"stroke":142,"style":619},"556",[44,624],{"x":625,"y":616,"width":626,"height":618,"rx":46,"fill":142,"stroke":142,"style":619},"636","100",[68,628,632],{"x":629,"y":630,"style":631,"fill":125,"textAnchor":74},"516","158","text-anchor:middle;font-size:10.0px;font-family:sans-serif","≤ 0 water",[68,634,636],{"x":635,"y":576,"style":631,"fill":146,"textAnchor":74},"596","0.2–0.4 sparse",[68,638,640],{"x":639,"y":630,"style":631,"fill":146,"textAnchor":74},"686","> 0.6 dense",[68,642,645],{"x":615,"y":87,"style":643,"fill":66,"textAnchor":644},"text-anchor:start;font-size:10.0px;font-family:monospace","start","−1",[68,647,651],{"x":648,"y":87,"style":649,"fill":66,"textAnchor":650},"736","text-anchor:end;font-size:10.0px;font-family:monospace","end","+1",[183,653,655],{"className":185,"code":654,"language":187,"meta":188,"style":188},"denominator = nir + red\nndvi = np.ma.where(denominator == 0, np.ma.masked, (nir - red) \u002F denominator)\nndvi = np.ma.clip(ndvi, -1, 1)\nprint(\"NDVI range\", float(ndvi.min()), float(ndvi.max()),\n      \"| vegetated share\", float((ndvi > 0.4).sum() \u002F ndvi.count()))\n",[190,656,657,673,704,724,746],{"__ignoreMap":188},[193,658,659,662,664,667,670],{"class":98,"line":195},[193,660,661],{"class":202},"denominator ",[193,663,233],{"class":198},[193,665,666],{"class":202}," nir ",[193,668,669],{"class":198},"+",[193,671,672],{"class":202}," red\n",[193,674,675,678,680,683,686,689,692,695,698,701],{"class":98,"line":206},[193,676,677],{"class":202},"ndvi ",[193,679,233],{"class":198},[193,681,682],{"class":202}," np.ma.where(denominator ",[193,684,685],{"class":198},"==",[193,687,688],{"class":246}," 0",[193,690,691],{"class":202},", np.ma.masked, (nir ",[193,693,694],{"class":198},"-",[193,696,697],{"class":202}," red) ",[193,699,700],{"class":198},"\u002F",[193,702,703],{"class":202}," denominator)\n",[193,705,706,708,710,713,715,718,720,722],{"class":98,"line":220},[193,707,677],{"class":202},[193,709,233],{"class":198},[193,711,712],{"class":202}," np.ma.clip(ndvi, ",[193,714,694],{"class":198},[193,716,717],{"class":246},"1",[193,719,428],{"class":202},[193,721,717],{"class":246},[193,723,516],{"class":202},[193,725,726,728,731,734,736,738,741,743],{"class":98,"line":227},[193,727,496],{"class":246},[193,729,730],{"class":202},"(",[193,732,733],{"class":239},"\"NDVI range\"",[193,735,428],{"class":202},[193,737,507],{"class":246},[193,739,740],{"class":202},"(ndvi.min()), ",[193,742,507],{"class":246},[193,744,745],{"class":202},"(ndvi.max()),\n",[193,747,748,751,753,755,758,761,764,767,769],{"class":98,"line":252},[193,749,750],{"class":239},"      \"| vegetated share\"",[193,752,428],{"class":202},[193,754,507],{"class":246},[193,756,757],{"class":202},"((ndvi ",[193,759,760],{"class":198},">",[193,762,763],{"class":246}," 0.4",[193,765,766],{"class":202},").sum() ",[193,768,700],{"class":198},[193,770,771],{"class":202}," ndvi.count()))\n",[14,773,774,776,777,780,781,785],{},[352,775,354],{}," Masking where the denominator is zero avoids division warnings and infinite values over pixels that are dark in both bands. Clipping guards against values just outside the valid range caused by sensor noise or scaling. ",[190,778,779],{},"ndvi.count()"," counts unmasked pixels only, so the vegetated share is a proportion of valid land, not of the whole rectangle. The same expression in QGIS's raster calculator would work too, as shown in ",[21,782,784],{"href":783},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fraster-calculator-pyqgis\u002F","the raster calculator recipe",", but would not mask zero denominators as cleanly.",[156,787,789],{"id":788},"reclassify-with-a-lookup-table","Reclassify with a lookup table",[14,791,792,793,796],{},"Reclassification maps ranges or codes to new values. With NumPy, ",[190,794,795],{},"digitize"," handles ranges and a lookup array handles codes, both in one vectorised step.",[183,798,800],{"className":185,"code":799,"language":187,"meta":188,"style":188},"breaks = np.array([0.0, 0.2, 0.4, 0.6])           # class edges\nclasses = np.ma.masked_array(np.digitize(ndvi.filled(-2), breaks), mask=ndvi.mask)\n# 0: \u003C 0 (water\u002Fbare), 1: 0–0.2, 2: 0.2–0.4, 3: 0.4–0.6, 4: > 0.6\ncounts = {int(c): int((classes == c).sum()) for c in range(5)}\nprint(counts)\n\nwith rasterio.open(\"\u002Fdata\u002Flandcover\u002Fcorine_2018.tif\") as lc:\n    codes = lc.read(1)\nlookup = np.zeros(1000, dtype=\"uint8\")             # code → simplified class\nlookup[[111, 112]] = 1                             # urban\nlookup[[311, 312, 313]] = 2                        # forest\nsimplified = lookup[codes]\n",[190,801,802,836,862,867,912,919,923,941,955,984,1007,1034],{"__ignoreMap":188},[193,803,804,807,809,812,815,817,820,822,825,827,830,833],{"class":98,"line":195},[193,805,806],{"class":202},"breaks ",[193,808,233],{"class":198},[193,810,811],{"class":202}," np.array([",[193,813,814],{"class":246},"0.0",[193,816,428],{"class":202},[193,818,819],{"class":246},"0.2",[193,821,428],{"class":202},[193,823,824],{"class":246},"0.4",[193,826,428],{"class":202},[193,828,829],{"class":246},"0.6",[193,831,832],{"class":202},"])           ",[193,834,835],{"class":347},"# class edges\n",[193,837,838,841,843,846,848,851,854,857,859],{"class":98,"line":206},[193,839,840],{"class":202},"classes ",[193,842,233],{"class":198},[193,844,845],{"class":202}," np.ma.masked_array(np.digitize(ndvi.filled(",[193,847,694],{"class":198},[193,849,850],{"class":246},"2",[193,852,853],{"class":202},"), breaks), ",[193,855,856],{"class":431},"mask",[193,858,233],{"class":198},[193,860,861],{"class":202},"ndvi.mask)\n",[193,863,864],{"class":98,"line":220},[193,865,866],{"class":347},"# 0: \u003C 0 (water\u002Fbare), 1: 0–0.2, 2: 0.2–0.4, 3: 0.4–0.6, 4: > 0.6\n",[193,868,869,872,874,877,880,883,885,888,890,893,896,899,902,905,907,909],{"class":98,"line":227},[193,870,871],{"class":202},"counts ",[193,873,233],{"class":198},[193,875,876],{"class":202}," {",[193,878,879],{"class":246},"int",[193,881,882],{"class":202},"(c): ",[193,884,879],{"class":246},[193,886,887],{"class":202},"((classes ",[193,889,685],{"class":198},[193,891,892],{"class":202}," c).sum()) ",[193,894,895],{"class":198},"for",[193,897,898],{"class":202}," c ",[193,900,901],{"class":198},"in",[193,903,904],{"class":246}," range",[193,906,730],{"class":202},[193,908,59],{"class":246},[193,910,911],{"class":202},")}\n",[193,913,914,916],{"class":98,"line":252},[193,915,496],{"class":246},[193,917,918],{"class":202},"(counts)\n",[193,920,921],{"class":98,"line":269},[193,922,224],{"emptyLinePlaceholder":223},[193,924,925,927,930,933,936,938],{"class":98,"line":285},[193,926,293],{"class":198},[193,928,929],{"class":202}," rasterio.open(",[193,931,932],{"class":239},"\"\u002Fdata\u002Flandcover\u002Fcorine_2018.tif\"",[193,934,935],{"class":202},") ",[193,937,299],{"class":198},[193,939,940],{"class":202}," lc:\n",[193,942,943,946,948,951,953],{"class":98,"line":290},[193,944,945],{"class":202},"    codes ",[193,947,233],{"class":198},[193,949,950],{"class":202}," lc.read(",[193,952,717],{"class":246},[193,954,516],{"class":202},[193,956,957,960,962,965,968,970,973,975,978,981],{"class":98,"line":305},[193,958,959],{"class":202},"lookup ",[193,961,233],{"class":198},[193,963,964],{"class":202}," np.zeros(",[193,966,967],{"class":246},"1000",[193,969,428],{"class":202},[193,971,972],{"class":431},"dtype",[193,974,233],{"class":198},[193,976,977],{"class":239},"\"uint8\"",[193,979,980],{"class":202},")             ",[193,982,983],{"class":347},"# code → simplified class\n",[193,985,986,989,992,994,996,999,1001,1004],{"class":98,"line":331},[193,987,988],{"class":202},"lookup[[",[193,990,991],{"class":246},"111",[193,993,428],{"class":202},[193,995,616],{"class":246},[193,997,998],{"class":202},"]] ",[193,1000,233],{"class":198},[193,1002,1003],{"class":246}," 1",[193,1005,1006],{"class":347},"                             # urban\n",[193,1008,1009,1011,1014,1016,1019,1021,1024,1026,1028,1031],{"class":98,"line":339},[193,1010,988],{"class":202},[193,1012,1013],{"class":246},"311",[193,1015,428],{"class":202},[193,1017,1018],{"class":246},"312",[193,1020,428],{"class":202},[193,1022,1023],{"class":246},"313",[193,1025,998],{"class":202},[193,1027,233],{"class":198},[193,1029,1030],{"class":246}," 2",[193,1032,1033],{"class":347},"                        # forest\n",[193,1035,1037,1040,1042],{"class":98,"line":1036},12,[193,1038,1039],{"class":202},"simplified ",[193,1041,233],{"class":198},[193,1043,1044],{"class":202}," lookup[codes]\n",[14,1046,1047,355,1049,1052,1053,1056,1057,171],{},[352,1048,354],{},[190,1050,1051],{},"np.digitize"," returns, for each value, the index of the bin it falls in; filling masked pixels with an out-of-range value first and re-applying the mask afterwards keeps NoData separate. For categorical rasters, indexing a lookup array with the code array reclassifies every pixel in one step, far faster than a loop or chained ",[190,1054,1055],{},"where"," calls; the array just needs to be at least as long as the largest code. QGIS's own approach is covered in ",[21,1058,1060],{"href":1059},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Freclassify-raster-values-pyqgis\u002F","reclassifying raster values",[156,1062,1064],{"id":1063},"write-the-result-correctly","Write the result correctly",[14,1066,1067],{},"The output needs the input's georeferencing but its own data type, band count and NoData value. Copying the profile and updating only what differs is the reliable pattern.",[14,1069,1070],{},[29,1071,1073,1076,1079,1081,1084,1089,1094,1098,1102,1106,1109,1113,1116,1119],{"viewBox":541,"role":32,"ariaLabel":1072,"xmlns":34},"The input raster profile copied to the output with georeferencing kept and data type, band count, NoData and compression updated",[36,1074,1075],{},"Copy the profile, change what differs",[40,1077,1078],{},"The input profile holds driver, width, height, CRS, transform, data type, band count, NoData and compression. For the NDVI output, width, height, CRS and transform are kept; data type changes to float32, count to one, NoData to minus 9999, and compression and tiling are set for a cloud-friendly GeoTIFF.",[44,1080],{"x":46,"y":46,"width":47,"height":551,"fill":49},[68,1082,1083],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Keep the grid, change the payload",[44,1085],{"x":78,"y":1086,"width":1087,"height":1088,"rx":58,"fill":141,"stroke":142,"style":84},"52","340","168",[68,1090,1093],{"x":1091,"y":1092,"style":89,"fill":146,"textAnchor":74},"194","94.78","kept from input",[68,1095,1097],{"x":1091,"y":1096,"style":94,"fill":66,"textAnchor":74},"124.78","width · height",[68,1099,1101],{"x":1091,"y":1100,"style":94,"fill":66,"textAnchor":74},"154.78","crs · transform",[68,1103,1105],{"x":1091,"y":1104,"style":94,"fill":95,"textAnchor":74},"184.78","same grid, same place",[44,1107],{"x":1108,"y":1086,"width":1087,"height":1088,"rx":58,"fill":124,"stroke":125,"style":84},"396",[68,1110,1112],{"x":1111,"y":1092,"style":89,"fill":125,"textAnchor":74},"566","updated for output",[68,1114,1115],{"x":1111,"y":1096,"style":94,"fill":66,"textAnchor":74},"dtype float32 · count 1",[68,1117,1118],{"x":1111,"y":1100,"style":94,"fill":66,"textAnchor":74},"nodata −9999",[68,1120,1121],{"x":1111,"y":1104,"style":94,"fill":95,"textAnchor":74},"compress, tiled",[183,1123,1125],{"className":185,"code":1124,"language":187,"meta":188,"style":188},"out_profile = profile.copy()\nout_profile.update(dtype=\"float32\", count=1, nodata=-9999.0,\n                   compress=\"deflate\", predictor=3, tiled=True,\n                   blockxsize=512, blockysize=512, driver=\"GTiff\")\n\nout_path = \"\u002Fdata\u002Fresults\u002Fndvi_2026_06.tif\"\nwith rasterio.open(out_path, \"w\", **out_profile) as dst:\n    dst.write(ndvi.filled(-9999.0).astype(\"float32\"), 1)\n    dst.set_band_description(1, \"NDVI\")\n    dst.update_tags(source=path, formula=\"(B08-B04)\u002F(B08+B04)\")\n",[190,1126,1127,1137,1171,1202,1233,1237,1247,1270,1290,1304],{"__ignoreMap":188},[193,1128,1129,1132,1134],{"class":98,"line":195},[193,1130,1131],{"class":202},"out_profile ",[193,1133,233],{"class":198},[193,1135,1136],{"class":202}," profile.copy()\n",[193,1138,1139,1142,1144,1146,1148,1150,1153,1155,1157,1159,1162,1165,1168],{"class":98,"line":206},[193,1140,1141],{"class":202},"out_profile.update(",[193,1143,972],{"class":431},[193,1145,233],{"class":198},[193,1147,443],{"class":239},[193,1149,428],{"class":202},[193,1151,1152],{"class":431},"count",[193,1154,233],{"class":198},[193,1156,717],{"class":246},[193,1158,428],{"class":202},[193,1160,1161],{"class":431},"nodata",[193,1163,1164],{"class":198},"=-",[193,1166,1167],{"class":246},"9999.0",[193,1169,1170],{"class":202},",\n",[193,1172,1173,1176,1178,1181,1183,1186,1188,1191,1193,1196,1198,1200],{"class":98,"line":220},[193,1174,1175],{"class":431},"                   compress",[193,1177,233],{"class":198},[193,1179,1180],{"class":239},"\"deflate\"",[193,1182,428],{"class":202},[193,1184,1185],{"class":431},"predictor",[193,1187,233],{"class":198},[193,1189,1190],{"class":246},"3",[193,1192,428],{"class":202},[193,1194,1195],{"class":431},"tiled",[193,1197,233],{"class":198},[193,1199,437],{"class":246},[193,1201,1170],{"class":202},[193,1203,1204,1207,1209,1212,1214,1217,1219,1221,1223,1226,1228,1231],{"class":98,"line":227},[193,1205,1206],{"class":431},"                   blockxsize",[193,1208,233],{"class":198},[193,1210,1211],{"class":246},"512",[193,1213,428],{"class":202},[193,1215,1216],{"class":431},"blockysize",[193,1218,233],{"class":198},[193,1220,1211],{"class":246},[193,1222,428],{"class":202},[193,1224,1225],{"class":431},"driver",[193,1227,233],{"class":198},[193,1229,1230],{"class":239},"\"GTiff\"",[193,1232,516],{"class":202},[193,1234,1235],{"class":98,"line":252},[193,1236,224],{"emptyLinePlaceholder":223},[193,1238,1239,1242,1244],{"class":98,"line":269},[193,1240,1241],{"class":202},"out_path ",[193,1243,233],{"class":198},[193,1245,1246],{"class":239}," \"\u002Fdata\u002Fresults\u002Fndvi_2026_06.tif\"\n",[193,1248,1249,1251,1254,1257,1259,1262,1265,1267],{"class":98,"line":285},[193,1250,293],{"class":198},[193,1252,1253],{"class":202}," rasterio.open(out_path, ",[193,1255,1256],{"class":239},"\"w\"",[193,1258,428],{"class":202},[193,1260,1261],{"class":198},"**",[193,1263,1264],{"class":202},"out_profile) ",[193,1266,299],{"class":198},[193,1268,1269],{"class":202}," dst:\n",[193,1271,1272,1275,1277,1279,1281,1283,1286,1288],{"class":98,"line":290},[193,1273,1274],{"class":202},"    dst.write(ndvi.filled(",[193,1276,694],{"class":198},[193,1278,1167],{"class":246},[193,1280,440],{"class":202},[193,1282,443],{"class":239},[193,1284,1285],{"class":202},"), ",[193,1287,717],{"class":246},[193,1289,516],{"class":202},[193,1291,1292,1295,1297,1299,1302],{"class":98,"line":305},[193,1293,1294],{"class":202},"    dst.set_band_description(",[193,1296,717],{"class":246},[193,1298,428],{"class":202},[193,1300,1301],{"class":239},"\"NDVI\"",[193,1303,516],{"class":202},[193,1305,1306,1309,1312,1314,1317,1320,1322,1325],{"class":98,"line":331},[193,1307,1308],{"class":202},"    dst.update_tags(",[193,1310,1311],{"class":431},"source",[193,1313,233],{"class":198},[193,1315,1316],{"class":202},"path, ",[193,1318,1319],{"class":431},"formula",[193,1321,233],{"class":198},[193,1323,1324],{"class":239},"\"(B08-B04)\u002F(B08+B04)\"",[193,1326,516],{"class":202},[14,1328,1329,1331,1332,428,1335,428,1338,1341,1342,1345,1346,1349,1350,1354],{},[352,1330,354],{}," Keeping ",[190,1333,1334],{},"crs",[190,1336,1337],{},"transform",[190,1339,1340],{},"width"," and ",[190,1343,1344],{},"height"," from the input places the output exactly on the input grid. ",[190,1347,1348],{},"filled(-9999)"," replaces masked pixels with the declared NoData value before writing. Deflate compression with predictor 3 suits floating-point data; tiled 512-pixel blocks make the file efficient for QGIS to render at any zoom and are a step towards a Cloud Optimized GeoTIFF, covered in ",[21,1351,1353],{"href":1352},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fexport-cloud-optimized-geotiff-pyqgis\u002F","exporting a COG",". The band description and tags record what the file contains and how it was made.",[156,1356,1358],{"id":1357},"load-and-style-the-result-in-qgis","Load and style the result in QGIS",[14,1360,1361],{},"The new file is an ordinary GeoTIFF; QGIS loads it like any other and a pseudocolour renderer makes it readable.",[183,1363,1365],{"className":185,"code":1364,"language":187,"meta":188,"style":188},"from qgis.core import (QgsRasterLayer, QgsColorRampShader, QgsRasterShader,\n                       QgsSingleBandPseudoColorRenderer, QgsStyle)\n\nndvi_layer = QgsRasterLayer(out_path, \"NDVI June 2026\")\nshader_fn = QgsColorRampShader(-0.2, 0.9, QgsStyle.defaultStyle().colorRamp(\"RdYlGn\"))\nshader_fn.classifyColorRamp(9)\nshader = QgsRasterShader()\nshader.setRasterShaderFunction(shader_fn)\nndvi_layer.setRenderer(QgsSingleBandPseudoColorRenderer(ndvi_layer.dataProvider(), 1, shader))\nQgsProject.instance().addMapLayer(ndvi_layer)\n",[190,1366,1367,1378,1383,1387,1402,1430,1440,1450,1455,1465],{"__ignoreMap":188},[193,1368,1369,1371,1373,1375],{"class":98,"line":195},[193,1370,209],{"class":198},[193,1372,212],{"class":202},[193,1374,199],{"class":198},[193,1376,1377],{"class":202}," (QgsRasterLayer, QgsColorRampShader, QgsRasterShader,\n",[193,1379,1380],{"class":98,"line":206},[193,1381,1382],{"class":202},"                       QgsSingleBandPseudoColorRenderer, QgsStyle)\n",[193,1384,1385],{"class":98,"line":220},[193,1386,224],{"emptyLinePlaceholder":223},[193,1388,1389,1392,1394,1397,1400],{"class":98,"line":227},[193,1390,1391],{"class":202},"ndvi_layer ",[193,1393,233],{"class":198},[193,1395,1396],{"class":202}," QgsRasterLayer(out_path, ",[193,1398,1399],{"class":239},"\"NDVI June 2026\"",[193,1401,516],{"class":202},[193,1403,1404,1407,1409,1412,1414,1416,1418,1421,1424,1427],{"class":98,"line":252},[193,1405,1406],{"class":202},"shader_fn ",[193,1408,233],{"class":198},[193,1410,1411],{"class":202}," QgsColorRampShader(",[193,1413,694],{"class":198},[193,1415,819],{"class":246},[193,1417,428],{"class":202},[193,1419,1420],{"class":246},"0.9",[193,1422,1423],{"class":202},", QgsStyle.defaultStyle().colorRamp(",[193,1425,1426],{"class":239},"\"RdYlGn\"",[193,1428,1429],{"class":202},"))\n",[193,1431,1432,1435,1438],{"class":98,"line":269},[193,1433,1434],{"class":202},"shader_fn.classifyColorRamp(",[193,1436,1437],{"class":246},"9",[193,1439,516],{"class":202},[193,1441,1442,1445,1447],{"class":98,"line":285},[193,1443,1444],{"class":202},"shader ",[193,1446,233],{"class":198},[193,1448,1449],{"class":202}," QgsRasterShader()\n",[193,1451,1452],{"class":98,"line":290},[193,1453,1454],{"class":202},"shader.setRasterShaderFunction(shader_fn)\n",[193,1456,1457,1460,1462],{"class":98,"line":305},[193,1458,1459],{"class":202},"ndvi_layer.setRenderer(QgsSingleBandPseudoColorRenderer(ndvi_layer.dataProvider(), ",[193,1461,717],{"class":246},[193,1463,1464],{"class":202},", shader))\n",[193,1466,1467],{"class":98,"line":331},[193,1468,1469],{"class":202},"QgsProject.instance().addMapLayer(ndvi_layer)\n",[14,1471,1472,1474,1475,171],{},[352,1473,354],{}," A fixed range of −0.2 to 0.9 rather than the layer's own minimum and maximum keeps colours comparable between dates — the same NDVI value gets the same colour in June and August. The red–yellow–green ramp is the convention for vegetation indices. For a reusable look, save the style as QML once and apply it to every new date, as in ",[21,1476,1478],{"href":1477},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fapply-color-ramp-to-raster-pyqgis\u002F","applying a colour ramp to a raster",[156,1480,1482],{"id":1481},"process-large-rasters-in-windows","Process large rasters in windows",[14,1484,1485],{},"A national mosaic does not fit in memory. Rasterio's windows let you read, compute and write block by block with the same code.",[183,1487,1489],{"className":185,"code":1488,"language":187,"meta":188,"style":188},"with rasterio.open(path) as src, rasterio.open(out_path, \"w\", **out_profile) as dst:\n    for _, window in src.block_windows(1):\n        r = src.read(4, window=window, masked=True).astype(\"float32\")\n        n = src.read(8, window=window, masked=True).astype(\"float32\")\n        d = n + r\n        v = np.ma.where(d == 0, np.ma.masked, (n - r) \u002F d)\n        dst.write(np.ma.clip(v, -1, 1).filled(-9999.0).astype(\"float32\"), 1, window=window)\n",[190,1490,1491,1514,1532,1565,1596,1611,1638],{"__ignoreMap":188},[193,1492,1493,1495,1497,1499,1502,1504,1506,1508,1510,1512],{"class":98,"line":195},[193,1494,293],{"class":198},[193,1496,296],{"class":202},[193,1498,299],{"class":198},[193,1500,1501],{"class":202}," src, rasterio.open(out_path, ",[193,1503,1256],{"class":239},[193,1505,428],{"class":202},[193,1507,1261],{"class":198},[193,1509,1264],{"class":202},[193,1511,299],{"class":198},[193,1513,1269],{"class":202},[193,1515,1516,1519,1522,1524,1527,1529],{"class":98,"line":206},[193,1517,1518],{"class":198},"    for",[193,1520,1521],{"class":202}," _, window ",[193,1523,901],{"class":198},[193,1525,1526],{"class":202}," src.block_windows(",[193,1528,717],{"class":246},[193,1530,1531],{"class":202},"):\n",[193,1533,1534,1537,1539,1541,1543,1545,1548,1550,1553,1555,1557,1559,1561,1563],{"class":98,"line":220},[193,1535,1536],{"class":202},"        r ",[193,1538,233],{"class":198},[193,1540,422],{"class":202},[193,1542,425],{"class":246},[193,1544,428],{"class":202},[193,1546,1547],{"class":431},"window",[193,1549,233],{"class":198},[193,1551,1552],{"class":202},"window, ",[193,1554,432],{"class":431},[193,1556,233],{"class":198},[193,1558,437],{"class":246},[193,1560,440],{"class":202},[193,1562,443],{"class":239},[193,1564,516],{"class":202},[193,1566,1567,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594],{"class":98,"line":227},[193,1568,1569],{"class":202},"        n ",[193,1571,233],{"class":198},[193,1573,422],{"class":202},[193,1575,58],{"class":246},[193,1577,428],{"class":202},[193,1579,1547],{"class":431},[193,1581,233],{"class":198},[193,1583,1552],{"class":202},[193,1585,432],{"class":431},[193,1587,233],{"class":198},[193,1589,437],{"class":246},[193,1591,440],{"class":202},[193,1593,443],{"class":239},[193,1595,516],{"class":202},[193,1597,1598,1601,1603,1606,1608],{"class":98,"line":252},[193,1599,1600],{"class":202},"        d ",[193,1602,233],{"class":198},[193,1604,1605],{"class":202}," n ",[193,1607,669],{"class":198},[193,1609,1610],{"class":202}," r\n",[193,1612,1613,1616,1618,1621,1623,1625,1628,1630,1633,1635],{"class":98,"line":269},[193,1614,1615],{"class":202},"        v ",[193,1617,233],{"class":198},[193,1619,1620],{"class":202}," np.ma.where(d ",[193,1622,685],{"class":198},[193,1624,688],{"class":246},[193,1626,1627],{"class":202},", np.ma.masked, (n ",[193,1629,694],{"class":198},[193,1631,1632],{"class":202}," r) ",[193,1634,700],{"class":198},[193,1636,1637],{"class":202}," d)\n",[193,1639,1640,1643,1645,1647,1649,1651,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672],{"class":98,"line":285},[193,1641,1642],{"class":202},"        dst.write(np.ma.clip(v, ",[193,1644,694],{"class":198},[193,1646,717],{"class":246},[193,1648,428],{"class":202},[193,1650,717],{"class":246},[193,1652,1653],{"class":202},").filled(",[193,1655,694],{"class":198},[193,1657,1167],{"class":246},[193,1659,440],{"class":202},[193,1661,443],{"class":239},[193,1663,1285],{"class":202},[193,1665,717],{"class":246},[193,1667,428],{"class":202},[193,1669,1547],{"class":431},[193,1671,233],{"class":198},[193,1673,1674],{"class":202},"window)\n",[14,1676,1677,355,1679,1682],{},[352,1678,354],{},[190,1680,1681],{},"block_windows"," iterates the file's internal blocks, so each read is aligned with how the data is stored and costs one disk read. Memory use is bounded by the block size regardless of raster size. The output must use the same grid; with matching block sizes in the output profile, writes are aligned too. For per-pixel operations like this the result is identical to the whole-array version.",[156,1684,1686],{"id":1685},"qgis-version-compatibility","QGIS version compatibility",[14,1688,1689],{},"The rasterio and NumPy code is independent of QGIS version; the QGIS parts work on 3.34 LTR, 3.40 LTR and QGIS 4. Rasterio must be built against a GDAL compatible with the one QGIS uses — mixing them in one process can crash on import. On conda, install both from conda-forge into the same environment; with OSGeo4W, use its packages.",[156,1691,1693],{"id":1692},"troubleshooting","Troubleshooting",[161,1695,1696,1705,1711,1717],{},[164,1697,1698,1704],{},[352,1699,1700,1703],{},[190,1701,1702],{},"ImportError"," or a crash when importing rasterio."," It was built against a different GDAL; install it from the same distribution as QGIS.",[164,1706,1707,1710],{},[352,1708,1709],{},"The output is shifted or stretched."," The transform or size in the profile was changed; copy them unchanged from the input.",[164,1712,1713,1716],{},[352,1714,1715],{},"NoData shows as black."," The NoData value was not set in the profile or the masked array was not filled with it.",[164,1718,1719,1722],{},[352,1720,1721],{},"QGIS cannot overwrite the output."," The file is still open in a rasterio handle or loaded in the project; close or remove it first.",[156,1724,1726],{"id":1725},"conclusion","Conclusion",[14,1728,1729,1730,1732],{},"Get the file path from the layer with ",[190,1731,358],{},", read masked float arrays, compute with NumPy while keeping masks, copy the input profile and change only data type, band count, NoData and compression, write with a description and tags, load and style with a fixed range, and use block windows for rasters that do not fit in memory.",[156,1734,1736],{"id":1735},"frequently-asked-questions","Frequently Asked Questions",[14,1738,1739,1742,1743,1746,1747,1750,1751,1755],{},[352,1740,1741],{},"Can I skip rasterio and use only PyQGIS?","\nYes. ",[190,1744,1745],{},"QgsRasterBlock"," reads arrays and ",[190,1748,1749],{},"QgsRasterFileWriter"," writes them, as in ",[21,1752,1754],{"href":1753},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fwrite-numpy-array-to-raster-pyqgis\u002F","writing a NumPy array to a raster",". Rasterio is more concise for heavy array work.",[14,1757,1758,1761,1762,1765,1766,1769],{},[352,1759,1760],{},"Does rasterio read COGs over HTTP?","\nYes, with ",[190,1763,1764],{},"\u002Fvsicurl\u002F"," or an ",[190,1767,1768],{},"https:\u002F\u002F"," path, using GDAL's range requests.",[14,1771,1772,1775],{},[352,1773,1774],{},"How do I apply a scikit-learn model to every pixel?","\nReshape bands to a 2-D array of pixels by features, predict, and reshape back to the grid.",[14,1777,1778,1781,1782,1785],{},[352,1779,1780],{},"Is xarray an alternative?","\nYes; ",[190,1783,1784],{},"rioxarray"," adds rasterio-backed I\u002FO to xarray and suits time series of rasters.",[156,1787,1789],{"id":1788},"related","Related",[161,1791,1792,1797,1803,1808,1813],{},[164,1793,1794,1796],{},[21,1795,24],{"href":23}," — the guide this recipe belongs to",[164,1798,1799],{},[21,1800,1802],{"href":1801},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fread-raster-pixels-with-qgsrasterblock-pyqgis\u002F","Read Raster Pixels with QgsRasterBlock in PyQGIS",[164,1804,1805],{},[21,1806,1807],{"href":1753},"Write a NumPy Array to a Raster in PyQGIS",[164,1809,1810],{},[21,1811,1812],{"href":783},"Use the Raster Calculator in PyQGIS",[164,1814,1815],{},[21,1816,1817],{"href":1059},"Reclassify Raster Values in PyQGIS",[1819,1820,1821],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":188,"searchDepth":206,"depth":206,"links":1823},[1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836],{"id":158,"depth":206,"text":159},{"id":177,"depth":206,"text":178},{"id":373,"depth":206,"text":374},{"id":532,"depth":206,"text":533},{"id":788,"depth":206,"text":789},{"id":1063,"depth":206,"text":1064},{"id":1357,"depth":206,"text":1358},{"id":1481,"depth":206,"text":1482},{"id":1685,"depth":206,"text":1686},{"id":1692,"depth":206,"text":1693},{"id":1725,"depth":206,"text":1726},{"id":1735,"depth":206,"text":1736},{"id":1788,"depth":206,"text":1789},"Work on QGIS raster layers with rasterio and NumPy — opening a layer's file, reading windows and masked arrays, computing indices and reclassifications, writing results with the correct profile and NoData, and loading them back as styled QGIS layers.","md",{"slug":12,"type":1840,"breadcrumb":1841,"datePublished":1842,"dateModified":1842},"article","Rasterio and NumPy with QGIS Rasters","2026-10-02","\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fuse-rasterio-and-numpy-with-qgis-rasters",{"title":5,"description":1837},"spatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fuse-rasterio-and-numpy-with-qgis-rasters\u002Findex","0FEQukhspJa5WjSF6J3A7-gWzBUjwFXqUaD3oIQW0dQ",1790966264428]