Trovare l'estensione minima di delimitazione del valore di pixel dato all'interno del raster?


9

Mi chiedo se esiste un modo per trovare l'estensione minima di delimitazione per un raster con un valore particolare. Ho ritagliato un raster da un'immagine globale e l'estensione è impostata come estensione globale con molta area NoData. Vorrei rimuovere l'area NoData da questo raster e conservare solo l'area contenente i pixel del valore particolare. Come posso fare questo?

Ecco il mio esempio: vorrei estrarre value = 1 (area blu) e utilizzare l'estensione dell'area blu anziché l'intero mondo per ulteriori elaborazioni.

Immagine di esempio


Potresti pubblicare un campione?
Aaron

"Vorrei eliminare le righe e le colonne null per questo raster." Cosa significa esattamente? Non capisco quale sia il risultato finale desiderato.
blah238,

Per "estensione minima del limite" stai cercando un'estensione rettangolare o un'impronta poligonale che rappresenti l'area dell'immagine con i dati?
blah238

1
@Tomek, l'OP sta cercando di trovare l'estensione, non è necessario crearlo manualmente.
blah238,

1
Se qualcosa è letteralmente un gioco leale, allora alcuni software hanno comandi integrati per farlo; consultare ad esempio reference.wolfram.com/mathematica/ref/ImageCrop.html .
whuber

Risposte:


6

Se ho capito correttamente la domanda, sembra che tu voglia conoscere il limite minimo dei valori che non sono nulli. Forse potresti convertire il raster in poligoni, selezionare i poligoni che ti interessano e poi convertirli nuovamente in raster. È quindi possibile esaminare i valori delle proprietà che dovrebbero fornire il riquadro di delimitazione minimo.


1
Tutto sommato, questo è probabilmente l'approccio migliore, visti i confini del flusso di lavoro di elaborazione raster ArcGIS.
blah238,

L'ho fatto esattamente. C'è un modo automatico? Penso che l'algoritmo da raster a poligono abbia un passaggio per estrarre il riquadro di delimitazione minimo del raster.
Visto il

stai cercando una soluzione Python?
dango,

8

Il trucco è calcolare i limiti dei dati che hanno valori. Forse il modo più veloce, più naturale e più generale per ottenerli è con i riassunti zonali: usando tutte le celle non NoData per la zona, il minimo e il massimo zonale delle griglie contenenti le coordinate X e Y forniranno la massima estensione.

ESRI continua a cambiare il modo in cui questi calcoli possono essere fatti; ad esempio, le espressioni integrate per le griglie delle coordinate sono state eliminate con ArcGIS 8 e sembrano non essere state restituite. Solo per divertimento, ecco una serie di calcoli veloci e semplici che faranno il lavoro, non importa quale.

  1. Converti la griglia in una singola zona equiparandola a se stessa, come in

    "La mia griglia" == "La mia griglia"

  2. Crea una griglia di indice di colonna accumulando un flusso di una griglia costante con valore 1. (Gli indici inizieranno con 0.) Se lo desideri, moltiplica questo per le dimensioni della cella e aggiungi la coordinata x dell'origine per ottenere una griglia di coordinate x " X "(mostrato sotto).

  3. Allo stesso modo, creare una griglia di indice di riga ( e quindi una griglia di coordinate Y "Y") accumulando una griglia costante con valore 64.

  4. Usa la griglia della zona dal punto (1) per calcolare il minimo e il massimo zonale di "X" e "Y" : ora hai l'estensione desiderata.

Immagine finale

(L'estensione, come mostrato nelle due tabelle delle statistiche zonali, è rappresentata da un contorno rettangolare in questa figura. La griglia "I" è la griglia di zona ottenuta nel passaggio (1).)

Per andare oltre, dovrai estrarre questi quattro numeri dalle loro tabelle di output e usarli per limitare l'estensione dell'analisi. La copia della griglia originale, con l'estensione limitata dell'analisi in atto, completa l'attività.


8

Ecco una versione del metodo @whubers per ArcGIS 10.1+ come toolbox python (.pyt).

import arcpy

class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        self.label = "Raster Toolbox"
        self.alias = ""

        # List of tool classes associated with this toolbox
        self.tools = [ClipNoData]


class ClipNoData(object):
    def __init__(self):
        """Clip raster extent to the data that have values"""
        self.label = "Clip NoData"
        self.description = "Clip raster extent to the data that have values. "
        self.description += "Method by Bill Huber - https://gis.stackexchange.com/a/55150/2856"

        self.canRunInBackground = False

    def getParameterInfo(self):
        """Define parameter definitions"""
        params = []

        # First parameter
        params+=[arcpy.Parameter(
            displayName="Input Raster",
            name="in_raster",
            datatype='GPRasterLayer',
            parameterType="Required",
            direction="Input")
        ]

        # Second parameter
        params+=[arcpy.Parameter(
            displayName="Output Raster",
            name="out_raster",
            datatype="DERasterDataset",
            parameterType="Required",
            direction="Output")
        ]

        return params

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return arcpy.CheckExtension('spatial')==u'Available'

    def execute(self, parameters, messages):
        """See https://gis.stackexchange.com/a/55150/2856
           ##Code comments paraphrased from @whubers GIS StackExchange answer
        """
        try:
            #Setup
            arcpy.CheckOutExtension('spatial')
            from arcpy.sa import *
            in_raster = parameters[0].valueAsText
            out_raster = parameters[1].valueAsText

            dsc=arcpy.Describe(in_raster)
            xmin=dsc.extent.XMin
            ymin=dsc.extent.YMin
            mx=dsc.meanCellWidth
            my=dsc.meanCellHeight
            arcpy.env.extent=in_raster
            arcpy.env.cellSize=in_raster
            arcpy.AddMessage(out_raster)

            ## 1. Convert the grid into a single zone by equating it with itself
            arcpy.AddMessage(r'1. Convert the grid into a single zone by equating it with itself...')
            zones = Raster(in_raster) == Raster(in_raster)

            ## 2. Create a column index grid by flow-accumulating a constant grid
            ##    with value 1. (The indexes will start with 0.) Multiply this by
            ##    the cellsize and add the x-coordinate of the origin to obtain
            ##    an x-coordinate grid.
            arcpy.AddMessage(r'Create a constant grid...')
            const = CreateConstantRaster(1)

            arcpy.AddMessage(r'2. Create an x-coordinate grid...')
            xmap = (FlowAccumulation(const)) * mx + xmin

            ## 3. Similarly, create a y-coordinate grid by flow-accumulating a
            ##    constant grid with value 64.
            arcpy.AddMessage(r'3. Create a y-coordinate grid...')
            ymap = (FlowAccumulation(const * 64)) * my + ymin

            ## 4. Use the zone grid from step (1) to compute the zonal min and
            ##    max of "X" and "Y"
            arcpy.AddMessage(r'4. Use the zone grid from step (1) to compute the zonal min and max of "X" and "Y"...')

            xminmax=ZonalStatisticsAsTable(zones, "value", xmap,r"IN_MEMORY\xrange", "NODATA", "MIN_MAX")
            xmin,xmax=arcpy.da.SearchCursor(r"IN_MEMORY\xrange", ["MIN","MAX"]).next()

            yminmax=ZonalStatisticsAsTable(zones, "value", ymap,r"IN_MEMORY\yrange", "NODATA", "MIN_MAX")
            ymin,ymax=arcpy.da.SearchCursor(r"IN_MEMORY\yrange", ["MIN","MAX"]).next()

            arcpy.Delete_management(r"IN_MEMORY\xrange")
            arcpy.Delete_management(r"IN_MEMORY\yrange")

            # Write out the reduced raster
            arcpy.env.extent = arcpy.Extent(xmin,ymin,xmax+mx,ymax+my)
            output = Raster(in_raster) * 1
            output.save(out_raster)

        except:raise
        finally:arcpy.CheckInExtension('spatial')

Molto carino Luca. Autonomo, eseguibile, utilizza in_memory e ben commentato per l'avvio. Ho dovuto disattivare l'elaborazione in background ( Geoprocessing> opzioni> ... ) per farlo funzionare.
Matt Wilson

1
Ho aggiornato lo script e impostato canRunInBackground = False. Noterò che vale la pena cambiare l'ambiente di lavoro / ambiente di lavoro in una cartella locale (non FGDB) come ho trovato quando li ho lasciati come predefiniti (ad es. <Profilo utente di rete> \ Default.gdb) lo script ha impiegato 9 minuti !!! per funzionare su una griglia di celle 250x250. Passando a un FGDB locale ci sono voluti 9sec e in una cartella locale 1-2sec ...
user2856

buon punto sulle cartelle locali e grazie per la rapida correzione in background (molto meglio che scrivere le istruzioni per tutti quelli a cui lo passo). Potrebbe valere la pena di vomitare su bitbucket / github / gcode / etc.
Matt Wilson

+1 Grazie per questo contributo, Luke! Apprezzo che tu abbia colmato il gap (piuttosto grande) rimasto nella mia risposta.
whuber

4

Ho escogitato una soluzione basata su gdal e intorpidimento. Rompe la matrice raster in righe e colonne e rilascia qualsiasi riga / colonna vuota. In questa implementazione "vuoto" è qualcosa di meno di 1 e sono stati considerati solo i raster a banda singola.

(Mi rendo conto mentre scrivo che questo approccio scanline è adatto solo per immagini con "collari" di nodati. Se i tuoi dati sono isole in mari di nulli, anche lo spazio tra le isole verrà lasciato cadere, schiacciando tutto insieme e rovinando totalmente la georeferenziazione .)

Le parti aziendali (devono essere scaricate, non funzioneranno così come sono):

    #read raster into a numpy array
    data = np.array(gdal.Open(src_raster).ReadAsArray())
    #scan for data
    non_empty_columns = np.where(data.max(axis=0)>0)[0]
    non_empty_rows = np.where(data.max(axis=1)>0)[0]
        # assumes data is any value greater than zero
    crop_box = (min(non_empty_rows), max(non_empty_rows),
        min(non_empty_columns), max(non_empty_columns))

    # retrieve source geo reference info
    georef = raster.GetGeoTransform()
    xmin, ymax = georef[0], georef[3]
    xcell, ycell = georef[1], georef[5]

    # Calculate cropped geo referencing
    new_xmin = xmin + (xcell * crop_box[0]) + xcell
    new_ymax = ymax + (ycell * crop_box[2]) - ycell
    cropped_transform = new_xmin, xcell, 0.0, new_ymax, 0.0, ycell

    # crop
    new_data = data[crop_box[0]:crop_box[1]+1, crop_box[2]:crop_box[3]+1]

    # write to disk
    band = out_raster.GetRasterBand(1)
    band.WriteArray(new_data)
    band.FlushCache()
    out_raster = None

In uno script completo:

import os
import sys
import numpy as np
from osgeo import gdal

if len(sys.argv) < 2:
    print '\n{} [infile] [outfile]'.format(os.path.basename(sys.argv[0]))
    sys.exit(1)

src_raster = sys.argv[1]
out_raster = sys.argv[2]

def main(src_raster):
    raster = gdal.Open(src_raster)

    # Read georeferencing, oriented from top-left
    # ref:GDAL Tutorial, Getting Dataset Information
    georef = raster.GetGeoTransform()
    print '\nSource raster (geo units):'
    xmin, ymax = georef[0], georef[3]
    xcell, ycell = georef[1], georef[5]
    cols, rows = raster.RasterYSize, raster.RasterXSize
    print '  Origin (top left): {:10}, {:10}'.format(xmin, ymax)
    print '  Pixel size (x,-y): {:10}, {:10}'.format(xcell, ycell)
    print '  Columns, rows    : {:10}, {:10}'.format(cols, rows)

    # Transfer to numpy and scan for data
    # oriented from bottom-left
    data = np.array(raster.ReadAsArray())
    non_empty_columns = np.where(data.max(axis=0)>0)[0]
    non_empty_rows = np.where(data.max(axis=1)>0)[0]
    crop_box = (min(non_empty_rows), max(non_empty_rows),
        min(non_empty_columns), max(non_empty_columns))

    # Calculate cropped geo referencing
    new_xmin = xmin + (xcell * crop_box[0]) + xcell
    new_ymax = ymax + (ycell * crop_box[2]) - ycell
    cropped_transform = new_xmin, xcell, 0.0, new_ymax, 0.0, ycell

    # crop
    new_data = data[crop_box[0]:crop_box[1]+1, crop_box[2]:crop_box[3]+1]

    new_rows, new_cols = new_data.shape # note: inverted relative to geo units
    #print cropped_transform

    print '\nCrop box (pixel units):', crop_box
    print '  Stripped columns : {:10}'.format(cols - new_cols)
    print '  Stripped rows    : {:10}'.format(rows - new_rows)

    print '\nCropped raster (geo units):'
    print '  Origin (top left): {:10}, {:10}'.format(new_xmin, new_ymax)
    print '  Columns, rows    : {:10}, {:10}'.format(new_cols, new_rows)

    raster = None
    return new_data, cropped_transform


def write_raster(template, array, transform, filename):
    '''Create a new raster from an array.

        template = raster dataset to copy projection info from
        array = numpy array of a raster
        transform = geo referencing (x,y origin and pixel dimensions)
        filename = path to output image (will be overwritten)
    '''
    template = gdal.Open(template)
    driver = template.GetDriver()
    rows,cols = array.shape
    out_raster = driver.Create(filename, cols, rows, gdal.GDT_Byte)
    out_raster.SetGeoTransform(transform)
    out_raster.SetProjection(template.GetProjection())
    band = out_raster.GetRasterBand(1)
    band.WriteArray(array)
    band.FlushCache()
    out_raster = None
    template = None

if __name__ == '__main__':
    cropped_raster, cropped_transform = main(src_raster)
    write_raster(src_raster, cropped_raster, cropped_transform, out_raster)

Lo script è nella mia scorta di codice su Github, se il collegamento va in giro un po '404; queste cartelle sono mature per qualche riorganizzazione.


1
Questo non funzionerà con set di dati molto grandi. Ottengo unMemoryError Source raster (geo units): Origin (top left): 2519950.0001220703, 5520150.00012207 Pixel size (x,-y): 100.0, -100.0 Columns, rows : 42000, 43200 Traceback (most recent call last): File "D:/11202067_COACCH/local_checkout/crop_raster.py", line 72, in <module> cropped_raster, cropped_transform = main(src_raster) File "D:/11202067_COACCH/local_checkout/crop_raster.py", line 22, in main data = np.array(raster.ReadAsArray()) MemoryError
user32882

2

Nonostante tutto il suo potere analitico, ArcGIS manca di manipolazioni raster di base che puoi trovare con i tradizionali editor di immagini desktop come GIMP . Si aspetta che si desideri utilizzare la stessa estensione di analisi per il raster di output del raster di input, a meno che non si ignori manualmente l' impostazione dell'ambiente di estensione di output . Poiché questo è esattamente ciò che stai cercando di trovare, non impostare, il modo in cui ArcGIS di fare le cose si sta mettendo in mezzo.

Nonostante i miei migliori sforzi, e senza ricorrere alla programmazione, non sono riuscito a trovare alcun modo per ottenere l'estensione del sottoinsieme desiderato dell'immagine (senza conversione da raster a vettore che è computazionalmente dispendiosa).

Invece, ho usato GIMP per selezionare l'area blu usando lo strumento Seleziona per colore e quindi ho invertito la selezione, ho premuto Elimina per rimuovere il resto dei pixel, ho invertito di nuovo la selezione, ritagliato l'immagine in selezione e infine esportata indietro in PNG. GIMP lo ha salvato come immagine di profondità a 1 bit. Il risultato è sotto:

Produzione

Naturalmente, poiché l'immagine di esempio mancava di un componente di riferimento spaziale e GIMP non è spazialmente consapevole, l'immagine di output è utile quanto l'input di esempio. Dovrai georeferenziarlo per essere utile in un'analisi spaziale.


In realtà, questa operazione era semplice nelle precedenti versioni di Analista spaziale: il massimo e il minimo zonali delle due griglie di coordinate (X e Y), usando la funzione come zona, danno esattamente l'estensione. (Beh, potresti voler espanderlo di mezza cella in tutte e quattro le direzioni.) In ArcGIS 10, devi essere creativo (o usare Python) per creare una griglia di coordinate. Indipendentemente da ciò, il tutto può essere fatto interamente all'interno di SA usando solo operazioni di rete e nessun intervento manuale.
whuber

@whuber Ho visto la tua soluzione da qualche altra parte, ma non sono ancora sicuro di come posso implementare il tuo metodo. Potresti mostrarmi qualche dettaglio in più di questo?
Visto

@Seen Una rapida ricerca di questo sito trova un account del metodo su gis.stackexchange.com/a/13467 .
whuber

1

Ecco una possibilità utilizzando SAGA GIS: http://permalink.gmane.org/gmane.comp.gis.gdal.devel/33021

In SAGA GIS è presente un modulo "Ritaglia ai dati" (nella libreria del modulo Grid Tools), che esegue l'attività.

Ma ciò richiederebbe di importare il tuo Geotif con il modulo di importazione GDAL, elaborarlo in SAGA e infine esportarlo nuovamente come Geotif con il modulo di esportazione GDAL.

Un'altra possibilità che utilizza solo gli strumenti ArcGIS GP sarebbe quella di costruire una TIN dal tuo raster usando Raster a TIN , calcolare il suo confine usando il dominio TIN e ritagliare il tuo raster per il confine (o la sua busta usando Feature Envelope to Polygon ).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.