Satellite Photogrammetry¶
This section provides some basic recipes for photogrammetric processing of satellite imagery using Geomatica’s Python library. Topics include: Import raw satellite imagery, automatic GCP collection, automatic tie point collection, automatic refinement of GCPs and Tie Points, DSM extraction, DSM-to-DEM filtering, orthorectification, batch processing, advanced conditions and more…
Import Satellite Images¶
This recipe demonstrates how to import a GDB supported satellite image into a .pix file. This is NOT required for viewing the imagery and performing many types of processes on the data, but is often useful for photogrammetric processing.
from pci.fimport import fimport fimport(fili=r"C:\Kompsat\MSC_080407140610_09045_24220801PMSN14_1G.eph", filo=r"C:\Kompsat\Ingest\kompsat2_image.pix", dblayout="TILED512") print "Complete"
Create .pix Link File for Satellite Images¶
This recipe demonstrates how to create a .pix link file from an input image (often raw satellite image from a newer sensor). A link file creates a .pix file, but rather than copying the imagery, it only links back to the original image file. However, the file will have all the same file support as a regular .pix file.
Importing the file (fimport) creates a copy of the input image in .pix format, which uses more disk space and takes more time to process. The link file is a nice alternative to quickly import data into the .pix format while saving on disk space and processing time. It is important to keep in mind that the relative path between the link file and the raw imagery must be maintained. Otherwise the link will be broken.
from pci.link import link link(fili=r"C:\geoeye\po_311159_0000000\po_311159_bgrn_0000000.tif", filo=r"C:\geoeye\Output\link_GeoEye.pix")
Import satellite images from auxiliary files¶
Some raw satellite images are loaded with auxiliary text or xml files, such as, Landsat-8 and Kompsat-3 images. This concept can sometimes confuse developers when importing the imagery through code.
This recipe demonstrates how to ingest panchromatic, multispectral and thermal imagery from landsat-8 imagery, using the auxiliary text file.
from pci.fimport import fimport # import landsat-8 multispectral imagery raw_ms_l8 = r"C:\LC80150292013111LGN01\LC80150292013111LGN01_MTL.txt-MS" # notice the -MS at the end pix_ms_l8 = r"C:\ingest\landsat8_ms.pix" fimport(fili=raw_ms_l8, filo=pix_ms_l8) # import landsat-8 panchromatic imagery raw_pan_l8 = r"C:\LC80150292013111LGN01\LC80150292013111LGN01_MTL.txt-PAN" # notice the -PAN at the end pix_pan_l8 = r"C:\ingest\landsat8_pan.pix" fimport(fili=raw_pan_l8, filo=pix_pan_l8) # import landsat-8 thermal imagery raw_therm_l8 = r"C:\LC80150292013111LGN01\LC80150292013111LGN01_MTL.txt-Thermal" # notice the -Thermal at the end pix_therm_l8 = r"C:\ingest\landsat8_therm.pix" fimport(fili=raw_therm_l8, filo=pix_therm_l8)
Automatic GCP Collection and Refinement¶
This recipe demonstrates how to automatically collect GCPs on a raw satellite image and refine them based on statistical accuracy. In this case, root mean square error (RMSE) will be used to remove blunder GCPs.
from pci.link import link from pci.autogcp import autogcp from pci.gcprefn import gcprefn # Create Link File to hold new GCP segment(s) raw_geoeye_image = r"C:\geoeye\po_311159_0000000\po_311159_bgrn_0000000.tif" link_pix_file = r"C:\geoeye\Output\po_311159_bgrn_0000000.pix" link(fili=raw_geoeye_image, filo=link_pix_file) # Run GCP Collection and store new GCP segment in link pix file reference_image = r"c:\geoeye\Ref\S-55-40_lr_2000.tif" dem = r"C:\geoeye\po_311159_0000000\dem\gmted2010.pix" gcp_seg = autogcp(mfile=link_pix_file, dbic=[3],filref=reference_image, dbic_ref=[3], filedem=dem, searchr=[50]) # autogcp function returns the new gcp segment (saved to variable gcp_seg), which can then be directly passed to the gcp refinement function # Setup required parameters for automatic GCP refinement order = [0] # a polynomial order of 0 is best for most DigitalGlobe sensors (due to very accurate RPCs) reject = [5,0.5,0.5] # Continue to reject worse GCPs until RMSE falls below 0.5 pixel in x and y directions gcprefn(file=link_pix_file, dbgc=gcp_seg, modeltyp="RF", order=order, reject=reject) # the refined GCPs are now stored in the link pix file and can be used for further photogrammetric processing
Automatic GCP Collection and Refinement - Batch Processing¶
This recipe demonstrates how to automatically discover all valid images in a folder and subfolders, import the images as a pci link files, and collect and refine GCPs on all discovered images.
import fnmatch, os, sys from pci.link import link from pci.autogcp import autogcp from pci.gcprefn import gcprefn raw_dir = r"c:\geoeye\RAWDATA" file_filter = '*_bgrn_*.tif' # Looks for the Multispectral image only input_files_list = [] for r,d,f in os.walk(raw_dir): for file in fnmatch.filter(f, file_filter): print "found valid input file: ", file input_files_list.append(os.path.join(r,file)) if len(input_files_list) > 0: # handles condition if the input list is empty for raw_file in input_files_list: # iterates through each image file found. # Create .pix link file to store the GCP segement that holds the GCPs ingest_link_dir = r"C:\geoeye\output\ingest" ingest_link_filename = os.path.splitext(os.path.basename(raw_file))[0] + ".pix" # gets the filename of the raw image and then adds .pix extension ingest_link_file = os.path.join(ingest_link_dir, ingest_link_filename) link(fili=raw_file, filo=ingest_link_file) # Perform automatic GCP Collection ref = r"c:\geoeye\Ref\S-55-40_lr_2000.tif" dem = r"C:\geoeye\dem\gmted2010.pix" gcp_seg = autogcp(mfile=ingest_link_file, dbic=[1],filref=ref, dbic_ref=[3], filedem=dem, searchr=[50]) # Refine the GCPs based on RMSE gcprefn(file=ingest_link_file, dbgc=gcp_seg, modeltyp="RF", order=[0], reject=[5,0.5,0.5]) else: print "No image files found in " + raw_dir + " that match your filter criteria." sys.exit()
Iterate through GCP collection/refinement until success - Batch Processing¶
This recipe provides an advanced workflow for intelligently running multiple scenarios with different GCP collection and refinement settings until acceptable results are achieved. Specifically, this recipe runs a maximum of 10 iterations of the GCP collection, each time increasing the search radius until more than 10 GCPs are found or the search radius exceeds 500 pixels.
import fnmatch, os, sys from pci.autogcp import * from pci.gcprefn import * from pci.link import * root_dir = r"D:\Kompsat-2\Raw_Scenes_1G" file_filter = '*G_1G.eph' # Looks for the multispectral Kompsat2 image input_files_list = [] for r,d,f in os.walk(root_dir): for file in fnmatch.filter(f, file_filter): print "found valid input file: ", file input_files_list.append(os.path.join(r,file)) if len(input_files_list) > 0: for raw_file in input_files_list: ingest_file = os.path.join(r"D:\Kompsat-2\ingested", os.path.splitext(os.path.basename(raw_file))[0] + ".pix") link(fili=raw_file, filo=ingest_file) increase_radius = 0 numgcps = [0] # It is required to initially set the number of GCPs to zero to satisfy the condition while ((int(numgcps[0]) > 9) == True) != ((increase_radius < 500) == True): # Mimicking an XOR condition with two expressions normalized as boolean # Run autogcp to collect GCPs gcp_match_chan = [1] ref = r"D:\Kompsat-2\Reference\SPOT\o2_5m.pix" dem = r"D:\Kompsat-2\DEM\torontodem.pix" search_radius = [50 + increase_radius] # Search radius. Increase the search radius by 50 pixels each iteration numgcps = [] # AUTOGCP2 will return the number of gcps it found in this list gcp_seg = autogcp(mfile=ingest_file, dbic=[1],filref=ref, filedem=dem, searchr=search_radius, numgcps=numgcps) increase_radius += 50 # adds 50 pixels to the search radius for the next iteration (If required) # Run automatic GCP refinement (remove bad gcps) gcprefn(file=ingest_file, dbgc=[-1], modeltyp="RF", order=[0], reject=[5,1,1]) else: print "No image files found in " + root_dir + " that match your filter criteria." sys.exit()
Automatic GCP Collection and refinement from road vector layer¶
This recipe demonstrates how to automatically collect GCPs from a reference road vector layer.
import os from pci.fftmvec import * from pci.imageinv import * from pci.fimport import * from pci.iii import * from pci.pcimod import * from pci.gcprefn import * # Import the raw Kompsat-2 image to .pix file raw_kompsat2_file = r"C:\Kompsat-2\Raw_Scenes_1G\MSC_070611154402_04650_23191328_1G\MSC_070611154402_04650_23191328M4P17R_1G.eph" imported_k2_image = r"C:\Kompsat-2\working\kompsat2.pix" fimport(fili=raw_kompsat2_file, filo=imported_k2_image, dblayout="TILED512") # Invert the Near-Infrared channel - Helps FFTMVEC to successfully collect GCPs inverse_image = r"C:\Kompsat-2\working\inv_k2.pix" imageinv(fili=imported_k2_image, filo=inverse_image, dbic=[3]) # copy the inverted image channel into the imported image that will be used for GCP collection pcimod(file=imported_k2_image, pciop="ADD", pcival=[0,0,0,1]) iii(fili=inverse_image, filo=imported_k2_image, dbic=[1], dboc=[5]) # Run FFTMVEC to automatically collect GCPs from road vectors roadvec = r"C:\Kompsat-2\Reference\roads\toronto_vector.pix" gcp_seg = [] # the fftmvec lasc parameter will populate this variable with the output gcp segment number fftmvec(file=imported_k2_image, dbic=[1,2,4], dbinvc=[5], filv=roadvec, dbvs=[2], vecwidth=[15], lasc=gcp_seg) # Refine GCPs - remove bad GCPs until RMSE falls below 1 pixel in x and y direction gcprefn(file=imported_k2_image, dbgc=gcp_seg, modeltyp="RF", order=[0], reject=[5,1,1]) # Clean up unnecessary files and channels pcimod(file=imported_k2_image, pciop="DEL", pcival=[5]) os.remove(inverse_image)
Satellite Orthorectification with GCPs previously collected - Batch Processing¶
This recipe demonstrates how to use previously collected GCPs to improve the accuracy of a math model (RPCs). The updated model is then used to orthorectify the imagery.
from pci.rfmodel import rfmodel from pci.ortho import ortho # Calculate the mathematical model from GCPs (rational function model) required to orthorectify the imagery ingest_dir = r"D:\Python_Cookbook\Kompsat-2\ingested\*.pix" rfmodel(mfile=ingest_dir, dbgc=[9], numcoeff=[1]) # Orthorectify the imagery with the new and improved math model (RPC segment) ortho_dir = r"D:\Python_Cookbook\Kompsat-2\orthos" dem = r"D:\Python_Cookbook\Kompsat-2\DEM\torontodem.pix" ortho(mfile=ingest_dir, filo=ortho_dir, ftype="tif", bxpxsz="4", bypxsz="4", filedem=dem)
Automatic Bundle Adjustment and Orthorectification (GCPs previously collected)¶
This recipe demonstrates how to automatically collect tie points from overlapping regions of images and perform a block bundle adjustment to ensure relative alignment between images.
import os from pci.autotie import autotie from pci.tprefn import tprefn from pci.crproj import crproj from pci.cpmmseg import cpmmseg from pci.ortho import ortho # Create OrthoEngine Project, which is required for the bundle adjustment image_dir = r"C:\kompsat\ingest" work_dir = r"C:\kompsat\working\TiePoints" proj = os.path.join(work_dir, "import_oemodel.prj") projection = "UTM 17 T D000" # D000 is WGS84 crproj(mfile=image_dir, dbgc=[4], oeproj=proj, modeltyp=u"RFI1", mapunits=projection) # Run Automatic Tie Point Collection tp_oeproj = os.path.join(work_dir,"output_oetp.prj") match_chan = [4] # NIR channel often yields best matching results dem = r"C:\dem\gmted2010.pix" autotie(mfile=tp_oeproj, oeprojo=tp_oeproj, dbic=match_chan, filedem=dem) # Run automatic Tie Point Refinement tprefn(oeproji=tp_oeproj, reject=[5, 1.0, 1.0]) # Copy Math Model out to file # The second parameter in cpmmseg is FILE, which provides the name of the input image file for which the math model will be copied. # If FILE is not specified, all images contained within the project are copied. cpmmseg(oeproj=tp_oeproj) # Orthorectify images ortho_dir = r"C:\Kompsat\orthos" format = "TIF" ortho(mfile=image_dir, filo=ortho_dir, ftype=format, bxpxsz=[4], bypxsz=[4], filedem=dem)
Automatic DEM Extraction with Model Refinement(GCPs previously collected)¶
This recipe demonstrates how to automatically extract a Digital Surface Model (DSM) from satellite imagery and automatically filter out the surface features to create a bare-earth Digital Terrain Model (DTM).
from pci.rfmodel import rfmodel from pci.epipolar import epipolar from pci.autodem import autodem from pci.dsm2dtm import dsm2dtm # Update the geometric accuracy of the input stereo pair based on the GCPs (previously collected) ingest_dir = r"C:\geoeye\ingest" gcp_seg = [4] rfmodel(mfile=ingest_dir, dbgc=gcp_seg, numcoeff=[0]) # Generate the epipolar images, which are used to triangulate the 3D coordinates for the DEM epi_dir = r"C:\geoeye\epipolars" epipolar(mfile=ingest_dir, dbic=gcp_seg, selmthd="max", sampling=[1], outdir=epi_dir) # Automatically extract a Digital Surface Model from satellite stereo pairs out_dir = r"C:\geoeye\working\dsms" geocoded_dsm = r"c:\geoeye\dsm_new\geoeye_dsm.pix" dem_res = [6, 6] autodem(mfile=epi_dir, outdir=out_dir, filedem=geocoded_dsm, pxszout=dem_res) # convert DSM to DEM (Automatically remove surface features such as trees and buildings from DSM) dem_file = r"C:\geoeye\dsm_new\geoeye_dem.pix" feature_size = [150] gradient = [35] remove_bumps = [9,5,9,5] fill_pits = [7,5] smooth_dem = [9] dsm2dtm(fili=geocoded_dsm, dbic=[1], filo=dem_file, dboc=[1], objsize=feature_size, gthresh=gradient, tertype="hilly", bumpflt=remove_bumps, pitflt=fill_pits, medflt=smooth_dem)
Satellite Photogrammetric Batch Workflow with user provided DEM - Full Workflow¶
This recipe is a complete batch satellite photogrammetric workflow that performs the following: Discovers and ingests multiple raw satellite images, automatically collects GCPs from a reference image, refines GCPs based on their statistical accuracy, automatically collects tie points and refines them based on statistical accuracy, performs a block bundle adjustment and finally, orthorectifies the imagery.
import fnmatch, os, sys from pci.link import * from pci.autogcp import * from pci.gcprefn import * from pci.autotie import * from pci.tprefn import * from pci.crproj import * from pci.cpmmseg import * from pci.ortho import * raw_dir = r"C:\geoeye\RAWDATA" file_filter = '*_bgrn_*.tif' # Looks for the Multispectral image only dem = r"C:\geoeye\dem\gmted2010.pix" ingest_link_dir = r"C:\geoeye\ingest" # Create the link file directory if it does not yet exist if not os.path.exists(ingest_link_dir): os.makedirs(ingest_link_dir) input_files_list = [] for r,d,f in os.walk(raw_dir): for file in fnmatch.filter(f, file_filter): print "found valid input file: ", file input_files_list.append(os.path.join(r,file)) if len(input_files_list) > 0: for raw_file in input_files_list: # Create link files of raw images so that they can store GCP segments ingest_link_filename = os.path.splitext(os.path.basename(raw_file))[0] + ".pix" # get the filename of the raw image (without extension) ingest_link_file = os.path.join(ingest_link_dir, ingest_link_filename) link(fili=raw_file, filo=ingest_link_file) # Collect GCPs with autogcp ref_image = r"C:\geoeye\Ref\S-55-40_lr_2000.tif" gcp_seg = autogcp(mfile=ingest_link_file, dbic=[1], filref=ref_image,filedem=dem, searchr=[50]) # Refine GCPs with gcprefn gcprefn(file=ingest_link_file, dbgc=gcp_seg, modeltyp="RF", order=[0], reject=[5,2.0,2.0]) else: print "No image files found in " + raw_dir + " that match your filter criteria." sys.exit() # Create OrthoEngine Project, which is required for the bundle adjustment work_dir = r"C:\geoeye\working" if not os.path.exists(work_dir): os.makedirs(work_dir) proj = os.path.join(work_dir, "import_oemodel.prj") projection = "UTM 55 G D000" # D000 is WGS84 refined_gcps = [-1] # setting the dbgc parameter to -1 will ensure that the last GCP segment in a file will be imported crproj(mfile=ingest_link_dir, dbgc=[-1], oeproj=proj, modeltyp="RFI1", mapunits=projection) # Run Automatic Tie Point Collection tp_oeproj = work_dir + os.sep + "output_oetp.prj" tp_match_chan = [4] # NIR channel often yields best matching results autotie(mfile=proj, oeprojo=tp_oeproj, dbic=tp_match_chan, filedem=dem) # Run automatic Tie Point Refinement tprefn(oeproji=tp_oeproj, reject=[5, 0.5, 0.5]) # Copy Math Model out to file # The second parameter in cpmmseg is FILE, which provides the name of the input image file for which the math model will be copied. # If FILE is not specified, all images contained within the project are copied. cpmmseg(oeproj=tp_oeproj) # orthorectify images ortho_dir = r"C:\geoeye\orthos" if not os.path.exists(ortho_dir): os.makedirs(ortho_dir) ortho(mfile=ingest_link_dir, filo=ortho_dir, ftype="TIF", bxpxsz=[2], bypxsz=[2], filedem=dem)
Satellite Photogrammetric Batch Workflow with DEM Extraction - Full Workflow¶
This recipe is a complete batch satellite photogrammetric workflow that performs the following: Discovers and ingests multiple raw satellite images, automatically collects GCPs from a reference image, refines GCPs based on their statistical accuracy, automatically collects tie points and refines them based on statistical accuracy, performs a block bundle adjustment, performs DEM extraction and finally, orthorectifies the imagery.
import fnmatch, os, sys, shutil from pci.link import * from pci.autogcp import * from pci.gcprefn import * from pci.autotie import * from pci.tprefn import * from pci.crproj import * from pci.cpmmseg import * from pci.epipolar import * from pci.autodem import * from pci.dsm2dtm import * from pci.ortho import * from pci.exceptions import * raw_dir = r"C:\geoeye\RAWDATA" file_filter = '*_bgrn_*.tif' # Looks for the Multispectral image only dem = r"C:\geoeye\dem\gmted2010.pix" ingest_link_dir = r"C:\geoeye\ingest" # Create the link file directory if it does not yet exist if not os.path.exists(ingest_link_dir): os.makedirs(ingest_link_dir) input_files_list = [] for r,d,f in os.walk(raw_dir): for file in fnmatch.filter(f, file_filter): print "found valid input file: ", file input_files_list.append(os.path.join(r,file)) if len(input_files_list) > 0: for raw_file in input_files_list: # Create link files of raw images so that they can store GCP segments ingest_link_filename = os.path.splitext(os.path.basename(raw_file))[0] + ".pix" # get the filename of the raw image (without extension) ingest_link_file = os.path.join(ingest_link_dir, ingest_link_filename) link(fili=raw_file, filo=ingest_link_file) # Collect GCPs with autogcp ref_image = r"C:\geoeye\Ref\S-55-40_lr_2000.tif" gcp_seg = autogcp(mfile=ingest_link_file, dbic=[1], filref=ref_image,filedem=dem, searchr=[50]) # Refine GCPs with gcprefn gcprefn(file=ingest_link_file, dbgc=gcp_seg, modeltyp="RF", order=[0], reject=[5,2.0,2.0]) else: print "No image files found in " + raw_dir + " that match your filter criteria." sys.exit() # Create OrthoEngine Project, which is required for the bundle adjustment work_dir = r"C:\geoeye\working" if not os.path.exists(work_dir): os.makedirs(work_dir) proj = os.path.join(work_dir, "import_oemodel.prj") projection = "UTM 55 G D000" # D000 is WGS84 refined_gcps = [-1] # setting the dbgc parameter to -1 will ensure that the last GCP segment in a file will be imported crproj(mfile=ingest_link_dir, dbgc=[-1], oeproj=proj, modeltyp="RFI1", mapunits=projection) # Run Automatic Tie Point Collection tp_oeproj = work_dir + os.sep + "output_oetp.prj" tp_match_chan = [4] # NIR channel often yields best matching results autotie(mfile=proj, oeprojo=tp_oeproj, dbic=tp_match_chan, filedem=dem) # Run automatic Tie Point Refinement tprefn(oeproji=tp_oeproj, reject=[5, 0.5, 0.5]) # Copy Math Model out to file # The second parameter in cpmmseg is FILE, which provides the name of the input image file for which the math model will be copied. # If FILE is not specified, all images contained within the project are copied. cpmmseg(oeproj=tp_oeproj) # Generate the epipolar images, which are used to triangulate the 3D coordinates for the DEM epi_dir = r"C:\geoeye\epipolars" if not os.path.exists(epi_dir): os.makedirs(epi_dir) epipolar(mfile=ingest_link_dir, dbic=[3], selmthd="max", sampling=[1], outdir=epi_dir) # Automatically extract a Digital Surface Model from satellite stereo pairs epi_dem_dir = r"C:\geoeye\epi_dems" if not os.path.exists(epi_dem_dir): os.makedirs(epi_dem_dir) geocoded_dsm = r"C:\geoeye\dsm_new\geoeye_dsm.pix" if not os.path.exists(os.path.dirname(geocoded_dsm)): os.makedirs(os.path.dirname(geocoded_dsm)) autodem(mfile=epi_dir, outdir=epi_dem_dir, filedem=geocoded_dsm, pxszout=[6,6]) # convert DSM to DEM (Automatically remove surface features such as trees and buildings from DSM) dem_file = r"C:\geoeye\dsm_new\geoeye_dem.pix" dsm2dtm(fili=geocoded_dsm, dbic=[1], filo=dem_file, dboc=[1], objsize=[150], gthresh=[35], tertype="hilly", bumpflt=[9,5,9,5], pitflt=[7,5], medflt=[9]) # orthorectify images ortho_dir = r"C:\geoeye\orthos" if not os.path.exists(ortho_dir): os.makedirs(ortho_dir) ortho(mfile=ingest_link_dir, filo=ortho_dir, ftype="TIF", bxpxsz=[2], bypxsz=[2], filedem=dem_file) # Clean up unnecessary files and directories del_dirs_list = [epi_dem_dir, epi_dir, work_dir, ingest_link_dir] for del_dir in del_dirs_list: shutil.rmtree(del_dir)