Merge branch 'develop' of ariadne.geophysik.ruhr-uni-bochum.de:/data/git/pylot into develop

This commit is contained in:
Marcel Paffrath 2016-08-30 15:00:34 +02:00
commit f1d806c154
8 changed files with 208 additions and 84 deletions

View File

@ -35,8 +35,9 @@ from PySide.QtCore import QCoreApplication, QSettings, Signal, QFile, \
from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \ from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \
QWidget, QHBoxLayout, QStyle, QKeySequence, QLabel, QFrame, QAction, \ QWidget, QHBoxLayout, QStyle, QKeySequence, QLabel, QFrame, QAction, \
QDialog, QErrorMessage, QApplication, QPixmap, QMessageBox, QSplashScreen, \ QDialog, QErrorMessage, QApplication, QPixmap, QMessageBox, QSplashScreen, \
QActionGroup, QListWidget, QDockWidget QActionGroup, QListWidget, QDockWidget, QLineEdit
import numpy as np import numpy as np
import subprocess
from obspy import UTCDateTime from obspy import UTCDateTime
from pylot.core.io.data import Data from pylot.core.io.data import Data
@ -44,7 +45,7 @@ from pylot.core.io.inputs import FilterOptions, AutoPickParameter
from pylot.core.pick.autopick import autopickevent from pylot.core.pick.autopick import autopickevent
from pylot.core.pick.compare import Comparison from pylot.core.pick.compare import Comparison
from pylot.core.io.phases import picksdict_from_picks from pylot.core.io.phases import picksdict_from_picks
from pylot.core.loc.nll import locate as locateNll import pylot.core.loc.nll as nll
from pylot.core.util.defaults import FILTERDEFAULTS, COMPNAME_MAP, \ from pylot.core.util.defaults import FILTERDEFAULTS, COMPNAME_MAP, \
AUTOMATIC_DEFAULTS AUTOMATIC_DEFAULTS
from pylot.core.util.errors import FormatError, DatastructureError, \ from pylot.core.util.errors import FormatError, DatastructureError, \
@ -61,7 +62,7 @@ from pylot.core.util.thread import AutoPickThread
from pylot.core.util.version import get_git_version as _getVersionString from pylot.core.util.version import get_git_version as _getVersionString
import icons_rc import icons_rc
locateTool = dict(nll=locateNll) locateTool = dict(nll=nll)
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
@ -630,6 +631,8 @@ class MainWindow(QMainWindow):
ans = self.data.setWFData(self.fnames) ans = self.data.setWFData(self.fnames)
elif self.fnames is None and self.okToContinue(): elif self.fnames is None and self.okToContinue():
ans = self.data.setWFData(self.getWFFnames()) ans = self.data.setWFData(self.getWFFnames())
else:
ans = False
self._stime = getGlobalTimes(self.getData().getWFData())[0] self._stime = getGlobalTimes(self.getData().getWFData())[0]
if ans: if ans:
self.plotWaveformData() self.plotWaveformData()
@ -889,12 +892,59 @@ class MainWindow(QMainWindow):
raise TypeError('Unknow picktype {0}'.format(picktype)) raise TypeError('Unknow picktype {0}'.format(picktype))
def locateEvent(self): def locateEvent(self):
"""
locate event using the manually picked phases
:return:
"""
if not self.okToContinue():
return
settings = QSettings() settings = QSettings()
# get location tool hook
loctool = settings.value("loc/tool", "nll") loctool = settings.value("loc/tool", "nll")
extlocpath = settings.value("%s/binPath".format(loctool), None) lt = locateTool[loctool]
locroot = settings.value("%s/rootPath".format(loctool), None) # get working directory
if extlocpath is None or locroot is None: locroot = settings.value("{0}/rootPath".format(loctool), None)
if locroot is None:
self.PyLoTprefs() self.PyLoTprefs()
self.locateEvent()
infile = settings.value("{0}/inputFile".format(loctool), None)
if not infile:
caption = 'Select {0} input file'.format(loctool)
filt = "Supported file formats" \
" (*.in *.ini *.conf *.cfg)"
ans = QFileDialog().getOpenFileName(self, caption=caption,
filter=filt, dir=locroot)
infile = ans[0]
settings.setValue("{0}/inputFile".format(loctool), infile)
settings.sync()
if loctool == 'nll':
ttt = settings.value("{0}/travelTimeTables", None)
ok = False
if ttt is None:
while not ok:
text, ok = QInputDialog.getText(self, 'Pattern for travel time tables',
'Base name of travel time tables',
echo=QLineEdit.Normal,
text="ttime")
ttt = text
outfile = settings.value("{0}/outputFile".format(loctool),
os.path.split(os.tempnam())[-1])
phasefile = os.path.split(os.tempnam())[-1]
phasepath = os.path.join(locroot, 'obs', phasefile)
locpath = os.path.join(locroot, 'loc', outfile)
lt.export(self.getPicks(), phasepath)
lt.modify_inputs(infile, locroot, outfile, phasefile, ttt)
try:
lt.locate(infile)
except RuntimeError as e:
print(e.message)
finally:
os.remove(phasepath)
self.getData().applyEVTData(lt.read_location(locpath), type='event')
def check4Loc(self): def check4Loc(self):
return self.picksNum() > 4 return self.picksNum() > 4

View File

@ -6,13 +6,15 @@ from __future__ import print_function
import argparse import argparse
import glob import glob
import string import string
import os
import numpy as np import numpy as np
from pylot.core.analysis.magnitude import M0Mw from pylot.core.analysis.magnitude import M0Mw
from pylot.core.io.data import Data from pylot.core.io.data import Data
from pylot.core.io.inputs import AutoPickParameter from pylot.core.io.inputs import AutoPickParameter
from pylot.core.loc.nll import * import pylot.core.loc.nll as nll
import pylot.core.loc.hsat as hsat
from pylot.core.pick.autopick import autopickevent, iteratepicker from pylot.core.pick.autopick import autopickevent, iteratepicker
from pylot.core.util.structure import DATASTRUCTURE from pylot.core.util.structure import DATASTRUCTURE
from pylot.core.util.version import get_git_version as _getVersionString from pylot.core.util.version import get_git_version as _getVersionString
@ -112,16 +114,17 @@ def autoPyLoT(inputfile):
# locating # locating
if locflag == 1: if locflag == 1:
# write phases to NLLoc-phase file # write phases to NLLoc-phase file
picksExport(picks, 'NLLoc', phasefile) nll.export(picks, phasefile)
# For locating the event the NLLoc-control file has to be modified! # For locating the event the NLLoc-control file has to be modified!
evID = event[string.rfind(event, "/") + 1: len(events) - 1] evID = event[string.rfind(event, "/") + 1: len(events) - 1]
nllocout = '%s_%s' % (evID, nllocoutpatter) nllocout = '%s_%s' % (evID, nllocoutpatter)
# create comment line for NLLoc-control file # create comment line for NLLoc-control file
modifyInputFile(ctrf, nllocroot, nllocout, phasef, ttpat) nll.modify_inputs(ctrf, nllocroot, nllocout, phasef,
ttpat)
# locate the event # locate the event
locate(nlloccall, ctrfile) nll.locate(ctrfile)
# !iterative picking if traces remained unpicked or occupied with bad picks! # !iterative picking if traces remained unpicked or occupied with bad picks!
# get theoretical onset times for picks with weights >= 4 # get theoretical onset times for picks with weights >= 4
@ -162,11 +165,11 @@ def autoPyLoT(inputfile):
print("autoPyLoT: Starting with iteration No. %d ..." % nlloccounter) print("autoPyLoT: Starting with iteration No. %d ..." % nlloccounter)
picks = iteratepicker(wfdat, nllocfile, picks, badpicks, parameter) picks = iteratepicker(wfdat, nllocfile, picks, badpicks, parameter)
# write phases to NLLoc-phase file # write phases to NLLoc-phase file
picksExport(picks, 'NLLoc', phasefile) nll.export(picks, phasefile)
# remove actual NLLoc-location file to keep only the last # remove actual NLLoc-location file to keep only the last
os.remove(nllocfile) os.remove(nllocfile)
# locate the event # locate the event
locate(nlloccall, ctrfile) nll.locate(ctrfile)
print("autoPyLoT: Iteration No. %d finished." % nlloccounter) print("autoPyLoT: Iteration No. %d finished." % nlloccounter)
# get updated NLLoc-location file # get updated NLLoc-location file
nllocfile = max(glob.glob(locsearch), key=os.path.getctime) nllocfile = max(glob.glob(locsearch), key=os.path.getctime)
@ -199,15 +202,11 @@ def autoPyLoT(inputfile):
# write phase files for various location routines # write phase files for various location routines
# HYPO71 # HYPO71
hypo71file = '%s/autoPyLoT_HYPO71.pha' % event hypo71file = '%s/autoPyLoT_HYPO71.pha' % event
if hasattr(finalpicks, 'getpicdic'): if hasattr(finalpicks, 'getpicdic') and finalpicks.getpicdic() is not None:
if finalpicks.getpicdic() is not None: hsat.export(finalpicks.getpicdic(), hypo71file)
writephases(finalpicks.getpicdic(), 'HYPO71', hypo71file) data.applyEVTData(finalpicks.getpicdic())
data.applyEVTData(finalpicks.getpicdic())
else:
writephases(picks, 'HYPO71', hypo71file)
data.applyEVTData(picks)
else: else:
writephases(picks, 'HYPO71', hypo71file) hsat.export(picks, hypo71file)
data.applyEVTData(picks) data.applyEVTData(picks)
fnqml = '%s/autoPyLoT' % event fnqml = '%s/autoPyLoT' % event
data.exportEvent(fnqml) data.exportEvent(fnqml)
@ -235,15 +234,15 @@ def autoPyLoT(inputfile):
# locating # locating
if locflag == 1: if locflag == 1:
# write phases to NLLoc-phase file # write phases to NLLoc-phase file
picksExport(picks, 'NLLoc', phasefile) nll.export(picks, phasefile)
# For locating the event the NLLoc-control file has to be modified! # For locating the event the NLLoc-control file has to be modified!
nllocout = '%s_%s' % (parameter.get('eventID'), nllocoutpatter) nllocout = '%s_%s' % (parameter.get('eventID'), nllocoutpatter)
# create comment line for NLLoc-control file # create comment line for NLLoc-control file
modifyInputFile(ctrf, nllocroot, nllocout, phasef, ttpat) nll.modify_inputs(ctrf, nllocroot, nllocout, phasef, ttpat)
# locate the event # locate the event
locate(nlloccall, ctrfile) nll.locate(ctrfile)
# !iterative picking if traces remained unpicked or occupied with bad picks! # !iterative picking if traces remained unpicked or occupied with bad picks!
# get theoretical onset times for picks with weights >= 4 # get theoretical onset times for picks with weights >= 4
# in order to reprocess them using smaller time windows around theoretical onset # in order to reprocess them using smaller time windows around theoretical onset
@ -283,11 +282,11 @@ def autoPyLoT(inputfile):
print("autoPyLoT: Starting with iteration No. %d ..." % nlloccounter) print("autoPyLoT: Starting with iteration No. %d ..." % nlloccounter)
picks = iteratepicker(wfdat, nllocfile, picks, badpicks, parameter) picks = iteratepicker(wfdat, nllocfile, picks, badpicks, parameter)
# write phases to NLLoc-phase file # write phases to NLLoc-phase file
picksExport(picks, 'NLLoc', phasefile) nll.export(picks, phasefile)
# remove actual NLLoc-location file to keep only the last # remove actual NLLoc-location file to keep only the last
os.remove(nllocfile) os.remove(nllocfile)
# locate the event # locate the event
locate(nlloccall, ctrfile) nll.locate(ctrfile)
print("autoPyLoT: Iteration No. %d finished." % nlloccounter) print("autoPyLoT: Iteration No. %d finished." % nlloccounter)
# get updated NLLoc-location file # get updated NLLoc-location file
nllocfile = max(glob.glob(locsearch), key=os.path.getctime) nllocfile = max(glob.glob(locsearch), key=os.path.getctime)
@ -320,15 +319,11 @@ def autoPyLoT(inputfile):
# write phase files for various location routines # write phase files for various location routines
# HYPO71 # HYPO71
hypo71file = '%s/%s/autoPyLoT_HYPO71.pha' % (datapath, parameter.get('eventID')) hypo71file = '%s/%s/autoPyLoT_HYPO71.pha' % (datapath, parameter.get('eventID'))
if hasattr(finalpicks, 'getpicdic'): if hasattr(finalpicks, 'getpicdic') and finalpicks.getpicdic() is not None:
if finalpicks.getpicdic() is not None: hsat.export(finalpicks.getpicdic(), hypo71file)
writephases(finalpicks.getpicdic(), 'HYPO71', hypo71file) data.applyEVTData(finalpicks.getpicdic())
data.applyEVTData(finalpicks.getpicdic())
else:
writephases(picks, 'HYPO71', hypo71file)
data.applyEVTData(picks)
else: else:
writephases(picks, 'HYPO71', hypo71file) hsat.export(picks, hypo71file)
data.applyEVTData(picks) data.applyEVTData(picks)
fnqml = '%s/%s/autoPyLoT' % (datapath, parameter.get('eventID')) fnqml = '%s/%s/autoPyLoT' % (datapath, parameter.get('eventID'))
data.exportEvent(fnqml) data.exportEvent(fnqml)

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import copy
import glob import glob
import os import os
from obspy import read_events, read_inventory from obspy import read_events, read_inventory
@ -434,7 +435,8 @@ class Data(object):
#firstonset = find_firstonset(picks) #firstonset = find_firstonset(picks)
if self.getEvtData().picks: if self.getEvtData().picks:
raise OverwriteError('Actual picks would be overwritten!') raise OverwriteError('Actual picks would be overwritten!')
picks = picks_from_picksdict(picks) else:
picks = picks_from_picksdict(picks)
self.getEvtData().picks = picks self.getEvtData().picks = picks
# if 'smi:local' in self.getID() and firstonset: # if 'smi:local' in self.getID() and firstonset:
# fonset_str = firstonset.strftime('%Y_%m_%d_%H_%M_%S') # fonset_str = firstonset.strftime('%Y_%m_%d_%H_%M_%S')
@ -443,25 +445,22 @@ class Data(object):
# self.getEvtData().resource_id = ID # self.getEvtData().resource_id = ID
def applyArrivals(arrivals):
"""
:param arrivals:
"""
pass
def applyEvent(event): def applyEvent(event):
""" """
takes an `obspy.core.event.Event` object and applies all new
information on the event to the actual data
:param event: :param event:
""" """
if not self.isNew(): if not self.isNew():
self.setEvtData(event) self.setEvtData(event)
else: else:
raise OverwriteError('Acutal event would be overwritten!') # prevent overwriting uncertainty information
picks = copy.deepcopy(self.getEvtData().picks)
event.picks = picks
# apply event information from location
self.getEvtData().update(event)
applydata = {'pick': applyPicks, applydata = {'pick': applyPicks,
'arrival': applyArrivals,
'event': applyEvent} 'event': applyEvent}
applydata[type](data) applydata[type](data)

View File

@ -117,7 +117,7 @@ def picksdict_from_pilot(fn):
except IndexError as e: except IndexError as e:
print(e.message + '\ntake two times the largest default error value') print(e.message + '\ntake two times the largest default error value')
spe = timeerrors[onset_name][-1] * 2 spe = timeerrors[onset_name][-1] * 2
phases[onset_name] = dict(mpp=pick, spe=spe) phases[onset_name] = dict(mpp=pick, spe=spe, weight=ierror)
picks[station] = phases picks[station] = phases
return picks return picks
@ -395,7 +395,11 @@ def writephases(arrivals, fformat, filename):
for key in arrivals: for key in arrivals:
# P onsets # P onsets
if arrivals[key]['P']: if arrivals[key]['P']:
fm = arrivals[key]['P']['fm'] try:
fm = arrivals[key]['P']['fm']
except KeyError as e:
print(e)
fm = None
if fm == None: if fm == None:
fm = '?' fm = '?'
onset = arrivals[key]['P']['mpp'] onset = arrivals[key]['P']['mpp']
@ -407,10 +411,12 @@ def writephases(arrivals, fformat, filename):
ss = onset.second ss = onset.second
ms = onset.microsecond ms = onset.microsecond
ss_ms = ss + ms / 1000000.0 ss_ms = ss + ms / 1000000.0
if arrivals[key]['P']['weight'] < 4: pweight = 1 # use pick
pweight = 1 # use pick try:
else: if arrivals[key]['P']['weight'] >= 4:
pweight = 0 # do not use pick pweight = 0 # do not use pick
except KeyError as e:
print(e.message + '; no weight set during processing')
fid.write('%s ? ? ? P %s %d%02d%02d %02d%02d %7.4f GAU 0 0 0 0 %d \n' % (key, fid.write('%s ? ? ? P %s %d%02d%02d %02d%02d %7.4f GAU 0 0 0 0 %d \n' % (key,
fm, fm,
year, year,
@ -421,7 +427,7 @@ def writephases(arrivals, fformat, filename):
ss_ms, ss_ms,
pweight)) pweight))
# S onsets # S onsets
if arrivals[key]['S']: if arrivals[key].has_key('S') and arrivals[key]['S']:
fm = '?' fm = '?'
onset = arrivals[key]['S']['mpp'] onset = arrivals[key]['S']['mpp']
year = onset.year year = onset.year
@ -432,10 +438,12 @@ def writephases(arrivals, fformat, filename):
ss = onset.second ss = onset.second
ms = onset.microsecond ms = onset.microsecond
ss_ms = ss + ms / 1000000.0 ss_ms = ss + ms / 1000000.0
if arrivals[key]['S']['weight'] < 4: sweight = 1 # use pick
sweight = 1 # use pick try:
else: if arrivals[key]['S']['weight'] >= 4:
sweight = 0 # do not use pick sweight = 0 # do not use pick
except KeyError as e:
print(str(e) + '; no weight set during processing')
fid.write('%s ? ? ? S %s %d%02d%02d %02d%02d %7.4f GAU 0 0 0 0 %d \n' % (key, fid.write('%s ? ? ? S %s %d%02d%02d %02d%02d %7.4f GAU 0 0 0 0 %d \n' % (key,
fm, fm,
year, year,

View File

@ -1,2 +1,21 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from pylot.core.io.phases import writephases
from pylot.core.util.version import get_git_version as _getVersionString
__version__ = _getVersionString()
def export(picks, fnout):
'''
Take <picks> dictionary and exports picking data to a NLLOC-obs
<phasefile> without creating an ObsPy event object.
:param picks: picking data dictionary
:type picks: dict
:param fnout: complete path to the exporting obs file
:type fnout: str
'''
# write phases to NLLoc-phase file
writephases(picks, 'HYPO71', fnout)

View File

@ -3,14 +3,18 @@
import subprocess import subprocess
import os import os
import glob
from obspy import read_events
from pylot.core.io.phases import writephases from pylot.core.io.phases import writephases
from pylot.core.util.utils import getPatternLine, runProgram from pylot.core.util.utils import getPatternLine, runProgram, which
from pylot.core.util.version import get_git_version as _getVersionString from pylot.core.util.version import get_git_version as _getVersionString
__version__ = _getVersionString() __version__ = _getVersionString()
class NLLocError(EnvironmentError):
pass
def picksExport(picks, locrt, phasefile): def export(picks, fnout):
''' '''
Take <picks> dictionary and exports picking data to a NLLOC-obs Take <picks> dictionary and exports picking data to a NLLOC-obs
<phasefile> without creating an ObsPy event object. <phasefile> without creating an ObsPy event object.
@ -18,17 +22,14 @@ def picksExport(picks, locrt, phasefile):
:param picks: picking data dictionary :param picks: picking data dictionary
:type picks: dict :type picks: dict
:param locrt: choose location routine :param fnout: complete path to the exporting obs file
:type locrt: str :type fnout: str
:param phasefile: complete path to the exporting obs file
:type phasefile: str
''' '''
# write phases to NLLoc-phase file # write phases to NLLoc-phase file
writephases(picks, locrt, phasefile) writephases(picks, 'NLLoc', fnout)
def modifyInputFile(ctrfn, root, nllocoutn, phasefn, tttn): def modify_inputs(ctrfn, root, nllocoutn, phasefn, tttn):
''' '''
:param ctrfn: name of NLLoc-control file :param ctrfn: name of NLLoc-control file
:type: str :type: str
@ -66,24 +67,32 @@ def modifyInputFile(ctrfn, root, nllocoutn, phasefn, tttn):
nllfile.close() nllfile.close()
def locate(call, fnin): def locate(fnin):
''' """
Takes paths to NLLoc executable <call> and input parameter file <fnin> takes an external program name
and starts the location calculation. :param fnin:
:return:
"""
:param call: full path to NLLoc executable exe_path = which('NLLoc')
:type call: str if exe_path is None:
raise NLLocError('NonLinLoc executable not found; check your '
'environment variables')
:param fnin: full path to input parameter file # locate the event utilizing external NonLinLoc installation
:type fnin: str try:
''' runProgram(exe_path, fnin)
except subprocess.CalledProcessError as e:
# locate the event raise RuntimeError(e.output)
runProgram(call, fnin)
def readLocation(fn): def read_location(fn):
pass path, file = os.path.split(fn)
file = glob.glob1(path, file + '.[0-9]*.grid0.loc.hyp')
if len(file) > 1:
raise IOError('ambiguous location name {0}'.format(file))
fn = os.path.join(path, file[0])
return read_events(fn)[0]
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -277,6 +277,15 @@ def getPatternLine(fn, pattern):
return None return None
def is_executable(fn):
"""
takes a filename and returns True if the file is executable on the system
and False otherwise
:param fn: path to the file to be tested
:return: True or False
"""
return os.path.isfile(fn) and os.access(fn, os.X_OK)
def isSorted(iterable): def isSorted(iterable):
''' '''
@ -393,9 +402,44 @@ def runProgram(cmd, parameter=None):
cmd.strip() cmd.strip()
cmd += ' %s 2>&1' % parameter cmd += ' %s 2>&1' % parameter
output = subprocess.check_output('{} | tee /dev/stderr'.format(cmd), subprocess.check_output('{} | tee /dev/stderr'.format(cmd), shell=True)
shell=True)
def which(program):
"""
takes a program name and returns the full path to the executable or None
modified after: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
:param program: name of the desired external program
:return: full path of the executable file
"""
try:
from PySide.QtCore import QSettings
settings = QSettings()
for key in settings.allKeys():
if 'binPath' in key:
os.environ['PATH'] += ':{0}'.format(settings.value(key))
except ImportError as e:
print(e.message)
def is_exe(fpath):
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
def ext_candidates(fpath):
yield fpath
for ext in os.environ.get("PATHEXT", "").split(os.pathsep):
yield fpath + ext
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
for candidate in ext_candidates(exe_file):
if is_exe(candidate):
return candidate
return None
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest

View File

@ -1337,8 +1337,8 @@ class LocalisationTab(PropTab):
self.locToolComboBox.setCurrentIndex(toolind) self.locToolComboBox.setCurrentIndex(toolind)
curroot = settings.value("%s/rootPath".format(curtool), None) curroot = settings.value("{0}/rootPath".format(curtool), None)
curbin = settings.value("%s/binPath".format(curtool), None) curbin = settings.value("{0}/binPath".format(curtool), None)
self.rootlabel = QLabel("root directory") self.rootlabel = QLabel("root directory")
self.binlabel = QLabel("bin directory") self.binlabel = QLabel("bin directory")
@ -1385,8 +1385,8 @@ class LocalisationTab(PropTab):
def getValues(self): def getValues(self):
loctool = self.locToolComboBox.currentText() loctool = self.locToolComboBox.currentText()
values = {"%s/rootPath".format(loctool): self.rootedit.text(), values = {"{0}/rootPath".format(loctool): self.rootedit.text(),
"%s/binPath".format(loctool): self.binedit.text(), "{0}/binPath".format(loctool): self.binedit.text(),
"loc/tool": loctool} "loc/tool": loctool}
return values return values