[{"data":1,"prerenderedAt":2067},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fwrite-numpy-array-to-raster-pyqgis":3},{"id":4,"title":5,"body":6,"description":2056,"extension":2057,"meta":2058,"navigation":281,"path":2063,"seo":2064,"stem":2065,"__hash__":2066},"docs\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fwrite-numpy-array-to-raster-pyqgis\u002Findex.md","Write a NumPy Array to a Raster in PyQGIS",{"type":7,"value":8,"toc":2043},"minimark",[9,13,17,31,185,190,212,216,223,608,644,650,766,770,773,923,942,946,949,1061,1292,1319,1323,1326,1740,1749,1753,1756,1866,1883,1887,1907,1911,1949,1953,1959,1963,1975,1985,1991,2005,2009,2039],[10,11,5],"h1",{"id":12},"write-a-numpy-array-to-a-raster-in-pyqgis",[14,15,16],"p",{},"Reading cells into NumPy is half of a custom raster workflow. The other half is getting the result back out as a file that lines up with everything else: the same cell size, the same origin, the same CRS, a sensible data type, and NoData where the calculation had nothing to say. Get the georeferencing half a cell wrong and the output looks fine on its own and shifts by a metre against the input — the kind of error that survives review.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002F","Raster Analysis Workflows in PyQGIS",". It writes arrays through ",[27,28,29],"code",{},"QgsRasterFileWriter"," so no second library is needed in a plugin, matches the output grid to a source layer, handles data types and NoData, writes large results tile by tile, and sets GeoTIFF creation options.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,96,101,105,108,112,117,121,128,132,135,139,149,153,158,163,169,175,181],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 300","img","What a written raster needs besides the values: a grid definition made of extent, width, height and CRS, a data type, a NoData value, and creation options, all supplied to QgsRasterFileWriter before blocks are written","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Values are the easy part",[45,46,47],"desc",{},"A NumPy array of computed values sits on the left. Around it, four other things the file needs: the grid, taken from the source layer as extent, width, height and CRS; the data type, such as Float32; the NoData value, such as minus 9999; and creation options such as deflate compression and tiling. All five go to QgsRasterFileWriter, which creates a provider, receives one or more blocks, and closes to produce a GeoTIFF.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","300","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"wrNeedArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Five things the output file needs",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"24","52","210","44","#eff3ff","#2563eb","stroke-width:1.8",[73,91,95],{"x":92,"y":93,"style":94,"fill":71,"textAnchor":79},"129","79","text-anchor:middle;font-size:10.5px;font-family:sans-serif","values: NumPy array",[49,97],{"x":83,"y":98,"width":85,"height":86,"rx":63,"fill":99,"stroke":100,"style":89},"106","#eef7f4","#0f766e",[73,102,104],{"x":92,"y":103,"style":94,"fill":71,"textAnchor":79},"133","grid: extent, size, CRS",[49,106],{"x":83,"y":107,"width":85,"height":86,"rx":63,"fill":99,"stroke":100,"style":89},"160",[73,109,111],{"x":92,"y":110,"style":94,"fill":71,"textAnchor":79},"187","data type: Float32",[49,113],{"x":83,"y":114,"width":85,"height":86,"rx":63,"fill":115,"stroke":116,"style":89},"214","#fdf2e2","#b45309",[73,118,120],{"x":92,"y":119,"style":94,"fill":71,"textAnchor":79},"241","NoData: -9999",[122,123],"line",{"x1":124,"y1":125,"x2":53,"y2":126,"stroke":71,"style":127},"234","74","140","stroke-width:1.6;marker-end:url(#wrNeedArrow)",[122,129],{"x1":124,"y1":130,"x2":53,"y2":131,"stroke":71,"style":127},"128","150",[122,133],{"x1":124,"y1":134,"x2":53,"y2":107,"stroke":71,"style":127},"182",[122,136],{"x1":124,"y1":137,"x2":53,"y2":138,"stroke":71,"style":127},"236","170",[49,140],{"x":141,"y":142,"width":143,"height":144,"rx":145,"fill":146,"stroke":147,"style":148},"308","108","196","96","10","#fffdf7","#59645f","stroke-width:2.5",[73,150,29],{"x":151,"y":126,"style":152,"fill":78,"textAnchor":79},"406","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif",[73,154,157],{"x":151,"y":155,"style":156,"fill":71,"textAnchor":79},"164","text-anchor:middle;font-size:10px;font-family:sans-serif","+ creation options",[73,159,162],{"x":151,"y":160,"style":161,"fill":71,"textAnchor":79},"184","text-anchor:middle;font-size:9.5px;font-family:monospace","COMPRESS=DEFLATE",[122,164],{"x1":165,"y1":166,"x2":167,"y2":166,"stroke":71,"style":168},"504","156","552","stroke-width:1.8;marker-end:url(#wrNeedArrow)",[49,170],{"x":171,"y":142,"width":172,"height":144,"rx":145,"fill":173,"stroke":174,"style":148},"560","176","#e8efe6","#15803d",[73,176,180],{"x":177,"y":178,"style":152,"fill":179,"textAnchor":79},"648","144","#166534","result.tif",[73,182,184],{"x":177,"y":183,"style":156,"fill":71,"textAnchor":79},"168","lines up with source",[186,187,189],"h2",{"id":188},"prerequisites","Prerequisites",[191,192,193,201,209],"ul",{},[194,195,196,200],"li",{},[197,198,199],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series.",[194,202,203,204,208],{},"A source raster whose grid the output should match, and a NumPy array computed from it — for example with the block reading shown in ",[21,205,207],{"href":206},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fread-raster-pixels-with-qgsrasterblock-pyqgis\u002F","reading raster pixels with QgsRasterBlock",".",[194,210,211],{},"Write access to the output folder.",[186,213,215],{"id":214},"write-a-single-band-raster-that-matches-its-source","Write a single-band raster that matches its source",[14,217,218,219,222],{},"The writer creates an empty raster with a given data type, size, extent and CRS and returns a data provider for it. You then put the array into a ",[27,220,221],{},"QgsRasterBlock"," and write the block.",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-python shiki shiki-themes github-dark","import numpy as np\nfrom qgis.core import (\n    Qgis, QgsProject, QgsRasterFileWriter, QgsRasterBlock,\n)\n\nsource = QgsProject.instance().mapLayersByName(\"dem_2m\")[0]\nwidth, height = source.width(), source.height()\n\nelevation = read_whole_band(source)            # float64 array with NaN for NoData\nslope_proxy = np.hypot(*np.gradient(elevation, 2.0))\n\nNODATA = -9999.0\nout = np.where(np.isnan(slope_proxy), NODATA, slope_proxy).astype(np.float32)\n\nwriter = QgsRasterFileWriter(\"\u002Fdata\u002Fwork\u002Fgradient.tif\")\nwriter.setOutputFormat(\"GTiff\")\nprovider = writer.createOneBandRaster(\n    Qgis.DataType.Float32, width, height, source.extent(), source.crs())\nif provider is None or not provider.isValid():\n    raise RuntimeError(f\"could not create output: {writer.error()}\")\n\nprovider.setNoDataValue(1, NODATA)\nprovider.setEditable(True)\nblock = QgsRasterBlock(Qgis.DataType.Float32, width, height)\nblock.setData(out.tobytes())\nif not provider.writeBlock(block, 1, 0, 0):\n    raise RuntimeError(\"writeBlock failed\")\nprovider.setEditable(False)\ndel provider\n","python","",[27,231,232,250,264,270,276,283,308,319,324,339,362,367,382,398,403,419,430,441,447,471,503,508,524,535,546,552,575,589,599],{"__ignoreMap":229},[233,234,236,240,244,247],"span",{"class":122,"line":235},1,[233,237,239],{"class":238},"snl16","import",[233,241,243],{"class":242},"s95oV"," numpy ",[233,245,246],{"class":238},"as",[233,248,249],{"class":242}," np\n",[233,251,253,256,259,261],{"class":122,"line":252},2,[233,254,255],{"class":238},"from",[233,257,258],{"class":242}," qgis.core ",[233,260,239],{"class":238},[233,262,263],{"class":242}," (\n",[233,265,267],{"class":122,"line":266},3,[233,268,269],{"class":242},"    Qgis, QgsProject, QgsRasterFileWriter, QgsRasterBlock,\n",[233,271,273],{"class":122,"line":272},4,[233,274,275],{"class":242},")\n",[233,277,279],{"class":122,"line":278},5,[233,280,282],{"emptyLinePlaceholder":281},true,"\n",[233,284,286,289,292,295,299,302,305],{"class":122,"line":285},6,[233,287,288],{"class":242},"source ",[233,290,291],{"class":238},"=",[233,293,294],{"class":242}," QgsProject.instance().mapLayersByName(",[233,296,298],{"class":297},"sU2Wk","\"dem_2m\"",[233,300,301],{"class":242},")[",[233,303,51],{"class":304},"sDLfK",[233,306,307],{"class":242},"]\n",[233,309,311,314,316],{"class":122,"line":310},7,[233,312,313],{"class":242},"width, height ",[233,315,291],{"class":238},[233,317,318],{"class":242}," source.width(), source.height()\n",[233,320,322],{"class":122,"line":321},8,[233,323,282],{"emptyLinePlaceholder":281},[233,325,327,330,332,335],{"class":122,"line":326},9,[233,328,329],{"class":242},"elevation ",[233,331,291],{"class":238},[233,333,334],{"class":242}," read_whole_band(source)            ",[233,336,338],{"class":337},"sjoCn","# float64 array with NaN for NoData\n",[233,340,342,345,347,350,353,356,359],{"class":122,"line":341},10,[233,343,344],{"class":242},"slope_proxy ",[233,346,291],{"class":238},[233,348,349],{"class":242}," np.hypot(",[233,351,352],{"class":238},"*",[233,354,355],{"class":242},"np.gradient(elevation, ",[233,357,358],{"class":304},"2.0",[233,360,361],{"class":242},"))\n",[233,363,365],{"class":122,"line":364},11,[233,366,282],{"emptyLinePlaceholder":281},[233,368,370,373,376,379],{"class":122,"line":369},12,[233,371,372],{"class":304},"NODATA",[233,374,375],{"class":238}," =",[233,377,378],{"class":238}," -",[233,380,381],{"class":304},"9999.0\n",[233,383,385,388,390,393,395],{"class":122,"line":384},13,[233,386,387],{"class":242},"out ",[233,389,291],{"class":238},[233,391,392],{"class":242}," np.where(np.isnan(slope_proxy), ",[233,394,372],{"class":304},[233,396,397],{"class":242},", slope_proxy).astype(np.float32)\n",[233,399,401],{"class":122,"line":400},14,[233,402,282],{"emptyLinePlaceholder":281},[233,404,406,409,411,414,417],{"class":122,"line":405},15,[233,407,408],{"class":242},"writer ",[233,410,291],{"class":238},[233,412,413],{"class":242}," QgsRasterFileWriter(",[233,415,416],{"class":297},"\"\u002Fdata\u002Fwork\u002Fgradient.tif\"",[233,418,275],{"class":242},[233,420,422,425,428],{"class":122,"line":421},16,[233,423,424],{"class":242},"writer.setOutputFormat(",[233,426,427],{"class":297},"\"GTiff\"",[233,429,275],{"class":242},[233,431,433,436,438],{"class":122,"line":432},17,[233,434,435],{"class":242},"provider ",[233,437,291],{"class":238},[233,439,440],{"class":242}," writer.createOneBandRaster(\n",[233,442,444],{"class":122,"line":443},18,[233,445,446],{"class":242},"    Qgis.DataType.Float32, width, height, source.extent(), source.crs())\n",[233,448,450,453,456,459,462,465,468],{"class":122,"line":449},19,[233,451,452],{"class":238},"if",[233,454,455],{"class":242}," provider ",[233,457,458],{"class":238},"is",[233,460,461],{"class":304}," None",[233,463,464],{"class":238}," or",[233,466,467],{"class":238}," not",[233,469,470],{"class":242}," provider.isValid():\n",[233,472,474,477,480,483,486,489,492,495,498,501],{"class":122,"line":473},20,[233,475,476],{"class":238},"    raise",[233,478,479],{"class":304}," RuntimeError",[233,481,482],{"class":242},"(",[233,484,485],{"class":238},"f",[233,487,488],{"class":297},"\"could not create output: ",[233,490,491],{"class":304},"{",[233,493,494],{"class":242},"writer.error()",[233,496,497],{"class":304},"}",[233,499,500],{"class":297},"\"",[233,502,275],{"class":242},[233,504,506],{"class":122,"line":505},21,[233,507,282],{"emptyLinePlaceholder":281},[233,509,511,514,517,520,522],{"class":122,"line":510},22,[233,512,513],{"class":242},"provider.setNoDataValue(",[233,515,516],{"class":304},"1",[233,518,519],{"class":242},", ",[233,521,372],{"class":304},[233,523,275],{"class":242},[233,525,527,530,533],{"class":122,"line":526},23,[233,528,529],{"class":242},"provider.setEditable(",[233,531,532],{"class":304},"True",[233,534,275],{"class":242},[233,536,538,541,543],{"class":122,"line":537},24,[233,539,540],{"class":242},"block ",[233,542,291],{"class":238},[233,544,545],{"class":242}," QgsRasterBlock(Qgis.DataType.Float32, width, height)\n",[233,547,549],{"class":122,"line":548},25,[233,550,551],{"class":242},"block.setData(out.tobytes())\n",[233,553,555,557,559,562,564,566,568,570,572],{"class":122,"line":554},26,[233,556,452],{"class":238},[233,558,467],{"class":238},[233,560,561],{"class":242}," provider.writeBlock(block, ",[233,563,516],{"class":304},[233,565,519],{"class":242},[233,567,51],{"class":304},[233,569,519],{"class":242},[233,571,51],{"class":304},[233,573,574],{"class":242},"):\n",[233,576,578,580,582,584,587],{"class":122,"line":577},27,[233,579,476],{"class":238},[233,581,479],{"class":304},[233,583,482],{"class":242},[233,585,586],{"class":297},"\"writeBlock failed\"",[233,588,275],{"class":242},[233,590,592,594,597],{"class":122,"line":591},28,[233,593,529],{"class":242},[233,595,596],{"class":304},"False",[233,598,275],{"class":242},[233,600,602,605],{"class":122,"line":601},29,[233,603,604],{"class":238},"del",[233,606,607],{"class":242}," provider\n",[14,609,610,613,614,519,617,519,620,623,624,627,628,631,632,635,636,639,640,643],{},[197,611,612],{},"Breakdown:"," Passing the source layer's ",[27,615,616],{},"extent()",[27,618,619],{},"width()",[27,621,622],{},"height()"," and ",[27,625,626],{},"crs()"," reproduces its grid exactly, which is the single most important step — the cell size is implied by extent divided by size, so the same four values guarantee alignment. The array is cast to ",[27,629,630],{},"float32"," to match the declared data type; ",[27,633,634],{},"tobytes()"," then produces exactly the byte layout the block expects, row by row from the top. NoData must be replaced with a real number before writing because a ",[27,637,638],{},"NaN"," in an integer file is impossible and in a float file is often mishandled by other software. ",[27,641,642],{},"setEditable(False)"," flushes and closes the dataset, and deleting the provider releases the file handle so the output can be opened straight away.",[14,645,646,649],{},[27,647,648],{},"read_whole_band"," stands for whichever reading approach fits the raster's size — a single block for small rasters, the tiled reader for large ones.",[14,651,652],{},[34,653,656,659,662,665,669,675,681,685,689,693,700,705,708,711,715,719,722,725,728,731,734,737,739,743,746,749,752,756,759,762],{"viewBox":654,"role":37,"ariaLabel":655,"xmlns":39},"0 0 760 262","Choosing an output data type by value range and precision: Byte for 0 to 255 classes, Int16 for signed small integers, UInt16 for scaled reflectance, Float32 for continuous measurements, Float64 only when needed, with file sizes for a 10,000 by 10,000 raster",[41,657,658],{},"Pick the smallest type that holds the answer",[45,660,661],{},"A table of five data types with value range, typical use and uncompressed size for a ten thousand by ten thousand cell raster. Byte, 0 to 255, class maps, 95 megabytes. Int16, minus 32,768 to 32,767, elevations in decimetres, 191 megabytes. UInt16, 0 to 65,535, scaled reflectance, 191 megabytes. Float32, about seven significant digits, continuous values, 381 megabytes. Float64, fifteen digits, rarely needed, 763 megabytes.",[49,663],{"x":51,"y":51,"width":52,"height":664,"fill":54},"262",[73,666,668],{"x":75,"y":667,"style":77,"fill":78,"textAnchor":79},"26","Data type sets range, precision and size",[49,670],{"x":83,"y":671,"width":672,"height":673,"rx":145,"fill":146,"stroke":147,"style":674},"42","712","200","stroke-width:2",[73,676,680],{"x":677,"y":678,"style":679,"fill":78,"textAnchor":79},"110","68","text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","type",[73,682,684],{"x":683,"y":678,"style":679,"fill":78,"textAnchor":79},"280","range",[73,686,688],{"x":687,"y":678,"style":679,"fill":78,"textAnchor":79},"490","typical use",[73,690,692],{"x":691,"y":678,"style":679,"fill":78,"textAnchor":79},"660","10k × 10k",[122,694],{"x1":695,"y1":696,"x2":697,"y2":696,"stroke":698,"style":699},"40","78","720","#d9d3c4","stroke-width:1.5",[73,701,704],{"x":677,"y":702,"style":703,"fill":71,"textAnchor":79},"104","text-anchor:middle;font-size:10px;font-family:monospace","Byte",[73,706,707],{"x":683,"y":702,"style":156,"fill":71,"textAnchor":79},"0 – 255",[73,709,710],{"x":687,"y":702,"style":156,"fill":71,"textAnchor":79},"classes, masks",[73,712,714],{"x":691,"y":702,"style":713,"fill":179,"textAnchor":79},"text-anchor:middle;font-size:10px;font-weight:bold;font-family:sans-serif","95 MB",[73,716,718],{"x":677,"y":717,"style":703,"fill":71,"textAnchor":79},"132","Int16",[73,720,721],{"x":683,"y":717,"style":156,"fill":71,"textAnchor":79},"±32,767",[73,723,724],{"x":687,"y":717,"style":156,"fill":71,"textAnchor":79},"heights in decimetres",[73,726,727],{"x":691,"y":717,"style":713,"fill":179,"textAnchor":79},"191 MB",[73,729,730],{"x":677,"y":107,"style":703,"fill":71,"textAnchor":79},"UInt16",[73,732,733],{"x":683,"y":107,"style":156,"fill":71,"textAnchor":79},"0 – 65,535",[73,735,736],{"x":687,"y":107,"style":156,"fill":71,"textAnchor":79},"scaled reflectance",[73,738,727],{"x":691,"y":107,"style":713,"fill":179,"textAnchor":79},[73,740,742],{"x":677,"y":741,"style":703,"fill":71,"textAnchor":79},"188","Float32",[73,744,745],{"x":683,"y":741,"style":156,"fill":71,"textAnchor":79},"~7 significant digits",[73,747,748],{"x":687,"y":741,"style":156,"fill":71,"textAnchor":79},"continuous measurements",[73,750,751],{"x":691,"y":741,"style":713,"fill":116,"textAnchor":79},"381 MB",[73,753,755],{"x":677,"y":754,"style":703,"fill":71,"textAnchor":79},"216","Float64",[73,757,758],{"x":683,"y":754,"style":156,"fill":71,"textAnchor":79},"~15 significant digits",[73,760,761],{"x":687,"y":754,"style":156,"fill":71,"textAnchor":79},"rarely justified",[73,763,765],{"x":691,"y":754,"style":713,"fill":764,"textAnchor":79},"#b91c1c","763 MB",[186,767,769],{"id":768},"data-types-and-nodata-choices","Data types and NoData choices",[14,771,772],{},"The output type should be the smallest that holds every value the calculation can produce, with room for a NoData value outside that range. Too small and values wrap or clip silently; too large and files are two to four times bigger than they need to be.",[224,774,776],{"className":226,"code":775,"language":228,"meta":229,"style":229},"classes = np.digitize(slope_proxy, bins=[2, 5, 15, 30]).astype(np.uint8)\nclasses[np.isnan(slope_proxy)] = 255\n\nwriter = QgsRasterFileWriter(\"\u002Fdata\u002Fwork\u002Fslope_classes.tif\")\nprovider = writer.createOneBandRaster(\n    Qgis.DataType.Byte, width, height, source.extent(), source.crs())\nprovider.setNoDataValue(1, 255)\nprovider.setEditable(True)\nblock = QgsRasterBlock(Qgis.DataType.Byte, width, height)\nblock.setData(classes.tobytes())\nprovider.writeBlock(block, 1, 0, 0)\nprovider.setEditable(False)\ndel provider\n",[27,777,778,817,827,831,844,852,857,870,878,887,892,909,917],{"__ignoreMap":229},[233,779,780,783,785,788,792,794,797,800,802,804,806,809,811,814],{"class":122,"line":235},[233,781,782],{"class":242},"classes ",[233,784,291],{"class":238},[233,786,787],{"class":242}," np.digitize(slope_proxy, ",[233,789,791],{"class":790},"s9osk","bins",[233,793,291],{"class":238},[233,795,796],{"class":242},"[",[233,798,799],{"class":304},"2",[233,801,519],{"class":242},[233,803,64],{"class":304},[233,805,519],{"class":242},[233,807,808],{"class":304},"15",[233,810,519],{"class":242},[233,812,813],{"class":304},"30",[233,815,816],{"class":242},"]).astype(np.uint8)\n",[233,818,819,822,824],{"class":122,"line":252},[233,820,821],{"class":242},"classes[np.isnan(slope_proxy)] ",[233,823,291],{"class":238},[233,825,826],{"class":304}," 255\n",[233,828,829],{"class":122,"line":266},[233,830,282],{"emptyLinePlaceholder":281},[233,832,833,835,837,839,842],{"class":122,"line":272},[233,834,408],{"class":242},[233,836,291],{"class":238},[233,838,413],{"class":242},[233,840,841],{"class":297},"\"\u002Fdata\u002Fwork\u002Fslope_classes.tif\"",[233,843,275],{"class":242},[233,845,846,848,850],{"class":122,"line":278},[233,847,435],{"class":242},[233,849,291],{"class":238},[233,851,440],{"class":242},[233,853,854],{"class":122,"line":285},[233,855,856],{"class":242},"    Qgis.DataType.Byte, width, height, source.extent(), source.crs())\n",[233,858,859,861,863,865,868],{"class":122,"line":310},[233,860,513],{"class":242},[233,862,516],{"class":304},[233,864,519],{"class":242},[233,866,867],{"class":304},"255",[233,869,275],{"class":242},[233,871,872,874,876],{"class":122,"line":321},[233,873,529],{"class":242},[233,875,532],{"class":304},[233,877,275],{"class":242},[233,879,880,882,884],{"class":122,"line":326},[233,881,540],{"class":242},[233,883,291],{"class":238},[233,885,886],{"class":242}," QgsRasterBlock(Qgis.DataType.Byte, width, height)\n",[233,888,889],{"class":122,"line":341},[233,890,891],{"class":242},"block.setData(classes.tobytes())\n",[233,893,894,897,899,901,903,905,907],{"class":122,"line":364},[233,895,896],{"class":242},"provider.writeBlock(block, ",[233,898,516],{"class":304},[233,900,519],{"class":242},[233,902,51],{"class":304},[233,904,519],{"class":242},[233,906,51],{"class":304},[233,908,275],{"class":242},[233,910,911,913,915],{"class":122,"line":369},[233,912,529],{"class":242},[233,914,596],{"class":304},[233,916,275],{"class":242},[233,918,919,921],{"class":122,"line":384},[233,920,604],{"class":238},[233,922,607],{"class":242},[14,924,925,927,928,930,931,934,935,938,939,941],{},[197,926,612],{}," A classified output with five classes needs one byte per cell, a quarter of ",[27,929,742],{},", and it compresses far better. Using 255 as NoData keeps 0 available as a real class. ",[27,932,933],{},"np.digitize"," returns int64 by default, so the explicit ",[27,936,937],{},"astype(np.uint8)"," is what makes the bytes match the declared type — writing int64 bytes into a Byte block produces a striped, eight-times-too-wide mess. For continuous results ",[27,940,742],{}," is almost always enough: its seven significant digits exceed the precision of any elevation or reflectance measurement.",[186,943,945],{"id":944},"write-large-outputs-in-tiles","Write large outputs in tiles",[14,947,948],{},"When the result is too large to hold as one array, write it the way you read it: tile by tile. The provider accepts blocks at any row and column offset, so each tile is computed, written and discarded.",[14,950,951],{},[34,952,955,958,961,963,970,973,977,982,985,991,994,998,1002,1006,1009,1013,1016,1020,1024,1058],{"viewBox":953,"role":37,"ariaLabel":954,"xmlns":39},"0 0 760 236","A tiled write loop: read a source tile, compute, write the result block at the same row and column offset, move to the next tile, with memory holding one tile at a time",[41,956,957],{},"Read, compute, write, repeat",[45,959,960],{},"A cycle of three steps repeated for every tile: read a block from the source at row0 and col0, compute the result array for that tile, and write the result block to the output provider at the same row0 and col0. A grid on the right shows tiles already written in green, the current tile in orange, and remaining tiles in grey.",[49,962],{"x":51,"y":51,"width":52,"height":137,"fill":54},[56,964,965],{},[59,966,968],{"id":967,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"wrTileArrow",[68,969],{"d":70,"fill":71},[73,971,972],{"x":75,"y":667,"style":77,"fill":78,"textAnchor":79},"One tile in memory, however big the output",[49,974],{"x":83,"y":975,"width":976,"height":975,"rx":145,"fill":87,"stroke":88,"style":674},"60","130",[73,978,981],{"x":979,"y":980,"style":679,"fill":88,"textAnchor":79},"89","86","read tile",[73,983,984],{"x":979,"y":98,"style":161,"fill":71,"textAnchor":79},"row0, col0",[122,986],{"x1":987,"y1":988,"x2":989,"y2":988,"stroke":71,"style":990},"154","90","186","stroke-width:1.8;marker-end:url(#wrTileArrow)",[49,992],{"x":993,"y":975,"width":976,"height":975,"rx":145,"fill":99,"stroke":100,"style":674},"194",[73,995,997],{"x":996,"y":980,"style":679,"fill":100,"textAnchor":79},"259","compute",[73,999,1001],{"x":996,"y":98,"style":1000,"fill":71,"textAnchor":79},"text-anchor:middle;font-size:9.5px;font-family:sans-serif","NumPy on the tile",[122,1003],{"x1":1004,"y1":988,"x2":1005,"y2":988,"stroke":71,"style":990},"324","356",[49,1007],{"x":1008,"y":975,"width":976,"height":975,"rx":145,"fill":173,"stroke":174,"style":674},"364",[73,1010,1012],{"x":1011,"y":980,"style":679,"fill":179,"textAnchor":79},"429","writeBlock",[73,1014,1015],{"x":1011,"y":98,"style":161,"fill":71,"textAnchor":79},"same row0, col0",[68,1017],{"d":1018,"fill":1019,"stroke":71,"style":990},"M429 120 L429 160 L89 160 L89 128","none",[73,1021,1023],{"x":996,"y":1022,"style":156,"fill":147,"textAnchor":79},"180","next tile",[1025,1026,1027,1032,1035,1038,1041,1044,1046,1048,1050,1052,1054,1056],"g",{"stroke":146,"style":674},[49,1028],{"x":1029,"y":1030,"width":1031,"height":1031,"fill":174},"540","50","45",[49,1033],{"x":1034,"y":1030,"width":1031,"height":1031,"fill":174},"585",[49,1036],{"x":1037,"y":1030,"width":1031,"height":1031,"fill":174},"630",[49,1039],{"x":1040,"y":1030,"width":1031,"height":1031,"fill":174},"675",[49,1042],{"x":1029,"y":1043,"width":1031,"height":1031,"fill":174},"95",[49,1045],{"x":1034,"y":1043,"width":1031,"height":1031,"fill":116},[49,1047],{"x":1037,"y":1043,"width":1031,"height":1031,"fill":698},[49,1049],{"x":1040,"y":1043,"width":1031,"height":1031,"fill":698},[49,1051],{"x":1029,"y":126,"width":1031,"height":1031,"fill":698},[49,1053],{"x":1034,"y":126,"width":1031,"height":1031,"fill":698},[49,1055],{"x":1037,"y":126,"width":1031,"height":1031,"fill":698},[49,1057],{"x":1040,"y":126,"width":1031,"height":1031,"fill":698},[73,1059,1060],{"x":1037,"y":85,"style":156,"fill":71,"textAnchor":79},"written · current · to do",[224,1062,1064],{"className":226,"code":1063,"language":228,"meta":229,"style":229},"TILE = 2048\nwriter = QgsRasterFileWriter(\"\u002Fdata\u002Fwork\u002Fgradient_national.tif\")\nwriter.setCreateOptions([\"COMPRESS=DEFLATE\", \"PREDICTOR=3\", \"TILED=YES\",\n                         \"BLOCKXSIZE=512\", \"BLOCKYSIZE=512\", \"BIGTIFF=IF_SAFER\"])\nout_provider = writer.createOneBandRaster(\n    Qgis.DataType.Float32, source.width(), source.height(),\n    source.extent(), source.crs())\nout_provider.setNoDataValue(1, NODATA)\nout_provider.setEditable(True)\n\nfor row0, col0, src_block in iter_tiles(source, tile=TILE):\n    arr = block_to_array(src_block)\n    result = np.hypot(*np.gradient(arr, 2.0))\n    result = np.where(np.isnan(result), NODATA, result).astype(np.float32)\n    h, w = result.shape\n    blk = QgsRasterBlock(Qgis.DataType.Float32, w, h)\n    blk.setData(result.tobytes())\n    out_provider.writeBlock(blk, 1, col0, row0)\n\nout_provider.setEditable(False)\ndel out_provider\n",[27,1065,1066,1076,1089,1110,1128,1137,1142,1147,1160,1169,1173,1196,1206,1224,1238,1248,1258,1263,1273,1277,1285],{"__ignoreMap":229},[233,1067,1068,1071,1073],{"class":122,"line":235},[233,1069,1070],{"class":304},"TILE",[233,1072,375],{"class":238},[233,1074,1075],{"class":304}," 2048\n",[233,1077,1078,1080,1082,1084,1087],{"class":122,"line":252},[233,1079,408],{"class":242},[233,1081,291],{"class":238},[233,1083,413],{"class":242},[233,1085,1086],{"class":297},"\"\u002Fdata\u002Fwork\u002Fgradient_national.tif\"",[233,1088,275],{"class":242},[233,1090,1091,1094,1097,1099,1102,1104,1107],{"class":122,"line":266},[233,1092,1093],{"class":242},"writer.setCreateOptions([",[233,1095,1096],{"class":297},"\"COMPRESS=DEFLATE\"",[233,1098,519],{"class":242},[233,1100,1101],{"class":297},"\"PREDICTOR=3\"",[233,1103,519],{"class":242},[233,1105,1106],{"class":297},"\"TILED=YES\"",[233,1108,1109],{"class":242},",\n",[233,1111,1112,1115,1117,1120,1122,1125],{"class":122,"line":272},[233,1113,1114],{"class":297},"                         \"BLOCKXSIZE=512\"",[233,1116,519],{"class":242},[233,1118,1119],{"class":297},"\"BLOCKYSIZE=512\"",[233,1121,519],{"class":242},[233,1123,1124],{"class":297},"\"BIGTIFF=IF_SAFER\"",[233,1126,1127],{"class":242},"])\n",[233,1129,1130,1133,1135],{"class":122,"line":278},[233,1131,1132],{"class":242},"out_provider ",[233,1134,291],{"class":238},[233,1136,440],{"class":242},[233,1138,1139],{"class":122,"line":285},[233,1140,1141],{"class":242},"    Qgis.DataType.Float32, source.width(), source.height(),\n",[233,1143,1144],{"class":122,"line":310},[233,1145,1146],{"class":242},"    source.extent(), source.crs())\n",[233,1148,1149,1152,1154,1156,1158],{"class":122,"line":321},[233,1150,1151],{"class":242},"out_provider.setNoDataValue(",[233,1153,516],{"class":304},[233,1155,519],{"class":242},[233,1157,372],{"class":304},[233,1159,275],{"class":242},[233,1161,1162,1165,1167],{"class":122,"line":326},[233,1163,1164],{"class":242},"out_provider.setEditable(",[233,1166,532],{"class":304},[233,1168,275],{"class":242},[233,1170,1171],{"class":122,"line":341},[233,1172,282],{"emptyLinePlaceholder":281},[233,1174,1175,1178,1181,1184,1187,1190,1192,1194],{"class":122,"line":364},[233,1176,1177],{"class":238},"for",[233,1179,1180],{"class":242}," row0, col0, src_block ",[233,1182,1183],{"class":238},"in",[233,1185,1186],{"class":242}," iter_tiles(source, ",[233,1188,1189],{"class":790},"tile",[233,1191,291],{"class":238},[233,1193,1070],{"class":304},[233,1195,574],{"class":242},[233,1197,1198,1201,1203],{"class":122,"line":369},[233,1199,1200],{"class":242},"    arr ",[233,1202,291],{"class":238},[233,1204,1205],{"class":242}," block_to_array(src_block)\n",[233,1207,1208,1211,1213,1215,1217,1220,1222],{"class":122,"line":384},[233,1209,1210],{"class":242},"    result ",[233,1212,291],{"class":238},[233,1214,349],{"class":242},[233,1216,352],{"class":238},[233,1218,1219],{"class":242},"np.gradient(arr, ",[233,1221,358],{"class":304},[233,1223,361],{"class":242},[233,1225,1226,1228,1230,1233,1235],{"class":122,"line":400},[233,1227,1210],{"class":242},[233,1229,291],{"class":238},[233,1231,1232],{"class":242}," np.where(np.isnan(result), ",[233,1234,372],{"class":304},[233,1236,1237],{"class":242},", result).astype(np.float32)\n",[233,1239,1240,1243,1245],{"class":122,"line":405},[233,1241,1242],{"class":242},"    h, w ",[233,1244,291],{"class":238},[233,1246,1247],{"class":242}," result.shape\n",[233,1249,1250,1253,1255],{"class":122,"line":421},[233,1251,1252],{"class":242},"    blk ",[233,1254,291],{"class":238},[233,1256,1257],{"class":242}," QgsRasterBlock(Qgis.DataType.Float32, w, h)\n",[233,1259,1260],{"class":122,"line":432},[233,1261,1262],{"class":242},"    blk.setData(result.tobytes())\n",[233,1264,1265,1268,1270],{"class":122,"line":443},[233,1266,1267],{"class":242},"    out_provider.writeBlock(blk, ",[233,1269,516],{"class":304},[233,1271,1272],{"class":242},", col0, row0)\n",[233,1274,1275],{"class":122,"line":449},[233,1276,282],{"emptyLinePlaceholder":281},[233,1278,1279,1281,1283],{"class":122,"line":473},[233,1280,1164],{"class":242},[233,1282,596],{"class":304},[233,1284,275],{"class":242},[233,1286,1287,1289],{"class":122,"line":505},[233,1288,604],{"class":238},[233,1290,1291],{"class":242}," out_provider\n",[14,1293,1294,1296,1297,1300,1301,1303,1304,623,1307,1310,1311,1314,1315,1318],{},[197,1295,612],{}," ",[27,1298,1299],{},"writeBlock(block, band, xOffset, yOffset)"," takes the column offset before the row offset — the reverse of the ",[27,1302,984],{}," order the reader yields, and an easy swap to make. ",[27,1305,1306],{},"iter_tiles",[27,1308,1309],{},"block_to_array"," are the helpers from the reading guide. The creation options produce an internally tiled, deflate-compressed GeoTIFF; ",[27,1312,1313],{},"PREDICTOR=3"," is the floating-point predictor that makes continuous surfaces compress well, and ",[27,1316,1317],{},"BIGTIFF=IF_SAFER"," avoids the 4 GB classic TIFF limit on large outputs. One caveat is specific to this example: a gradient computed per tile has edge effects at tile boundaries, because each tile cannot see its neighbours' cells. Read tiles with a one-cell overlap and trim the result when a calculation depends on neighbours.",[186,1320,1322],{"id":1321},"verify-that-the-output-lines-up","Verify that the output lines up",[14,1324,1325],{},"A written raster that opens and looks right can still be misaligned by a fraction of a cell, and that is invisible at normal zoom. Three checks catch it in a second, and they are worth running at the end of every script that writes rasters — especially one that other people's analysis will depend on.",[224,1327,1329],{"className":226,"code":1328,"language":228,"meta":229,"style":229},"from qgis.core import QgsRasterLayer\n\ndef assert_same_grid(a, b, tolerance=1e-6):\n    problems = []\n    if a.crs() != b.crs():\n        problems.append(f\"CRS {a.crs().authid()} != {b.crs().authid()}\")\n    if (a.width(), a.height()) != (b.width(), b.height()):\n        problems.append(f\"size {a.width()}x{a.height()} != {b.width()}x{b.height()}\")\n    for name in (\"xMinimum\", \"yMinimum\", \"xMaximum\", \"yMaximum\"):\n        va, vb = getattr(a.extent(), name)(), getattr(b.extent(), name)()\n        if abs(va - vb) > tolerance:\n            problems.append(f\"{name} {va} != {vb}\")\n    if problems:\n        raise AssertionError(\"; \".join(problems))\n\ncheck = QgsRasterLayer(\"\u002Fdata\u002Fwork\u002Fgradient.tif\", \"check\")\nassert_same_grid(source, check)\n\nprobe = source.extent().center()\nsrc_val, _ = source.dataProvider().sample(probe, 1)\nout_val, _ = check.dataProvider().sample(probe, 1)\nprint(f\"centre cell: source {src_val:.2f}, output {out_val:.4f}\")\nprint(\"output NoData declared:\", check.dataProvider().sourceHasNoDataValue(1))\n",[27,1330,1331,1342,1346,1365,1375,1389,1420,1432,1480,1513,1532,1555,1592,1599,1615,1619,1638,1643,1647,1657,1671,1685,1724],{"__ignoreMap":229},[233,1332,1333,1335,1337,1339],{"class":122,"line":235},[233,1334,255],{"class":238},[233,1336,258],{"class":242},[233,1338,239],{"class":238},[233,1340,1341],{"class":242}," QgsRasterLayer\n",[233,1343,1344],{"class":122,"line":252},[233,1345,282],{"emptyLinePlaceholder":281},[233,1347,1348,1351,1355,1358,1360,1363],{"class":122,"line":266},[233,1349,1350],{"class":238},"def",[233,1352,1354],{"class":1353},"svObZ"," assert_same_grid",[233,1356,1357],{"class":242},"(a, b, tolerance",[233,1359,291],{"class":238},[233,1361,1362],{"class":304},"1e-6",[233,1364,574],{"class":242},[233,1366,1367,1370,1372],{"class":122,"line":272},[233,1368,1369],{"class":242},"    problems ",[233,1371,291],{"class":238},[233,1373,1374],{"class":242}," []\n",[233,1376,1377,1380,1383,1386],{"class":122,"line":278},[233,1378,1379],{"class":238},"    if",[233,1381,1382],{"class":242}," a.crs() ",[233,1384,1385],{"class":238},"!=",[233,1387,1388],{"class":242}," b.crs():\n",[233,1390,1391,1394,1396,1399,1401,1404,1406,1409,1411,1414,1416,1418],{"class":122,"line":285},[233,1392,1393],{"class":242},"        problems.append(",[233,1395,485],{"class":238},[233,1397,1398],{"class":297},"\"CRS ",[233,1400,491],{"class":304},[233,1402,1403],{"class":242},"a.crs().authid()",[233,1405,497],{"class":304},[233,1407,1408],{"class":297}," != ",[233,1410,491],{"class":304},[233,1412,1413],{"class":242},"b.crs().authid()",[233,1415,497],{"class":304},[233,1417,500],{"class":297},[233,1419,275],{"class":242},[233,1421,1422,1424,1427,1429],{"class":122,"line":310},[233,1423,1379],{"class":238},[233,1425,1426],{"class":242}," (a.width(), a.height()) ",[233,1428,1385],{"class":238},[233,1430,1431],{"class":242}," (b.width(), b.height()):\n",[233,1433,1434,1436,1438,1441,1443,1446,1448,1451,1453,1456,1458,1460,1462,1465,1467,1469,1471,1474,1476,1478],{"class":122,"line":321},[233,1435,1393],{"class":242},[233,1437,485],{"class":238},[233,1439,1440],{"class":297},"\"size ",[233,1442,491],{"class":304},[233,1444,1445],{"class":242},"a.width()",[233,1447,497],{"class":304},[233,1449,1450],{"class":297},"x",[233,1452,491],{"class":304},[233,1454,1455],{"class":242},"a.height()",[233,1457,497],{"class":304},[233,1459,1408],{"class":297},[233,1461,491],{"class":304},[233,1463,1464],{"class":242},"b.width()",[233,1466,497],{"class":304},[233,1468,1450],{"class":297},[233,1470,491],{"class":304},[233,1472,1473],{"class":242},"b.height()",[233,1475,497],{"class":304},[233,1477,500],{"class":297},[233,1479,275],{"class":242},[233,1481,1482,1485,1488,1490,1493,1496,1498,1501,1503,1506,1508,1511],{"class":122,"line":326},[233,1483,1484],{"class":238},"    for",[233,1486,1487],{"class":242}," name ",[233,1489,1183],{"class":238},[233,1491,1492],{"class":242}," (",[233,1494,1495],{"class":297},"\"xMinimum\"",[233,1497,519],{"class":242},[233,1499,1500],{"class":297},"\"yMinimum\"",[233,1502,519],{"class":242},[233,1504,1505],{"class":297},"\"xMaximum\"",[233,1507,519],{"class":242},[233,1509,1510],{"class":297},"\"yMaximum\"",[233,1512,574],{"class":242},[233,1514,1515,1518,1520,1523,1526,1529],{"class":122,"line":341},[233,1516,1517],{"class":242},"        va, vb ",[233,1519,291],{"class":238},[233,1521,1522],{"class":304}," getattr",[233,1524,1525],{"class":242},"(a.extent(), name)(), ",[233,1527,1528],{"class":304},"getattr",[233,1530,1531],{"class":242},"(b.extent(), name)()\n",[233,1533,1534,1537,1540,1543,1546,1549,1552],{"class":122,"line":364},[233,1535,1536],{"class":238},"        if",[233,1538,1539],{"class":304}," abs",[233,1541,1542],{"class":242},"(va ",[233,1544,1545],{"class":238},"-",[233,1547,1548],{"class":242}," vb) ",[233,1550,1551],{"class":238},">",[233,1553,1554],{"class":242}," tolerance:\n",[233,1556,1557,1560,1562,1564,1566,1569,1571,1574,1577,1579,1581,1583,1586,1588,1590],{"class":122,"line":369},[233,1558,1559],{"class":242},"            problems.append(",[233,1561,485],{"class":238},[233,1563,500],{"class":297},[233,1565,491],{"class":304},[233,1567,1568],{"class":242},"name",[233,1570,497],{"class":304},[233,1572,1573],{"class":304}," {",[233,1575,1576],{"class":242},"va",[233,1578,497],{"class":304},[233,1580,1408],{"class":297},[233,1582,491],{"class":304},[233,1584,1585],{"class":242},"vb",[233,1587,497],{"class":304},[233,1589,500],{"class":297},[233,1591,275],{"class":242},[233,1593,1594,1596],{"class":122,"line":384},[233,1595,1379],{"class":238},[233,1597,1598],{"class":242}," problems:\n",[233,1600,1601,1604,1607,1609,1612],{"class":122,"line":400},[233,1602,1603],{"class":238},"        raise",[233,1605,1606],{"class":304}," AssertionError",[233,1608,482],{"class":242},[233,1610,1611],{"class":297},"\"; \"",[233,1613,1614],{"class":242},".join(problems))\n",[233,1616,1617],{"class":122,"line":405},[233,1618,282],{"emptyLinePlaceholder":281},[233,1620,1621,1624,1626,1629,1631,1633,1636],{"class":122,"line":421},[233,1622,1623],{"class":242},"check ",[233,1625,291],{"class":238},[233,1627,1628],{"class":242}," QgsRasterLayer(",[233,1630,416],{"class":297},[233,1632,519],{"class":242},[233,1634,1635],{"class":297},"\"check\"",[233,1637,275],{"class":242},[233,1639,1640],{"class":122,"line":432},[233,1641,1642],{"class":242},"assert_same_grid(source, check)\n",[233,1644,1645],{"class":122,"line":443},[233,1646,282],{"emptyLinePlaceholder":281},[233,1648,1649,1652,1654],{"class":122,"line":449},[233,1650,1651],{"class":242},"probe ",[233,1653,291],{"class":238},[233,1655,1656],{"class":242}," source.extent().center()\n",[233,1658,1659,1662,1664,1667,1669],{"class":122,"line":473},[233,1660,1661],{"class":242},"src_val, _ ",[233,1663,291],{"class":238},[233,1665,1666],{"class":242}," source.dataProvider().sample(probe, ",[233,1668,516],{"class":304},[233,1670,275],{"class":242},[233,1672,1673,1676,1678,1681,1683],{"class":122,"line":505},[233,1674,1675],{"class":242},"out_val, _ ",[233,1677,291],{"class":238},[233,1679,1680],{"class":242}," check.dataProvider().sample(probe, ",[233,1682,516],{"class":304},[233,1684,275],{"class":242},[233,1686,1687,1690,1692,1694,1697,1699,1702,1705,1707,1710,1712,1715,1718,1720,1722],{"class":122,"line":510},[233,1688,1689],{"class":304},"print",[233,1691,482],{"class":242},[233,1693,485],{"class":238},[233,1695,1696],{"class":297},"\"centre cell: source ",[233,1698,491],{"class":304},[233,1700,1701],{"class":242},"src_val",[233,1703,1704],{"class":238},":.2f",[233,1706,497],{"class":304},[233,1708,1709],{"class":297},", output ",[233,1711,491],{"class":304},[233,1713,1714],{"class":242},"out_val",[233,1716,1717],{"class":238},":.4f",[233,1719,497],{"class":304},[233,1721,500],{"class":297},[233,1723,275],{"class":242},[233,1725,1726,1728,1730,1733,1736,1738],{"class":122,"line":526},[233,1727,1689],{"class":304},[233,1729,482],{"class":242},[233,1731,1732],{"class":297},"\"output NoData declared:\"",[233,1734,1735],{"class":242},", check.dataProvider().sourceHasNoDataValue(",[233,1737,516],{"class":304},[233,1739,361],{"class":242},[14,1741,1742,1744,1745,208],{},[197,1743,612],{}," Comparing CRS, size and all four extent edges proves the two grids are identical, because cell size and origin follow from those values. The tolerance absorbs floating-point noise in coordinates without letting a half-cell shift through. Sampling the same point in both layers is a sanity check on the values rather than the geometry — for a derived product like a gradient the numbers differ, but a NoData output where the source has data, or a value wildly out of range, shows up immediately. Confirming that NoData is declared in the written file guards against the most common downstream complaint, a black border where there should be transparency. Wrapped in a test, the same function belongs in a plugin's test suite, as in ",[21,1746,1748],{"href":1747},"\u002Fqgis-plugin-development\u002Ftesting-and-ci-for-plugins\u002Ftest-processing-algorithms-with-pytest-qgis\u002F","testing Processing algorithms with pytest-qgis",[186,1750,1752],{"id":1751},"add-the-result-to-the-project-with-a-style","Add the result to the project with a style",[14,1754,1755],{},"A written raster is just a file until someone opens it. Loading it and applying a renderer immediately makes the output reviewable in the same run.",[224,1757,1759],{"className":226,"code":1758,"language":228,"meta":229,"style":229},"from qgis.core import QgsRasterLayer, QgsSingleBandPseudoColorRenderer, QgsStyle\n\nresult_layer = QgsRasterLayer(\"\u002Fdata\u002Fwork\u002Fgradient.tif\", \"gradient\")\nstats = result_layer.dataProvider().bandStatistics(1)\nramp = QgsStyle.defaultStyle().colorRamp(\"Viridis\")\n\nrenderer = QgsSingleBandPseudoColorRenderer(result_layer.dataProvider(), 1)\nrenderer.setClassificationMin(stats.minimumValue)\nrenderer.setClassificationMax(stats.maximumValue)\nrenderer.createShader(ramp)\nresult_layer.setRenderer(renderer)\nQgsProject.instance().addMapLayer(result_layer)\n",[27,1760,1761,1772,1776,1794,1808,1823,1827,1841,1846,1851,1856,1861],{"__ignoreMap":229},[233,1762,1763,1765,1767,1769],{"class":122,"line":235},[233,1764,255],{"class":238},[233,1766,258],{"class":242},[233,1768,239],{"class":238},[233,1770,1771],{"class":242}," QgsRasterLayer, QgsSingleBandPseudoColorRenderer, QgsStyle\n",[233,1773,1774],{"class":122,"line":252},[233,1775,282],{"emptyLinePlaceholder":281},[233,1777,1778,1781,1783,1785,1787,1789,1792],{"class":122,"line":266},[233,1779,1780],{"class":242},"result_layer ",[233,1782,291],{"class":238},[233,1784,1628],{"class":242},[233,1786,416],{"class":297},[233,1788,519],{"class":242},[233,1790,1791],{"class":297},"\"gradient\"",[233,1793,275],{"class":242},[233,1795,1796,1799,1801,1804,1806],{"class":122,"line":272},[233,1797,1798],{"class":242},"stats ",[233,1800,291],{"class":238},[233,1802,1803],{"class":242}," result_layer.dataProvider().bandStatistics(",[233,1805,516],{"class":304},[233,1807,275],{"class":242},[233,1809,1810,1813,1815,1818,1821],{"class":122,"line":278},[233,1811,1812],{"class":242},"ramp ",[233,1814,291],{"class":238},[233,1816,1817],{"class":242}," QgsStyle.defaultStyle().colorRamp(",[233,1819,1820],{"class":297},"\"Viridis\"",[233,1822,275],{"class":242},[233,1824,1825],{"class":122,"line":285},[233,1826,282],{"emptyLinePlaceholder":281},[233,1828,1829,1832,1834,1837,1839],{"class":122,"line":310},[233,1830,1831],{"class":242},"renderer ",[233,1833,291],{"class":238},[233,1835,1836],{"class":242}," QgsSingleBandPseudoColorRenderer(result_layer.dataProvider(), ",[233,1838,516],{"class":304},[233,1840,275],{"class":242},[233,1842,1843],{"class":122,"line":321},[233,1844,1845],{"class":242},"renderer.setClassificationMin(stats.minimumValue)\n",[233,1847,1848],{"class":122,"line":326},[233,1849,1850],{"class":242},"renderer.setClassificationMax(stats.maximumValue)\n",[233,1852,1853],{"class":122,"line":341},[233,1854,1855],{"class":242},"renderer.createShader(ramp)\n",[233,1857,1858],{"class":122,"line":364},[233,1859,1860],{"class":242},"result_layer.setRenderer(renderer)\n",[233,1862,1863],{"class":122,"line":369},[233,1864,1865],{"class":242},"QgsProject.instance().addMapLayer(result_layer)\n",[14,1867,1868,1870,1871,1874,1875,1878,1879,208],{},[197,1869,612],{}," Band statistics come from the file and exclude NoData, so the ramp spans only real values — a check in itself, since a minimum of ",[27,1872,1873],{},"-9999"," would mean NoData was not declared. ",[27,1876,1877],{},"createShader"," builds the colour ramp shader between the classification bounds. Styling options beyond this are covered in ",[21,1880,1882],{"href":1881},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fapply-color-ramp-to-raster-pyqgis\u002F","applying a colour ramp to a raster",[186,1884,1886],{"id":1885},"qgis-version-compatibility","QGIS version compatibility",[14,1888,1889,623,1892,1895,1896,1898,1899,1902,1903,1906],{},[27,1890,1891],{},"QgsRasterFileWriter.createOneBandRaster",[27,1893,1894],{},"createMultiBandRaster"," have been available since QGIS 3.0, and ",[27,1897,1012],{}," with offsets since 2.x. ",[27,1900,1901],{},"setCreateOptions"," accepts GDAL creation options as a list of strings on all 3.x releases; on the QGIS 4 series check the method name against the API documentation for your build, as some option setters have gained typed variants. ",[27,1904,1905],{},"Qgis.DataType"," is required in its scoped form on QGIS 4.",[186,1908,1910],{"id":1909},"troubleshooting","Troubleshooting",[191,1912,1913,1919,1925,1934,1943],{},[194,1914,1915,1918],{},[197,1916,1917],{},"The output is shifted against the input."," The extent or size did not come from the source layer, so the implied cell size differs.",[194,1920,1921,1924],{},[197,1922,1923],{},"Stripes or noise instead of values."," The NumPy dtype does not match the block's data type.",[194,1926,1927,1930,1931,1933],{},[197,1928,1929],{},"The file is locked or empty after the script."," The provider was not closed; call ",[27,1932,642],{}," and delete it.",[194,1935,1936,1296,1939,1942],{},[197,1937,1938],{},"NoData shows as black.",[27,1940,1941],{},"setNoDataValue"," was not called, so the value is treated as data.",[194,1944,1945,1948],{},[197,1946,1947],{},"Seams at tile edges."," A neighbourhood calculation was run per tile without overlap.",[186,1950,1952],{"id":1951},"conclusion","Conclusion",[14,1954,1955,1956,1958],{},"Create the output from the source layer's extent, size and CRS so the grids match, pick the smallest data type with room for NoData, cast arrays to exactly that type before ",[27,1957,634],{},", and close the provider when done. For large rasters write tiles at their offsets with compression options set, and add overlap for neighbourhood calculations.",[186,1960,1962],{"id":1961},"frequently-asked-questions","Frequently Asked Questions",[14,1964,1965,1968,1969,1971,1972,1974],{},[197,1966,1967],{},"How do I write several bands?","\nUse ",[27,1970,1894],{}," with a band count, then call ",[27,1973,1012],{}," once per band number.",[14,1976,1977,1980,1981,1984],{},[197,1978,1979],{},"Can I write to formats other than GeoTIFF?","\nAny GDAL driver that supports creation works via ",[27,1982,1983],{},"setOutputFormat","; GeoTIFF is the safest default.",[14,1986,1987,1990],{},[197,1988,1989],{},"Should I use GDAL's Python bindings instead?","\nThey work equally well. The QGIS writer avoids a second API in a plugin and respects QGIS's CRS objects directly.",[14,1992,1993,1996,1997,1999,2000,623,2002,2004],{},[197,1994,1995],{},"Why is my output so much larger than the input?","\nProbably ",[27,1998,755],{}," without compression. Use ",[27,2001,742],{},[27,2003,162],{}," with a predictor.",[186,2006,2008],{"id":2007},"related","Related",[191,2010,2011,2016,2021,2027,2033],{},[194,2012,2013,2015],{},[21,2014,24],{"href":23}," — the guide this recipe belongs to",[194,2017,2018],{},[21,2019,2020],{"href":206},"Read Raster Pixels with QgsRasterBlock in PyQGIS",[194,2022,2023],{},[21,2024,2026],{"href":2025},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fraster-calculator-pyqgis\u002F","Use the Raster Calculator in PyQGIS",[194,2028,2029],{},[21,2030,2032],{"href":2031},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Freclassify-raster-values-pyqgis\u002F","Reclassify Raster Values in PyQGIS",[194,2034,2035],{},[21,2036,2038],{"href":2037},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fgenerate-slope-aspect-hillshade-pyqgis\u002F","Generate Slope, Aspect and Hillshade in PyQGIS",[2040,2041,2042],"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}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":229,"searchDepth":252,"depth":252,"links":2044},[2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055],{"id":188,"depth":252,"text":189},{"id":214,"depth":252,"text":215},{"id":768,"depth":252,"text":769},{"id":944,"depth":252,"text":945},{"id":1321,"depth":252,"text":1322},{"id":1751,"depth":252,"text":1752},{"id":1885,"depth":252,"text":1886},{"id":1909,"depth":252,"text":1910},{"id":1951,"depth":252,"text":1952},{"id":1961,"depth":252,"text":1962},{"id":2007,"depth":252,"text":2008},"Save computed arrays as georeferenced rasters with QgsRasterFileWriter — matching the grid of a source layer, choosing data types and NoData, writing large outputs in tiles, and setting compression options for GeoTIFF.","md",{"slug":2059,"type":2060,"breadcrumb":2061,"datePublished":2062,"dateModified":2062},"write-numpy-array-to-raster-pyqgis","article","Write a NumPy Array to a Raster","2026-09-17","\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fwrite-numpy-array-to-raster-pyqgis",{"title":5,"description":2056},"spatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fwrite-numpy-array-to-raster-pyqgis\u002Findex","zLTQ1SKYy392GdRaVVk9j6Srux_94I25b4Eqf1sE8DQ",1789632908560]