#!/usr/bin/env python3
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Copyright 2010 California Institute of Technology. ALL RIGHTS RESERVED.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# United States Government Sponsorship acknowledged. This software is subject to
# U.S. export control laws and regulations and has been classified as 'EAR99 NLR'
# (No [Export] License Required except when exporting to an embargoed country,
# end user, or in support of a prohibited end use). By downloading this software,
# the user agrees to comply with all applicable U.S. export laws and regulations.
# The user has the responsibility to obtain export licenses, or other export
# authority as may be required before exporting this software to any 'EAR99'
# embargoed foreign country or citizen of those countries.
#
# Author: Giangi Sacco
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import print_function
import os
import sys
import isce
from iscesys.Parsers.FileParserFactory import createFileParser
from iscesys.ImageApi import DataAccessor as DA
from isceobj.Util import key_of_same_content
import math
class Display(object):
def getRscExt(self,ext):
ret = ''
for k in self._mapDataType['rsc'].keys():
if self._ext[k].count(ext):
ret = k
break
return ret
def setIfNotPresent(self,opList,option,default):
# check if option is present in the opList
# and if not add it with default value
if not option in opList:
opList.append(option)
opList.append(default)
def getIfPresent(self,opList,option):
# check if option is present in the opList
# and return the value. Return None if not present
ret = None
try:
indx = opList.index(option)
ret = opList[indx+1]
#remove the option
opList.pop(indx)
#remove the value. same indx since just popped one
opList.pop(indx)
except:
# the option is not present
pass
return ret
def createCommand(self,options):
ext = options['ext']
dataType = options['dataType']
image = options['image']
width = options['width']
# numBands and length only used for isce products, not roi_pac
numBands = options['numBands'] if 'numBands' in options else 1
length = options['length'] if 'length' in options else 0
argv = options['other']
command = ''
if ext in self._ext['cpx'] or ext in self._ext['scor'] or ext in self._ext['byt']:
command = image + ' ' + dataType + ' -s ' + str(width) + ' ' + ' '.join(argv)
elif ext in self._ext['rmg']:
command = image + ' -s ' + str(width) + ' ' + ' -rmg -RMG-Mag -CW -RMG-Hgt ' + ' '.join(argv)
elif ext in self._ext['unw']:
tpi=str(2.*math.pi)
self.setIfNotPresent(argv,'-wrap',tpi)
command = image + ' -s ' + str(width) + ' -amp ' + dataType + ' -rtlr ' + str(width*int(dataType[2:])) + ' -CW -unw ' + dataType + ' -rhdr ' + str(width*int(dataType[2:])) + ' -cmap cmy ' + ' '.join(argv)
elif ext in self._ext['cor']:
self.setIfNotPresent(argv,'-wrap','1.2')
if numBands == 2:
command = image + ' -s ' + str(width) + ' -rmg -RMG-Mag -CW -RMG-Hgt ' + ' '.join(argv)
command = image + ' -s ' + str(width) + ' -rmg -RMG-Mag -CW -RMG-Hgt ' + ' '.join(argv)
elif numBands == 1:
command = image + ' -s ' + str(width) + ' -cmap cmy ' + ' '.join(argv)
elif ext in self._ext['dem']:
self.setIfNotPresent(argv,'-wrap','100')
self.setIfNotPresent(argv,'-cmap','cmy')
command = image + ' -slope ' + dataType + ' -s ' + str(width) + ' ' + image + ' ' + dataType +' -s ' + str(width) + ' ' + ' '.join(argv)
elif ext in self._ext['amp']:
#get the numeric part of the data type which corresponds to the size
chdr = dataType[2:]
ctlr = dataType[2:]
newChdr = self.getIfPresent(argv,'-chdr')
if not newChdr is None:
chdr = newChdr
newCtlr = self.getIfPresent(argv,'-ctlr')
if not newCtlr is None:
ctlr = newCtlr
command = image + ' -s ' + str(width) + ' -CW ' +' -amp1 ' + dataType + ' -ctlr ' + ctlr + ' -amp2 ' + dataType + ' -chdr ' + chdr + ' ' + ' '.join(argv)
elif ext in self._ext['bil']:
sizeof = self.getDataSize(dataType)
command = image + ' -s ' + str(width)
for i in range(1,numBands + 1):#do it one based
rhdr = (i - 1)*width*sizeof
rtlr = (numBands - i)*width*sizeof
command += ' -ch' + str(i) + ' ' + dataType
command += ((' -rhdr ' + str(rhdr)) if rhdr else '') + ' '
command += ((' -rtlr ' + str(rtlr)) if rtlr else '') + ' '
command += ' '.join(argv)
elif ext in self._ext['bip']:
sizeof = self.getDataSize(dataType)
command = image + ' -s ' + str(width)
for i in range(1,numBands + 1):#do it one based
chdr = (i - 1)*sizeof
ctlr = (numBands - i)*sizeof
command += ' -ch' + str(i) + ' ' + dataType
command += ((' -chdr ' + str(chdr)) if chdr else '') + ' '
command += ((' -ctlr ' + str(ctlr)) if ctlr else '') + ' '
command += ' '.join(argv)
elif ext in self._ext['bsq']:
sizeof = self.getDataSize(dataType)
command = image + ' -s ' + str(width)
for i in range(1,numBands + 1):#do it one based
shdr = (i - 1)*width*length*sizeof
stlr = (numBands - i)*width*length*sizeof
command += ' -ch' + str(i) + ' ' + dataType
command += ((' -shdr ' + str(shdr)) if shdr else '') + ' '
command += ((' -stlr ' + str(stlr)) if stlr else '') + ' '
command += ' '.join(argv)
return command
def parse(self,argv):
ret = {}
upTo = 0
#get global options '-z val' and '-kml'
#the first option could possibly be the -z applied globally
try:
indx = argv.index('-z')
ret['-z'] = argv[indx+1]
# remove the -z val from list.
argv.pop(indx)
# same indx since popped the previous
argv.pop(indx)
except:
#not present
pass
try:
indx = argv.index('-P')
ret['-P'] = '-P'
# remove the -P from list.
argv.pop(indx)
except:
#not present
pass
try:
indx = argv.index('-kml')
ret['-kml'] = argv[indx+1]
# remove the -kml and val from list.
argv.pop(indx)
argv.pop(indx)
except:
#not present
pass
# the reamining part of the command has to be
# file -op val -op val file -op val -op val ....
# so the different file options are recognized with two argv with
# no dash are present (first is a val and second an image)
imgOpt = []
parOpt = []
pos = 0
while(True):
if(pos >= len(argv)):
imgOpt.append(parOpt)
break
# is an option
if argv[pos].startswith('-'):
parOpt.append(argv[pos])
pos += 1
parOpt.append(argv[pos])
# is a metadata file
else:
# is the first time, just add the image
if not parOpt:
parOpt.append(argv[pos])
# else save what was there before, clear and add the new image
else:
imgOpt.append(parOpt)
parOpt = [argv[pos]]
pos += 1
ret['imageArgs'] = imgOpt
return ret
def getMetaFile(self,image):
metafile = None
for ext in self._metaExtensions:
if os.path.exists(image + ext):
metafile = image + ext
break
if metafile is None:
print("Error. Cannot find any metadata file associated with the image",image)
raise ValueError
return metafile
def getInfo(self,image):
metafile = self.getMetaFile(image)
ret = None
if(metafile.endswith('xml')):
ret = self.getInfoFromXml(metafile,image)
elif(metafile.endswith('rsc')):
ret = self.getInfoFromRsc(metafile,image)
else:
print("Error. Metadata file must have extension 'rsc' or 'xml'")
raise ValueError
return ret
def getInfoFromXml(self,imagexml,image):
""" Determines image name, width, image type and data type from input xml"""
# first is alway the xml file
ext = None
dataType = None
width = None
length = None
PA = createFileParser('xml')
dictNow, dictFact, dictMisc = PA.parse(imagexml) #get only the property dictionary
numBands = 0
numBands = key_of_same_content('number_bands', dictNow)[1]
dataTypeImage = key_of_same_content('data_type', dictNow)[1]
dataType = self._mapDataType['xml'][dataTypeImage]
try:#new format of image
coordinate1 = key_of_same_content('coordinate1',dictNow)[1]
width = key_of_same_content('size',coordinate1)[1]
coordinate2 = key_of_same_content('coordinate2',dictNow)[1]
length = key_of_same_content('size',coordinate2)[1]
try:#only for geo image to create kml
self._width.append(float(width))
self._startLon.append(float(key_of_same_content('startingValue',coordinate1)[1]))
self._deltaLon.append(float(key_of_same_content('delta',coordinate1)[1]))
coordinate2 = key_of_same_content('coordinate2',dictNow)[1]
self._length.append(float(key_of_same_content('size',coordinate2)[1]))
self._startLat.append(float(key_of_same_content('startingValue',coordinate2)[1]))
self._deltaLat.append(float(key_of_same_content('delta',coordinate2)[1]))
self._names.append(imagexml.replace('.xml',''))
except Exception as e:
pass # not a geo image
except:# use old format
try:
width = key_of_same_content('width',dictNow)[1]
except:
print("Error. Cannot figure out width from input file.")
raise Exception
ext = self.getIsceExt(dictNow,image)
if ext is None or dataType is None or width is None:#nothing worked. Through exception caught next
print("Error. Cannot figure out extension from input file.")
raise Exception
return {'image':image,'ext':ext,'width':width,'length':length,'dataType':dataType,'numBands':numBands}
def isExt(self,ext):
found = False
for k,v in self._ext.items():
if ext in v:
found = True
break
return found
#try few things to get the right extension
def getIsceExt(self,info,imagename):
ext = None
# try to see if the image has the property imageType
try:
ext = key_of_same_content('image_type',info)[1]
#if it is not a valid extension try something else
if(not self.isExt(ext)):
raise Exception
except:
# if not try to get the ext from the filename
try:
nameSplit = imagename.split('.')
if len(nameSplit) > 1:#there was atleast one dot in the name
ext = nameSplit[-1]
if(not self.isExt(ext)):
raise Exception
except:
#try to use the scheme
try:
scheme = key_of_same_content('scheme',info)[1]
ext = scheme.lower()
if(not self.isExt(ext)):
raise Exception
except:
ext = None
return ext
def getInfoFromRsc(self,imagersc,image):
""" Determines image name, width, image type and data type from input rsc"""
try:
PA = createFileParser('rsc')
dictOut = PA.parse(imagersc)
#dictOut has a top node that is just a name
dictNow = dictOut[list(dictOut.keys())[0]]
if 'WIDTH' in dictNow:
width = int(dictNow['WIDTH'])
try:
if 'LAT_REF1' in dictNow:
#extract the geo info
self._width.append(float(width))
self._startLon.append(float(dictNow['X_FIRST']))
self._deltaLon.append(float(dictNow['X_STEP']))
self._length.append(float(dictNow['FILE_LENGTH']))
self._startLat.append(float(dictNow['Y_FIRST']))
self._deltaLat.append(float(dictNow['Y_STEP']))
self._names.append(image)
except:
pass#not a geo file
except:
print("Error. Cannot figure out width from input file.")
raise Exception
# assume imagersc = 'name.ext.rsc'
try:
ext = imagersc.split('.')[-2]
except:
print("Error. Cannot figure out extension from input file.")
raise Exception
found = False
for k,v in self._ext.items():
if ext in v:
found = True
break
if not found:
print("Error. Invalid image extension",ext,".")
self.printExtensions()
raise Exception
extNow = self.getRscExt(ext)
dataType = self._mapDataType['rsc'][extNow]
return {'image':image,'ext':ext,'width':width,'dataType':dataType}
def getCommand(self,options):
#if creating kml then commands is a list of singles mdx commands, one per input image with -P option
#otherwise is a string made of a unique command for all the images at once
commands = ''
command = 'mdx'
if '-z' in options:
command += ' -z ' + options['-z']
if ('-kml' in options or '-P' in options):
command += ' -P '
commands = []
#build command for each single image
for listOp in options['imageArgs']:
#first arg is the metadata file. opDict contains image,ext,width,dataType
opDict = self.getInfo(listOp[0])
if not (opDict is None):
try:
# if any extra command put it into other
opDict['other'] = listOp[1:]
except:
# only had the metadata in listOp
pass
if not '-kml' in options:
command += ' ' + self.createCommand(opDict)
else:
commands.append(command + ' ' + self.createCommand(opDict))
if not '-kml' in options:
commands = command
return commands
def printExtensions(self):
#perhaps turn it into a dictionary with key = extension and value = description
print("Supported extensions:")
for k,v in self._ext.items():
for ext in v:
print(ext)
def printUsage(self):
print(" Usage:\n")
print(" mdx.py filename [-wrap wrap] ... [-z zoom -kml output.kml]\n")
print(" where\n")
for mess in self._docIn:
print(mess)
print('\n or\n')
print(" mdx.py -ext\n")
print(" to see the supported image extensions.\n\n")
def mdx(self, argv=None):
if argv is None:
self.printUsage()
else:
if len(argv) == 1 and argv[0] == '-ext':
self.printExtensions()
elif len(argv) == 0:
self.printUsage()
else:
#argv is modified in parse and -kml is removed so check before parsing
doKml = self.isKml(argv)
options = self.parse(argv)
command = self.getCommand(options)
if not doKml:
print("Running:",command)
os.system(command)
else:
#options['-kml'] is the output filename passed as input arg
self.createKml(options['-kml'],command)
def createKml(self,name,commands):
fp = open(name,'w')
fp.write('\n\
\n\n')
#mdx creates a out.ppm file in the cwd
ppm = 'out.ppm'
cwd = os.getcwd()
for i in range(len(self._startLat)):
os.system(commands[i])
lat1 = self._startLat[i]
lat2 = self._startLat[i] + self._deltaLat[i]*(self._length[i] - 1)
lon1 = self._startLon[i]
lon2 = self._startLon[i] + self._deltaLon[i]*(self._width[i] - 1)
maxLon = max(lon1,lon2)
minLon = min(lon1,lon2)
maxLat = max(lat1,lat2)
minLat = min(lat1,lat2)
icon = os.path.join(cwd,os.path.basename(self._names[i])) + '.png'
command = 'convert ' + ppm + ' -resize 80% -transparent black' + ' ' + icon
os.system(command)
os.remove(ppm)
self.appendToKmlFile(fp,os.path.basename(self._names[i]),icon,[maxLat,minLat,maxLon,minLon])
fp.write('\n\n')
fp.close()
def appendToKmlFile(self,fp,name,icon,bbox):
fp.write('\t\n')
fp.write('\t\t%s\n'%name)
fp.write('\t\tafffffff\n')
fp.write('\t\t1\n')
fp.write('\t\t\n')
fp.write('\t\t\t%s\n'%icon)
fp.write('\t\t\n')
fp.write('\t\t\n')
fp.write('\t\t\t%f\n'%bbox[0])
fp.write('\t\t\t%f\n'%bbox[1])
fp.write('\t\t\t%f\n'%bbox[2])
fp.write('\t\t\t%f\n'%bbox[3])
fp.write('\t\t\n')
fp.write('\t\n')
def isKml(self,argv):
try:
argv.index('-kml')
ret = True
except:
ret = False
return ret
def getDataSize(self,dataType):
try:
size = int(dataType[2:])
except:
size = 0
return size
def __init__(self):
size = DA.getTypeSize('LONG') #the size depends on the platform. the ImageAPI does e sizeof(long int) and returns the size
#NOTE the unw doent't need a datatype so put ''
self._mapDataType = {'xml':{'BYTE':'-i1','SHORT':'-i2','CFLOAT':'-c8','FLOAT':'-r4','INT':'-i4','LONG':'-i'+ str(size),'DOUBLE':'-r8'},'rsc':{'cpx':'-c8','rmg':'-r4','scor':'-r4','dem':'-i2','byt':'-i1','amp':'-r4','unw':'-r4','cor':''}}
self._docIn = [
' mdx.py : displays one or more data files simultaneously by ',
' specifying their names as input. The maximun number,'
' of images that can be displayed depends on the machine',
' architecture and mdx limits. If displayed (no -kml flag)',
' the images don\'t need to have the same extension, but need',
' to have same width.',
' ',
' filename: input file containing the image metadata.',
' Metadata files must be of format filename.{xml,rsc}',
' and must be present in the same directory as filename.',
' Different formats (xml,rsc) can be mixed.',
' ',
' -wrap : sets display scaling to wrap mode with a modules of Pi.',
' It must follow the filename to which the wrap is applied.',
' ',
' ... : the command can be repeated for different images.',
' ',
' -z : zoom factor (+ or -) to apply to all layers. It\'s optional',
' and can appear anywhere in the command sequence and must',
' appear only once.',
' ',
' -kml : only for geocoded images it creates a klm file with all the',
' input images overlaid. Each layer can be turn on or off in ',
' Goolge Earth. It\'s optional and can appear anywhere in the ',
' command sequence and must appear only once. The images don\'t ',
' need to be co-registed.',
' ',
' Examples:',
' mdx.py 01_02.int # Standard way to run mdx.py',
' mdx.py -P 01_02.int # Create a ppm image named out.ppm',
' mdx.py 03_04.int 05_06.int -z -8 # Display two images; zoom out by 8',
' mdx.py 03_04.geo -z 8 05_06.geo -kml fileout.klm # Create a kml file named fileout.kml with two',
' # layers, one per image. Both images are zoomed in',
' # by a factor of 8 ' ,
' mdx.py 03_04.int 05_06.int -wrap 6.28 # Display two images. Wrap the second modulo 2Pi',
' ']
# the input file is the image itself. Search for the same filename
# and one of the extensions below to figure out the metadata type
self._metaExtensions = ['.xml','.rsc']
self._ext = {}
self._ext['cpx'] = ['slc','int','flat','mph','cpx']
self._ext['rmg'] = ['hgt','hgt_holes','rect','rmg']
self._ext['scor'] = ['scor']
self._ext['dem'] = ['dem','dte','dtm']
self._ext['unw'] = ['unw']
self._ext['cor'] = ['cor']
self._ext['byt'] = ['byt','flg']
self._ext['amp'] = ['amp']
self._ext['bil'] = ['bil']
self._ext['bip'] = ['bip']
self._ext['bsq'] = ['bsq']
# save this quantities in the case we are dealing with a geo image
self._startLat = []
self._deltaLat = []
self._startLon = []
self._deltaLon = []
self._length = []
self._width = []
self._names = []
def main():
# just for testing purposes
ds = Display()
""" #test parser
print(ds.parse(sys.argv[1:]))
"""
""" test info extractor from xml
print(ds.getInfoFromXml(sys.argv[1]))
"""
""" test info extractor from rsc
print(ds.getInfoFromRsc(sys.argv[1]))
"""
ds.mdx(sys.argv[1:])
if __name__ == '__main__':
sys.exit(main())