Commit 04ef5566 authored by Lozac'h Loic's avatar Lozac'h Loic
Browse files

Merge branch 'develop'

Conflicts:
	python/SoilMoistureBatchExtractAndMosaic2S2Tile.py
	python/SoilMoisturePipeline.py
parents 851b1c59 bdfa638e
......@@ -11,7 +11,7 @@
<provider copy-of="extension" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser"/>
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="-598394472140321343" id="org.eclipse.cdt.managedbuilder.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="941863880402804919" id="org.eclipse.cdt.managedbuilder.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
......
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>soilmoisturepython</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/${PROJECT_DIR_NAME}</path>
</pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
</pydev_project>
......@@ -77,7 +77,6 @@ def exmos_pipeline(argsformat, mos1, mos2, ref, out):
app0.SetParameterString("interpolator","nn")
app0.SetParameterString("inr",ref)
app0.Execute()
print("debug app0")
app1 = otbApplication.Registry.CreateApplication("Superimpose")
app1.SetParameterString("inm",mos2)
......@@ -153,11 +152,9 @@ if __name__ == "__main__":
if len(l) == 2 :
dejafait.append(indir[l[0]])
dejafait.append(indir[l[1]])
in1 = os.path.join(indir[l[0]], "Sigma0_VV.img")
in2 = os.path.join(indir[l[1]], "Sigma0_VV.img")
outfile = outfilebase +"_VV.TIF"
if os.path.exists(outfile) :
......@@ -167,10 +164,14 @@ if __name__ == "__main__":
in1 = os.path.join(indir[l[0]], "incidenceAngleFromEllipsoid.img")
in2 = os.path.join(indir[l[1]], "incidenceAngleFromEllipsoid.img")
outfile = outfilebase +"_THETA.TIF"
exmos_pipeline(args.format, in1, in2, args.inref, outfile)
dejafait.append(indir[l[0]])
dejafait.append(indir[l[1]])
elif len(l) == 1 :
in1 = os.path.join(file, "Sigma0_VV.img")
outfile = outfilebase +"_VV.TIF"
......
......@@ -6,13 +6,13 @@
__author__ = "Loic Lozach"
__date__ = "$Dec 14, 2018 12:40:21 PM$"
import os, argparse, subprocess, math
import os, argparse, subprocess, math, datetime,glob
import otbApplication
from osgeo import gdal, osr, ogr
from subprocess import Popen, PIPE
maskedlabels, labels, vectorlabels, labelsfield, lulc, agrivalues, ndvi, sarvv, \
slope, slopevalue, sarth, modeldir, output, outformat, sarmode = "","","","","","","","","","","","","","",""
slope, slopevalue, sarth, modeldir, modelchoice, output, outformat, sarmode = "","","","","","","","","","","","","","","",""
def search_files(directory='.', resolution='NDVI', extension='tif', fictype='f'):
images=[]
......@@ -37,7 +37,7 @@ def search_files(directory='.', resolution='NDVI', extension='tif', fictype='f')
images.append(abspath)
else:
print("search_files type error")
exit
exit()
return images
......@@ -151,6 +151,242 @@ def normalize_proj_and_extend():
return intersectionEnv
def get_meteodates_from_sardate(sardate):
exformat = "%Y%m%d"
saryear,sarmonth,sarday = sardate[:4],sardate[4:6],sardate[6:8]
fsardate = datetime.date(int(saryear),int(sarmonth),int(sarday))
oneday = datetime.timedelta(days=1)
sardatem1 = (fsardate - oneday).strftime(exformat)
sardatem2 = (fsardate - oneday - oneday).strftime(exformat)
return [sardate,sardatem1,sardatem2]
def download_gpm(sarlist, sartype, gpmdir):
datepos=None
if sartype == "snap":
print("#date position")
datepos=4
elif sartype == "s2tile":
datepos=4
else:
print("Error on sartype!")
exit()
if not os.path.exists(gpmdir):
os.mkdir(gpmdir)
cmd = "wget --user=loic.lozach@irstea.fr --password=loic.lozach@irstea.fr \
ftp://arthurhou.pps.eosdis.nasa.gov/gpmdata/"
arg0 = "wget"
arg1 = "--user=loic.lozach@irstea.fr"
arg2 = "--password=loic.lozach@irstea.fr"
arg3 = "ftp://arthurhou.pps.eosdis.nasa.gov/gpmdata/"
for sar in sarlist:
sardata = os.path.basename(sar)
sarsplit = sardata.split('_')
sardate = sarsplit[datepos][:8]
sar3dates = get_meteodates_from_sardate(sardate)
tifexits = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[0]+"-S000000-E235959*.tif"))
if len(tifexits) >= 1 :
continue
tifexitsm1 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[1]+"-S000000-E235959*.tif"))
if len(tifexitsm1) >= 1 :
continue
tifexitsm2 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[2]+"-S000000-E235959*.tif"))
if len(tifexitsm2) >= 1 :
continue
gpmtif = sar3dates[0][:4] +"/"+ sar3dates[0][4:6] +"/"+ sar3dates[0][6:8] +"/gis/"+ "3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[0]+"-S000000-E235959*.t*"
gpmtifm1 = sar3dates[1][:4] +"/"+ sar3dates[1][4:6] +"/"+ sar3dates[1][6:8] +"/gis/"+ "3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[1]+"-S000000-E235959*.t*"
gpmtifm2 = sar3dates[2][:4] +"/"+ sar3dates[2][4:6] +"/"+ sar3dates[2][6:8] +"/gis/"+ "3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[2]+"-S000000-E235959*.t*"
cmdlist=[]
cmdlist.append([arg0,arg1,arg2,arg3 + gpmtif])
cmdlist.append([arg0,arg1,arg2,arg3 + gpmtifm1])
cmdlist.append([arg0,arg1,arg2,arg3 + gpmtifm2])
for c in cmdlist:
p = Popen(c, cwd=gpmdir, stdout=PIPE)
output = p.communicate()[0]
if p.returncode != 0:
print("wget failed %d : %s" % (p.returncode, output))
def process_gpm(sar, sar3dates, gpmdir):
outdir = os.path.join( os.path.dirname(gpmdir),"MaskWetOrDry")
outfile = os.path.join(outdir,"MaskWetOrDry"+args.zone+"_"+sar3dates[0]+".tif")
if os.path.exists(outfile):
print("Already exists : "+outfile)
return
if not os.path.exists(outdir):
os.mkdir(outdir)
gpmfiles1 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[0]+"*.tif"))
gpmfiles2 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[1]+"*.tif"))
gpmfiles3 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[2]+"*.tif"))
if len(gpmfiles1) == 0 :
print("Error: Can't find gpm file "+"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[0]+"*.tif")
return 1
if len(gpmfiles2) == 0 :
print("Error: Can't find gpm file "+"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[1]+"*.tif")
return 1
if len(gpmfiles3) == 0 :
print("Error: Can't find gpm file "+"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[2]+"*.tif")
return 1
gpmfiles=[gpmfiles1[0],gpmfiles2[0],gpmfiles3[0]]
apps=[]
for f in gpmfiles :
app = otbApplication.Registry.CreateApplication("Superimpose")
app.SetParameterString("inm", f)
app.SetParameterString("inr", sar)
app.SetParameterString("interpolator","bco")
app.SetParameterString("out", "temp2.tif")
app.Execute() #ExecuteAndWriteOutput()
apps.append(app)
app0 = otbApplication.Registry.CreateApplication("BandMath")
app0.AddImageToParameterInputImageList("il", apps[0].GetParameterOutputImage("out"))
app0.AddImageToParameterInputImageList("il", apps[1].GetParameterOutputImage("out"))
app0.AddImageToParameterInputImageList("il", apps[2].GetParameterOutputImage("out"))
app0.SetParameterString("out", outfile)
app0.SetParameterOutputImagePixelType("out", otbApplication.ImagePixelType_uint8)
app0.SetParameterString("exp", "im1b1+im2b1+im3b1>5?2:1")
app0.ExecuteAndWriteOutput()
def process_gpm_noresampling(sar, sar3dates, gpmdir):
outdir = os.path.join( os.path.dirname(gpmdir),"CumulPluie")
outfile = os.path.join(outdir,"CumulPluie_"+args.zone+"_"+sar3dates[0]+".tif")
if os.path.exists(outfile):
print("Already exists : "+outfile)
return
if not os.path.exists(outdir):
os.mkdir(outdir)
gpmfiles1 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[0]+"*.tif"))
gpmfiles2 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[1]+"*.tif"))
gpmfiles3 = glob.glob(os.path.join(gpmdir,"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[2]+"*.tif"))
if len(gpmfiles1) == 0 :
print("Error: Can't find gpm file "+"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[0]+"*.tif")
return 1
if len(gpmfiles2) == 0 :
print("Error: Can't find gpm file "+"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[1]+"*.tif")
return 1
if len(gpmfiles3) == 0 :
print("Error: Can't find gpm file "+"3B-DAY-GIS.MS.MRG.3IMERG."+sar3dates[2]+"*.tif")
return 1
gpmfiles=[gpmfiles1[0],gpmfiles2[0],gpmfiles3[0]]
apps=[]
for f in gpmfiles :
app = otbApplication.Registry.CreateApplication("ExtractROI")
app.SetParameterString("in", f)
app.SetParameterString("mode.fit.im", sar)
app.SetParameterString("mode","fit")
app.SetParameterString("out", "temp2.tif")
app.Execute() #ExecuteAndWriteOutput()
apps.append(app)
app0 = otbApplication.Registry.CreateApplication("BandMath")
app0.AddImageToParameterInputImageList("il", apps[0].GetParameterOutputImage("out"))
app0.AddImageToParameterInputImageList("il", apps[1].GetParameterOutputImage("out"))
app0.AddImageToParameterInputImageList("il", apps[2].GetParameterOutputImage("out"))
app0.SetParameterString("out", outfile)
app0.SetParameterString("exp", "im1b1+im2b1+im3b1")
app0.SetParameterOutputImagePixelType("out", otbApplication.ImagePixelType_uint16)
app0.ExecuteAndWriteOutput()
# app1 = otbApplication.Registry.CreateApplication("Smoothing")
# app1.SetParameterInputImage("in", app0.GetParameterOutputImage("out"))
# app1.SetParameterString("type", "gaussian")
# app1.SetParameterString("out", outfile)
# app1.SetParameterOutputImagePixelType("out", otbApplication.ImagePixelType_uint16)
# app1.ExecuteAndWriteOutput()
def select_tfmodel_from_gpm(sardate,gpmdir):
cumul = glob.glob(os.path.join( os.path.join( os.path.dirname(gpmdir),"CumulPluie"), "CumulPluie_"+args.zone+"_"+sardate+".tif"))
if len(cumul) != 1 :
print("Error : Can't find file "+"CumulPluie_"+args.zone+"_"+sardate+".tif")
exit()
raster = gdal.Open(cumul[0])
srcband = raster.GetRasterBand(1)
stats = gdal.Band.GetStatistics(srcband,1,1)
if stats[2] > 5 :
return str(stats[2]),"wet"
else :
return str(stats[2]),"dry"
def wet_or_dry_pipeline(args):
sars=[]
datepos=None
if args.sardirtype == "snap":
sars=search_files(args.sardir, 'S1', 'data', 'd')
datepos=4
elif args.sardirtype == "s2tile":
datepos=4
if args.sarmode == "vv":
sars=search_files(args.sardir, 'S1', 'VV.TIF', 'f')
elif args.sarmode == "vh":
sars=search_files(args.sardir, 'S1', 'VH.TIF', 'f')
else:
print("Error: Exception on sarmode")
exit()
else:
print("ERROR: wrong sardirtype argument")
exit()
download_gpm(sars, args.sardirtype, args.gpmdir)
affectation=[]
with open(args.outtxt, 'w') as affout:
for sar in sars :
sardata = os.path.basename(sar)
sarsplit = sardata.split('_')
sardatetime = sarsplit[datepos]
sardate = sardatetime.split("T")[0]
sar3dates = get_meteodates_from_sardate(sardate)
if args.resampling :
process_gpm(sar, sar3dates, args.gpmdir)
else:
process_gpm_noresampling(sar, sar3dates, args.gpmdir)
res=select_tfmodel_from_gpm(sardate, args.gpmdir)
affout.write(sar+";"+res[0]+";"+res[1]+"\n")
def get_model4invertion():
sardate = os.path.basename(sarvv).split("_")[4].split("T")[0]
print("sardate:"+sardate)
result=None
with open(modelchoice) as mc:
for line in mc:
sline = line.split(";")
dline = os.path.basename(sline[0]).split("_")[4].split("T")[0]
if dline == sardate :
result=sline[2][:-1]
return result
if result == None:
print("Error : Can't find date in modelchoice file for sar :"+os.path.basename(sarvv))
exit()
def short_pipeline(intersectionEnv):
global slopevalue
......@@ -245,13 +481,17 @@ def short_pipeline(intersectionEnv):
appS.Execute()
print("End of Slope Filtering \n")
if not modelchoice == None :
choix = get_model4invertion()
modelchoix = os.path.join(modeldir,"SavedModel_"+choix)
else:
modelchoix = modeldir
# The following line creates an instance of the Superimpose application
app5 = otbApplication.Registry.CreateApplication("InvertSARModel")
app5.SetParameterInputImage("insarth", app2.GetParameterOutputImage("out") )
app5.SetParameterInputImage("inndvi", app3.GetParameterOutputImage("out")) #ndvisup)
app5.SetParameterInputImage("inlabels", app4.GetParameterOutputImage("out")) # labelssup)
app5.SetParameterString("model.dir", modeldir)
app5.SetParameterString("model.dir", modelchoix)
if not slope == None and len(slope) > 2:
app5.SetParameterInputImage("insarvv", appS.GetParameterOutputImage("out"))
......@@ -264,14 +504,14 @@ def short_pipeline(intersectionEnv):
if outformat == "csv" :
app5.SetParameterString("format", outformat)
app5.SetParameterString("format.csv.out", output)
print("Iverting model to csv with modeldir : "+ modeldir)
print("Iverting model to csv with modeldir : "+ modelchoix)
app5.ExecuteAndWriteOutput()
print("Done \n")
else:
app5.SetParameterString("format", outformat)
app5.SetParameterString("format.raster.out", "temp.tif")
print("Iverting model to raster with modeldir : "+ modeldir)
print("Iverting model to raster with modeldir : "+ modelchoix)
# The following line execute the application
app5.Execute()
print("End of Ivertion \n")
......@@ -396,13 +636,6 @@ def labels_vector_pipeline():
masked_pipeline()
def sar_series_pipeline():
#TO-DO
for sar in sarlist:
#get incidence
sarvv=sar
sarth=sar + "theta" ##TO-DO
# short_pipeline(maskedlabels)
def ndvistackandmask_pipeline(ndvis,lulc,agrivalues, out):
......@@ -517,7 +750,8 @@ if __name__ == "__main__":
# SAR Series pipeline
list_parser = subparsers.add_parser('serie', help="Batch inversion pipeline over a directory of Sentinel-1 images")
list_parser.add_argument('-modeldir', action='store', required=True, help='Directory to find tensorflow model (dry or wet)')
list_parser.add_argument('-modeldir', action='store', required=True, help='Directory to find both wet and dry tensorflow model')
list_parser.add_argument('-modelchoice', action='store', required=False, help='[Optional] Results text file from gpm pipeline')
list_parser.add_argument('-sardir', action='store', required=True, help='Directory to find Sentinel-1 images ')
list_parser.add_argument('-sardirtype', choices=['snap', 's2tile'], default='s2tile', required=False, help='[Optional] Choose between Sentinel-1 images processed by ESA-SNAP software ("Sigma0_VV.img" and "incidenceAngleFromEllipsoid.img" in .data directory) \
AND Sentinel-1 images processed by SoilMoistureBatchExtractAndMosaic2S2Tile.py')
......@@ -542,11 +776,22 @@ if __name__ == "__main__":
list_parser.add_argument('-srtmdir', action='store', required=True, help='Snap DEM SRTM 1Sec HGT directory containing zip file')
list_parser.add_argument('-inref', action='store', required=True, help='Image reference for DEM extraction in projected reference system')
list_parser.add_argument('-zone', action='store', required=True, help='Geographic Sentinel2 zone reference for output files naming')
list_parser.add_argument('-demtype', choices=['srtmhgt1sec', 'srtm3sec'], default='srtmhgt1sec', required=False, help='[Optional] Choose between SRTM HGT 1sec or SRMT 3sec, default srtmhgt1sec')
list_parser.add_argument('--no-deletedem', dest='deletedem', action='store_false', help='Avoid deleting DEM file used to produce slope (default True)')
list_parser.set_defaults(deletedem=True)
list_parser.add_argument('-outdir', action='store', required=True, help='Output directory for slope image')
# Select wet or dry model for inversion from GPM data
list_parser = subparsers.add_parser('gpm', help="Downloads, extracts and computes synthesis of NASA GPM raster for TF model selection (wet or dry)")
list_parser.add_argument('-sardir', action='store', required=True, help='Snap DEM SRTM 1Sec HGT directory containing zip file')
list_parser.add_argument('-sardirtype', choices=['snap', 's2tile'], default='s2tile', required=False, help='[Optional] Choose between Sentinel-1 images processed by ESA-SNAP software ("Sigma0_VV.img" and "incidenceAngleFromEllipsoid.img" in .data directory) \
AND Sentinel-1 images processed by SoilMoistureBatchExtractAndMosaic2S2Tile.py')
list_parser.add_argument('-sarmode', choices=['vv', 'vh'], default='vv', required=False, help='[Optional] Choose between VV or VH mode for input SAR image, default vv')
list_parser.add_argument('-gpmdir', action='store', required=True, help='Directory where GPM data will be downloaded')
list_parser.add_argument('-zone', action='store', required=True, help='Geographic zone reference for output files naming')
list_parser.add_argument('-outtxt', action='store', required=True, help='OUtput text file where each Sentinel-1 dates correspond their model dry or wet to use')
list_parser.add_argument('--no-resampling', dest='resampling', action='store_false', help='Resample GPM 100km to 10m')
list_parser.set_defaults(resampling=True)
args=parser.parse_args()
......@@ -618,14 +863,14 @@ if __name__ == "__main__":
elif args.pipeline == 'serie' :
if not os.path.isdir(args.sardir):
print("erreur "+args.sardir+" n'est pas un dossier")
exit
print("erreur "+args.sardir+" n'est pas un dossier")
exit()
if not os.path.isdir(args.ndvidir):
print("erreur "+args.ndvidir+" n'est pas un dossier")
exit
print("erreur "+args.ndvidir+" n'est pas un dossier")
exit()
if not os.path.isdir(args.outdir):
print("erreur "+args.outdir+" n'est pas un dossier")
exit
print("erreur "+args.outdir+" n'est pas un dossier")
exit()
sars=[]
if args.sardirtype == "snap":
......@@ -637,15 +882,16 @@ if __name__ == "__main__":
sars=search_files(args.sardir, 'S1', 'VH.TIF', 'f')
else:
print("Error: Exception on sarmode")
exit
exit()
else:
print("ERROR: wrong sardirtype argument")
exit
exit()
ndvis=[]
ndvis=search_files(args.ndvidir)
modeldir = args.modeldir
modelchoice = args.modelchoice
maskedlabels = args.maskedlabels
slope = args.slope
slopevalue = args.slopevalue
......@@ -719,7 +965,7 @@ if __name__ == "__main__":
sarvv = os.path.join(sard , "Sigma0_VH.img")
else:
print("Error: Exception on sarmode")
exit
exit()
sarth = os.path.join(sard , "incidenceAngleFromEllipsoid.img")
else:
sarvv = sard
......@@ -731,11 +977,7 @@ if __name__ == "__main__":
output = os.path.join(args.outdir, "MV_"+sarsplit[0]+"_"+args.zone+"_"+sarsplit[4]+".csv")
else:
print("Error: Exception on sarmode")
exit
if os.path.exists(output):
print(output+" already exists! Passing...")
continue
exit()
print("using: \n"+sarvv+" \n"+sarth+" \n"+ndvi+" \n"+output)
......@@ -744,8 +986,8 @@ if __name__ == "__main__":
elif args.pipeline == 'stack' :
if not os.path.isdir(args.ndvidir):
print("erreur "+args.ndvidir+" n'est pas un dossier")
exit
print("erreur "+args.ndvidir+" n'est pas un dossier")
exit()
ndvis=[]
ndvis=search_files(args.ndvidir)
......@@ -765,7 +1007,7 @@ if __name__ == "__main__":
if proj.IsGeographic() :
print("Reference image is in Geographic coordinate system. Reprojection is needed.")
exit
exit()
upx, xres, xskew, upy, yskew, yres = raster.GetGeoTransform()
cols = raster.RasterXSize
......@@ -816,10 +1058,18 @@ if __name__ == "__main__":
tilefiles = []
if len(tilesname) == 0:
print("bug")
exit
exit()
if args.demtype == "srtmhgt1sec":
srtmsuf = ".SRTMGL1.hgt.zip"
elif args.demtype == "srtm3sec":
srtmsuf = ".SRTM.zip"
else:
print("wrong argument demtype!")
exit()
for tile in tilesname :
tilef = os.path.join(args.srtmdir,tile+".SRTMGL1.hgt.zip")
tilef = os.path.join(args.srtmdir,tile + srtmsuf)
if not os.path.exists(tilef):
print("Missing SRTM tile : "+tilef)
continue
......@@ -857,7 +1107,10 @@ if __name__ == "__main__":
os.remove(outdem)
print("Done.")
elif args.pipeline == 'gpm':
wet_or_dry_pipeline(args)
else :
parser.exit()
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment