# PipelinePanSharp.sml # Sample script that illustrates # 1) the use of a custom dialog window constructed using an XML dialog specification # 2) assembly of an Image Pipeline using options set from the dialog # The script computes a pan-sharpened color-composite image from three low-resolution bands # of a multispectral image (must match each other in dimensions and extents) and a higher-resolution # panchromatic image. The low-res bands must overlap but need not match the extents of the hi-res # image; the low-res composite image is automatically resampled to match the extents of the hi-res # image before the multiresolution fusion stage. # The pan-sharpened image can be saved as an RVC raster object or as a TIFF, PNG, JP2, or JPEG file. # If the Apply Contrast option is selected for an input raster, # the last-used contrast table is used to set up a filter to apply contrast to that # raster before further processing. If no contrast table is found, a # default normalized contrast table is automatically computed and used. # Randy Smith # MicroImages, Inc. # 26 March 2008 # Requires Version 2007:73 of the TNT products. ############################################################################## ################### Global Variable Declarations ########################### ############################################################################## class STRING xml$; # string containing the XML text with the dialog specification class XMLDOC dlgdoc; # class instance for the XML document class XMLNODE psfdlgnode; # class instance for the node in the XML # document corresponding to the dialog class GUI_DLG psfdialog; # class instance for the GUI dialog class GUI_CTRL_LABEL panSatLabel, rgbSatLabel; # labels for pan and rgb saturation fields class GUI_CTRL_LABEL methodLabel, geoformatLabel; # labels for the method and geoformat comboboxes class GUI_CTRL_LABEL ratioLabel; # label for the ratio field class GUI_CTRL_EDIT_NUMBER panSat, rgbSat; # numeric fields for saturation values class GUI_CTRL_EDIT_NUMBER ratio; # numeric field for JP2 compression ratio / JPEG quality class GUI_CTRL_COMBOBOX methodBox, formatBox; # method and format comboboxes class GUI_CTRL_COMBOBOX geoformatBox; # geoformat combobox class GUI_FORMDATA data; # class instance for retrieved dialog settings class RVC_RASTER Red, Green, Blue, Pan; # class instances for the input rasters class STRING method$; # color blending method from dialog combobox; class STRING format$; # selected output format from dialog combobox numeric err; # value returned by various predefined functions; use to manually handle errors numeric rgbMax; # maximum value for setting RGB Saturation, set from datatype numeric panMax; # maximum value for setting Pan Saturation, set from datatype numeric satEnabled; # flag to indicate whether saturation controls are enabled/disabled numeric geoformatEnabled; # flag to indicate whether geoformat controls are enabled/disabled numeric ratioSet; # flag to indicate whether ratio controls are enabled/disabled ####################################################################### ################## Procedures & Functions ########################### ######################################## ### error checking procedure proc ReportError(numeric linenum, numeric err) { printf("FAILED -line: %d, error: %d\n", linenum - 1, err); PopupError(err); } #################################################################### ### procedure called when color blending method is set on the dialog proc OnSelectMethod() { method$ = methodBox.GetSelectedItemID(); if ( (method$ == "basicHIS" or method$ == "modHIS" or method$ == "HBS") && satEnabled == 0 ) { panSatLabel.SetEnabled(1); rgbSatLabel.SetEnabled(1); panSat.SetValueNum(panMax); panSat.SetEnabled(1); rgbSat.SetValueNum(rgbMax); rgbSat.SetEnabled(1); satEnabled = 1; } else if (method$ == "Brovey" && satEnabled == 1) { panSatLabel.SetEnabled(0); rgbSatLabel.SetEnabled(0); panSat.SetEnabled(0); rgbSat.SetEnabled(0); satEnabled = 0; } } ################################################# ### procedure to enable geoformat dialog controls proc geoformatEnable() { geoformatLabel.SetEnabled(1); geoformatBox.SetEnabled(1); geoformatEnabled = 1; } ############################################################################### ### procedure to disable JP2 target ratio and JPEG compression quality controls proc ratioDisable() { ratioLabel.SetVisible(0); ratio.SetVisible(0); ratioSet = 0; } ############################################################################## ### procedure to enable JP2 target ratio and JPEG compression quality controls proc ratioEnable() { ratioLabel.SetVisible(1); ratioLabel.SetEnabled(1); ratio.SetVisible(1); ratio.SetEnabled(1); ratioSet = 1; } ################################################################# ### function called when Output Format is changed on the dialog proc OnSelectFormat() { format$ = formatBox.GetSelectedItemID(); if ( format$ == "GeoTIFF" or format$ == "PNG" or format$ == "JP2" ) { if (geoformatEnabled == 0) geoformatEnable(); if (ratioSet) then ratioDisable(); } else if (format$ == "JP2l") { if (geoformatEnabled == 0) geoformatEnable(); ratioLabel.SetLabel("Target Ratio"); if (ratioSet == 0) then ratioEnable(); ratio.SetValue(10,0); } else if (format$ == "JPEG") { if (geoformatEnabled == 0) then geoformatEnable(); ratioLabel.SetLabel("Quality"); ratio.SetValue(80,0); if (ratioSet == 0) then ratioEnable(); } else if (format$ == "RVC" && geoformatEnabled == 1) { geoformatLabel.SetEnabled(0); geoformatBox.SetEnabled(0); geoformatEnabled = 0; if (ratioSet) then ratioDisable(); } } # end of proc OnSelectFormat() ############################################################################## ### procedure to compute standard contrast table for copy of input raster that ### has no contrast table if apply contrast is selected proc computeCon(class CONTRAST con, class RVC_RASTER Rast) { class RVC_OBJECT tempFile; class RVC_RASTER tempRast; # copy source raster to temporary file, then close source raster tempFile.MakeTempFile(1); Rast.CopyTo(tempFile, tempRast); Rast.Close(); # open temporary raster and compute standard contrast table for it tempRast.OpenAttached("Write"); con.ComputeStandard(tempRast, "normalize"); Rast = tempRast; # reassign temporary raster to original raster variable tempRast.Close(); # close temporary raster } #################################################################################### ### procedure to initially disable OK button on main dialog window when it opens and ### get handles for controls that are accessed more than once in the script proc OnOpenDlg () { psfdialog.SetOkEnabled( 0 ); methodBox = psfdialog.GetCtrlByID("method"); formatBox = psfdialog.GetCtrlByID("format"); ratio = psfdialog.GetCtrlByID("ratio"); ratio.SetVisible(0); ratioLabel = psfdialog.GetCtrlByID("ratioLabel"); ratioLabel.SetVisible(0); geoformatBox = psfdialog.GetCtrlByID("geoformat"); geoformatLabel = psfdialog.GetCtrlByID("geoformatLabel"); panSatLabel = psfdialog.GetCtrlByID("panSatLabel"); rgbSatLabel = psfdialog.GetCtrlByID("rgbSatLabel"); panSat = psfdialog.GetCtrlByID("panSat"); rgbSat = psfdialog.GetCtrlByID("rgbSat"); } ################################################################################### ### procedure to write band filename / object name to the dialog (in edittext field) proc SetBandInfo(class RVC_RASTER Band, string id$) { local string filename$, objname$, dlgtext$; local numeric objnum; filename$ = GetObjectFileName( Band ); objnum = GetObjectNumber( Band ); objname$ = GetObjectName( filename$, objnum ); dlgtext$ = sprintf( "%s.%s / %s", FileNameGetName(filename$), FileNameGetExt(filename$), objname$ ); psfdialog.SetCtrlValueStr( id$, dlgtext$ ); } # end proc GetBand() ################################################################################################ ### callback procedure for the Red... pushbutton; prompts user to select the Red color component proc SelectRed () { GetInputRaster( Red, 0, 0); # check to ensure that first raster selected is an unsigned 8-bit or 16-bit grayscale raster if (Red.$Info.Type == "8-bit unsigned" or Red.$Info.Type == "16-bit unsigned") { SetBandInfo( Red, "rname" ); psfdialog.GetCtrlByID( "conred" ).SetEnabled( 1 ); psfdialog.GetCtrlByID( "getg" ).SetEnabled( 1 ); # PIPELINE SOURCE FOR RED LOW-RES class IMAGE_PIPELINE_SOURCE_RVC source_Red(Red.GetObjItem() ); err = source_Red.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Set red source."); # set default maximum value for RGB saturation from datatype if (Red.$Info.Type == "8-bit unsigned") then rgbMax = 254; else if (Red.$Info.Type == "16-bit unsigned") then rgbMax = 62537; } else { print("Only an 8-bit or 16-bit unsigned raster can be used for this input."); } } # end proc SelectRed() #################################################################################################### ### callback procedure for the Green... pushbutton; prompts user to select the Green color component proc SelectGreen () { GetInputRaster(Green, Red.$Info.NumLins, Red.$Info.NumCols, Red.$Info.Type); SetBandInfo( Green, "gname" ); psfdialog.GetCtrlByID( "congrn" ).SetEnabled( 1 ); psfdialog.GetCtrlByID( "getb" ).SetEnabled( 1 ); # PIPELINE SOURCE FOR GREEN LOW-RES class IMAGE_PIPELINE_SOURCE_RVC source_Green(Green.GetObjItem() ); err = source_Green.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Set green source."); } # end proc SelectGreen() ################################################################################################## ### callback procedure for the Blue... pushbutton; prompts user to select the Blue color component proc SelectBlue () { GetInputRaster(Blue, Red.$Info.NumLins, Red.$Info.NumCols, Red.$Info.Type); SetBandInfo( Blue, "bname" ); psfdialog.GetCtrlByID( "conblue" ).SetEnabled( 1 ); psfdialog.GetCtrlByID( "getp" ).SetEnabled( 1 ); # PIPELINE SOURCE FOR BLUE LOW-RES class IMAGE_PIPELINE_SOURCE_RVC source_Blue(Blue.GetObjItem() ); err = source_Blue.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Set blue source."); } # end proc SelectBlue() ###################################################################################################### ### callback procedure for the Intensity... pushbutton; prompts user to select the Intensity component proc SelectPan () { GetInputRaster(Pan); if (Pan.$Info.Type == "8-bit unsigned" or Pan.$Info.Type == "16-bit unsigned") { SetBandInfo( Pan, "pname" ); psfdialog.GetCtrlByID( "conpan" ).SetEnabled( 1 ); # PIPELINE SOURCE FOR HI-RES PANCHROMATIC IMAGE class IMAGE_PIPELINE_SOURCE_RVC source_Pan(Pan.GetObjItem() ); err = source_Pan.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Set pan source."); psfdialog.GetCtrlByID("methodLabel").SetEnabled(1); # enable controls for method and format methodBox.SetEnabled(1); psfdialog.GetCtrlByID("formatLabel").SetEnabled(1); formatBox.SetEnabled(1); # set default values for color blending method and output format in case these # control settings are not changed method$ = "Brovey"; format$ = "RVC"; # set maximum value for pan saturation if (Pan.$Info.Type == "8-bit unsigned") then panMax = 254; else if (Pan.$Info.Type == "16-bit unsigned") then panMax = 62537; psfdialog.SetOkEnabled( 1 ); # enable the dialog's OK button } else { print("Binary, composite, & floating point rasters cannot be used as input for this script. Exiting now."); Exit(); } } # end proc SelectPan() ################################################################################################## ### callback procedure for the OK button on the dialog to retrieve the dialog settings and store in GUI_FORMDATA proc GetValues () { data = psfdialog.GetValues(); } ##################################################################################### #################################### Main Program ############################### ##################################################################################### clear(); $warnings 3; ############################################################## ### string variable with XML specification for control dialog xml$=' Brovey basic HIS modified HIS HBS RVC GeoTIFF PNG JP2 Lossless JP2 Lossy JPEG None ArcWorld Google KML '; ### parse XML text for the dialog into memory; return an error code (number < 0 ) if there are syntax errors err = dlgdoc.Parse(xml$); if ( err < 0 ) { PopupError( err ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } ### get the dialog element from the parsed XML document and show error message if the dialog element can't be found psfdlgnode = dlgdoc.GetElementByID("psf"); if ( psfdlgnode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } #### Set the XML dialog element as the source for the GUI_DLG class instance we are using for the dialog window. psfdialog.SetXMLNode(psfdlgnode); err = psfdialog.DoModal(); if ( err == -1 ) { # exit script if Cancel button on dialog is pressed Exit(); } ###################################################################################################### ### Set up STAGE_ARRAY of threee stages for compositing the low-res bands. Populate later either with ### source or with filter-lookup stage if contrast enhancement is to be applied for that color class IMAGE_PIPELINE_STAGE_ARRAY stagesForComposite(3); ####################################################### ### apply contrast tables to Low Res bands if requested ## contrast for Red if ( data.GetValueStr( "conred" ) == "true" ) { print("Setting up Red contrast."); class CONTRAST conRed; err = conRed.Read(Red); if ( err < 0 ) # no contrast table found { computeCon(conRed, Red); # compute standard normalized contrast print("No contrast table found for Red, computing normalized contrast."); } # PIPELINE FILTER_LOOKUP to apply lookup table (from contrast) to source class IMAGE_PIPELINE_FILTER_LOOKUP lookupRed (source_Red); err = lookupRed.SetContrastParm(conRed); # set CONTRAST class instance as lookup table if ( err < 0) ReportError(_context.CurrentLineNum, err); err = lookupRed.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Initialized Red contrast"); stagesForComposite.SetItem(0, lookupRed); # set red lookup filter as first item in stage array (0-based indexing) } else { stagesForComposite.SetItem(0, source_Red); # set red source as first item in stage array print("Using uncontrasted Red source."); } # contrast for Green if ( data.GetValueStr( "congrn" ) == "true" ) { print("Setting up Green contrast."); class CONTRAST conGreen; err = conGreen.Read(Green); if ( err < 0 ) # no contrast table found { computeCon(conGreen, Green); # compute standard normalized contrast print("No contrast table found for Green, computing normalized contrast."); } # PIPELINE FILTER_LOOKUP to apply lookup table (from contrast) to source class IMAGE_PIPELINE_FILTER_LOOKUP lookupGreen (source_Green); err = lookupGreen.SetContrastParm(conGreen); # set CONTRAST class instance as lookup table if ( err < 0) ReportError(_context.CurrentLineNum, err); err = lookupGreen.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Initialized Green contrast"); stagesForComposite.SetItem(1, lookupGreen); # set green lookup filter as second item in stage array } else { stagesForComposite.SetItem(1, source_Green); # set green source as second item in stage array print("Using uncontrasted Green source."); } # contrast for Blue if ( data.GetValueStr( "conblue" ) == "true" ) { print("Setting up Blue contrast."); class CONTRAST conBlue; err = conBlue.Read(Blue); if ( err < 0 ) # no contrast table found { computeCon(conBlue, Blue); # compute standard normalized contrast print("No contrast table found for Blue, computing normalized contrast."); } # PIPELINE FILTER_LOOKUP to apply lookup table (from contrast) to source class IMAGE_PIPELINE_FILTER_LOOKUP lookupBlue (source_Blue); err = lookupBlue.SetContrastParm(conBlue); # set CONTRAST class instance as lookup table if ( err < 0) ReportError(_context.CurrentLineNum, err); err = lookupBlue.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Initialized Blue contrast."); stagesForComposite.SetItem(2, lookupBlue); # set blue lookup filter as third item in stage array } else { stagesForComposite.SetItem(2, source_Blue); # set blue source as third item in stage array print("Using uncontrasted Blue source."); } # PIPELINE FILTER_COMPOSITE to make composite from Low Res RGB bands (required for Fusion filter) # Filter can make different composites from different numbers of components, so it takes a STAGE_ARRAY of # input stages set up previously (populated in this case by sources or derived FILTER_LOOKUP stages) class IMAGE_PIPELINE_FILTER_COMPOSITE filter_comp(stagesForComposite, "RGB"); err = filter_comp.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Composite filter initialized."); # PIPELINE FILTER_RESAMPLE to resample composite Low Res RGB image to match the extents and georeference # of the Hi Res panchromatic image. class IMAGE_PIPELINE_FILTER_RESAMPLE filter_resample(filter_comp, source_Pan, "Nearest"); err = filter_resample.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Resample filter initialized."); ############################################################ # Apply contrast to Hi Res panchromatic image if requested # and set up fusion filter with either pan source or its # derived filter_lookup ############################################################ class IMAGE_PIPELINE_STAGE HiRes; # stage for Hi Res pan image, either original or contrast-enhanced if ( data.GetValueStr( "conpan" ) == "true" ) # apply contrast selected { print("Setting up Pan contrast."); class CONTRAST conPan; err = conPan.Read(Pan); if ( err < 0 ) # no contrast table found { computeCon(conPan, Pan); # compute standard normalized contrast print("No contrast table found for Pan, computing normalized contrast."); } # PIPELINE FILTER_LOOKUP to apply lookup table (from contrast) to source class IMAGE_PIPELINE_FILTER_LOOKUP lookupPan (source_Pan); err = lookupPan.SetContrastParm(conPan); # set CONTRAST class instance as lookup table if ( err < 0) ReportError(_context.CurrentLineNum, err); err = lookupPan.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Initialized Pan contrast."); HiRes = lookupPan; # assign FILTER_LOOKUP to HiRes stage } else # no apply contrast, use original pan image source { HiRes = source_Pan; # assign SOURCE_RVC to HiRes stage print("Using uncontrasted Pan source."); } ########################################################################## # PIPELINE FILTER_FUSION_XXX # Set up appropriate fusion filter for the selected color blending method ########################################################################## class IMAGE_PIPELINE_FILTER filter_fusion; # generic class instance for the fusion filter; # assign particular fusion filter to this name if (method$ == "Brovey") { class IMAGE_PIPELINE_FILTER_FUSION_BROVEY filter_Brovey(HiRes, filter_resample); filter_fusion = filter_Brovey; print("Using Brovey method."); } # need panSat and rgbSat values for remaining methods else if (method$ == "basicHIS") { class IMAGE_PIPELINE_FILTER_FUSION_BASICHIS filter_basicHIS(HiRes, filter_resample, data.GetValueNum("panSat"), data.GetValueNum("rgbSat") ); filter_fusion = filter_basicHIS; print("Using Basic HIS method."); } else if (method$ == "modHIS") { class IMAGE_PIPELINE_FILTER_FUSION_MODHIS filter_modHIS(HiRes, filter_resample, data.GetValueNum("panSat"), data.GetValueNum("rgbSat") ); filter_modHIS.SetIntensityProportion(0.5); filter_modHIS.SetSaturationBoost(1.3); filter_modHIS.SetSaturationMax(100); filter_fusion = filter_modHIS; print("Using Modified HIS method."); } else if (method$ == "HBS") { class IMAGE_PIPELINE_FILTER_FUSION_BASICHBS filter_HBS(HiRes, filter_resample, data.GetValueNum("panSat"), data.GetValueNum("rgbSat") ); filter_fusion = filter_HBS; print("Using HBS method."); } err = filter_fusion.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Initialized fusion filter."); ########################################################################## # PIPELINE TARGET # Prompt for output and set up appropriate pipeline target based on selected format ########################################################################## class IMAGE_PIPELINE_TARGET target; # generic target; assign selected target class to this variable #format$ = data.GetValueStr("format"); class STRING defaultName$ = _context.ScriptDir + "/image"; if (format$ == "RVC") { class RVC_OBJITEM outObjItem; DlgGetObject("Choose new raster for the pan-sharpened composite:", "Raster", outObjItem, "NewOnly"); class IMAGE_PIPELINE_TARGET_RVC target_RVC(filter_fusion, outObjItem); target = target_RVC; } else if (format$ == "GeoTIFF") { class STRING outFileName$ = GetOutputFileName(defaultName$, "Choose destination and name for output GeoTIFF file", ".tif"); class FILEPATH outFilePath(outFileName$); printf("Output file path = %s\n", outFilePath); class IMAGE_PIPELINE_TARGET_TIFF_SETTINGS tiffSettings; tiffSettings.SetCompression("LZW"); class IMAGE_PIPELINE_TARGET_TIFF target_TIFF(filter_fusion, outFilePath, data.GetValueStr("geoformat") ); target_TIFF.SetParms(tiffSettings); target = target_TIFF; } else if (format$ == "PNG") { class STRING outFileName$ = GetOutputFileName(defaultName$, "Choose destination and name for output PNG file", ".png"); class FILEPATH outFilePath(outFileName$); printf("Output file path = %s\n", outFilePath); class IMAGE_PIPELINE_TARGET_PNG target_PNG(filter_fusion, outFilePath, data.GetValueStr("geoformat") ); target = target_PNG; } else if (format$ == "JP2" or format$ == "JP2l") { class STRING outFileName$ = GetOutputFileName(defaultName$, "Choose destination and name for output JP2 file", ".jp2"); class FILEPATH outFilePath(outFileName$); printf("Output file path = %s\n", outFilePath); class IMAGE_PIPELINE_TARGET_J2K_SETTINGS j2kSettings; j2kSettings.SetGeoMethod("GeoTIFF"); if (format$ == "JP2") { j2kSettings.SetReversible(1); print("Setting JP2 lossless compression."); } else { j2kSettings.SetTargetRatio(data.GetValueNum("ratio") ); printf("Setting JP2 lossy compression with target ratio %d\n", data.GetValueNum("ratio") ); } class IMAGE_PIPELINE_TARGET_J2K target_J2K(filter_fusion, outFilePath, j2kSettings, data.GetValueStr("geoformat") ); target = target_J2K; } else if (format$ == "JPEG") { class STRING outFileName$ = GetOutputFileName(defaultName$, "Choose destination and name for output JPEG file", ".jpg"); class FILEPATH outFilePath(outFileName$); printf("Output file path = %s\n", outFilePath); class IMAGE_PIPELINE_TARGET_JPEG target_JPEG(filter_fusion, outFilePath, data.GetValueNum("ratio"), data.GetValueStr("geoformat") ); target = target_JPEG; } target.Initialize(); if ( err < 0) ReportError(_context.CurrentLineNum, err); else print("Initialized target."); target.Process(); print("Processing..."); print("Done.");