[seiscomp, scanloc] Install, add .gitignore

This commit is contained in:
2025-10-09 15:07:02 +02:00
commit 20f5301bb1
2848 changed files with 1315858 additions and 0 deletions

938
bin/playback_picks Executable file
View File

@ -0,0 +1,938 @@
#!/usr/bin/env seiscomp-python
############################################################################
# Copyright (C) 2016 by gempa GmbH #
# #
# All Rights Reserved. #
# #
# NOTICE: All information contained herein is, and remains #
# the property of gempa GmbH and its suppliers, if any. The intellectual #
# and technical concepts contained herein are proprietary to gempa GmbH #
# and its suppliers. #
# Dissemination of this information or reproduction of this material #
# is strictly forbidden unless prior written permission is obtained #
# from gempa GmbH. #
# #
# Author: Enrico Ellguth, Dirk Roessler #
# Email: enrico.ellguth@gempa.de, roessler@gempa.de #
############################################################################
import os
import sys
import time
import seiscomp.client
import seiscomp.core
import seiscomp.io
import seiscomp.datamodel
def timing_pickTime(obj):
"""
Sort picks, origins by their time values
Sort amplitudes by their reference time
"""
po = seiscomp.datamodel.Pick.Cast(obj[0])
oo = seiscomp.datamodel.Origin.Cast(obj[0])
if po or oo:
t = obj[0].time().value()
else:
t = obj[0].timeWindow().reference()
return t
def timing_creationTime(obj):
"""
Sort all objects by their creation time
"""
ct = obj[0].creationInfo().creationTime()
return ct
def listPicks(self, objects):
print(
"\n#phase, time , streamID , author, to previous pick "
"[s], delay [s]",
file=sys.stdout,
)
t0 = None
for obj, _ in objects:
p = seiscomp.datamodel.Pick.Cast(obj)
if not p:
continue
time = p.time().value()
try:
phase = p.phaseHint().code()
except ValueError:
phase = "None"
wfID = p.waveformID()
net = wfID.networkCode()
sta = wfID.stationCode()
loc = wfID.locationCode()
cha = wfID.channelCode()
try:
author = p.creationInfo().author()
except ValueError:
author = "None"
try:
delay = f"{(p.creationInfo().creationTime() - time).toDouble():.3f}"
except ValueError:
delay = "None"
if t0 is not None:
deltaT = f"{(time - t0).toDouble():.3f}"
else:
deltaT = "None"
streamID = f"{net}.{sta}.{loc}.{cha}"
print(
f"{phase: <6}, {time.toString('%FT%T')}.{int(time.microseconds()/1000):03d}"
f", {streamID: <15}, {author}, {deltaT}, {delay}",
file=sys.stdout,
)
t0 = time
return True
def printStatistics(ep):
minPickTime = None
maxPickTime = None
minPickCTime = None
maxPickCTime = None
minAmplitudeTime = None
maxAmplitudeTime = None
minOriginTime = None
maxOriginTime = None
minOriginCTime = None
maxOriginCTime = None
# read picks
nslc = set()
authorPick = set()
authorAmplitude = set()
authorOrigin = set()
detectionStreams = set()
cntPick = ep.pickCount()
for i in range(cntPick):
pick = ep.pick(i)
try:
authorPick.add(pick.creationInfo().author())
except ValueError:
print(
f"Author information not found in pick {pick.publicID()}: NSLC list may"
" be incomplete",
file=sys.stderr,
)
try:
net = pick.waveformID().networkCode()
sta = pick.waveformID().stationCode()
loc = pick.waveformID().locationCode()
cha = pick.waveformID().channelCode()
nslc.add(f"{net}.{sta}.{loc}.{cha}")
detectionStreams.add(f".{loc}.{cha}")
except ValueError:
print(
f"Stream information not found in pick {pick.publicID()}: NSLC list "
"may be incomplete",
file=sys.stderr,
)
if not minPickTime:
minPickTime = pick.time().value()
elif pick.time().value() < minPickTime:
minPickTime = pick.time().value()
if not maxPickTime:
maxPickTime = pick.time().value()
elif pick.time().value() > maxPickTime:
maxPickTime = pick.time().value()
try:
pick.creationInfo().creationTime()
except ValueError:
print(
f"Creation time not found in pick {pick.publicID()}: Statistics may "
"be incomplete",
file=sys.stderr,
)
continue
if not minPickCTime:
minPickCTime = pick.creationInfo().creationTime()
elif pick.creationInfo().creationTime() < minPickCTime:
minPickCTime = pick.creationInfo().creationTime()
if not maxPickCTime:
maxPickCTime = pick.creationInfo().creationTime()
elif pick.creationInfo().creationTime() > maxPickCTime:
maxPickCTime = pick.creationInfo().creationTime()
# read amplitudes
cntAmp = ep.amplitudeCount()
for i in range(cntAmp):
amp = ep.amplitude(i)
try:
authorAmplitude.add(amp.creationInfo().author())
except ValueError:
print(
f"Author information not found in amplitude {amp.publicID()}: NSLC "
"list may be incomplete",
file=sys.stderr,
)
try:
net = amp.waveformID().networkCode()
sta = amp.waveformID().stationCode()
loc = amp.waveformID().locationCode()
cha = amp.waveformID().channelCode()
nslc.add(f"{net}.{sta}.{loc}.{cha}")
detectionStreams.add(f".{loc}.{cha}")
except ValueError:
print(
f"Stream information not found in amplitude {amp.publicID()}: NSLC "
"list may be incomplete",
file=sys.stderr,
)
if not minAmplitudeTime:
minAmplitudeTime = amp.timeWindow().reference()
elif amp.timeWindow().reference() < minAmplitudeTime:
minAmplitudeTime = amp.timeWindow().reference()
if not maxAmplitudeTime:
maxAmplitudeTime = amp.timeWindow().reference()
elif amp.timeWindow().reference() > maxAmplitudeTime:
maxAmplitudeTime = amp.timeWindow().reference()
# read origins
cntOrg = ep.originCount()
for i in range(cntOrg):
oo = ep.origin(i)
try:
authorOrigin.add(oo.creationInfo().author())
except ValueError:
print(
f"Author information not found in origin {oo.publicID()}:",
file=sys.stderr,
)
if not minOriginTime:
minOriginTime = oo.time().value()
elif oo.time().value() < minOriginTime:
minOriginTime = oo.time().value()
if not maxOriginTime:
maxOriginTime = oo.time().value()
elif oo.time().value() > maxOriginTime:
maxOriginTime = oo.time().value()
try:
oo.creationInfo().creationTime()
except ValueError:
print(
f"Creation time not found in oo {oo.publicID()}: Statistics may "
"be incomplete",
file=sys.stderr,
)
continue
if not minOriginCTime:
minOriginCTime = oo.creationInfo().creationTime()
elif oo.creationInfo().creationTime() < minOriginCTime:
minOriginCTime = oo.creationInfo().creationTime()
if not maxOriginCTime:
maxOriginCTime = oo.creationInfo().creationTime()
elif oo.creationInfo().creationTime() > maxOriginCTime:
maxOriginCTime = oo.creationInfo().creationTime()
print(
f"""
Picks
+ number: {cntPick}
+ first pick: {minPickTime}
+ last pick: {maxPickTime}""",
file=sys.stdout,
)
if cntPick > 0:
print(
f""" + interval: {(maxPickTime - minPickTime).toDouble():.3f} s""",
file=sys.stdout,
)
try:
print(
f""" + first created: {minPickCTime}
+ last created: {maxPickCTime}
+ interval: {(maxPickCTime - minPickCTime).toDouble():.3f} s""",
file=sys.stdout,
)
except TypeError:
print(
""" + first created: no creation information
+ last created: no creation information
+ interval: no creation information""",
file=sys.stdout,
)
pass
print(f" + found {len(authorPick)} pick author(s):", file=sys.stdout)
for i in authorPick:
print(f" + {i}", file=sys.stdout)
print(
f"""
Amplitudes
+ number: {cntAmp}""",
file=sys.stdout,
)
if cntAmp > 0:
print(
f""" + first amplitude: {minAmplitudeTime}
+ last amplitude: {maxAmplitudeTime}
+ interval: {(maxAmplitudeTime - minAmplitudeTime).toDouble():.3f} s""",
file=sys.stdout,
)
print(f" + found {len(authorAmplitude)} amplitude author(s):", file=sys.stdout)
for i in authorAmplitude:
print(f" + {i}", file=sys.stdout)
print(
f"""
Origins
+ number: {cntOrg}""",
file=sys.stdout,
)
if cntOrg > 0:
print(
f""" + first origin: {minOriginTime}
+ last origin: {maxOriginTime}
+ interval: {(maxOriginTime - minOriginTime).toDouble():.3f} s""",
file=sys.stdout,
)
try:
print(
f""" + first created: {minOriginCTime}
+ last created: {maxOriginCTime}
+ interval: {(maxOriginCTime - minOriginCTime).toDouble():.3f} s""",
file=sys.stdout,
)
except TypeError:
print(
""" + first created: no creation information
+ last created: no creation information
+ interval: no creation information""",
file=sys.stdout,
)
pass
print(f" + found {len(authorOrigin)} origin author(s):", file=sys.stdout)
for i in authorOrigin:
print(f" + {i}", file=sys.stdout)
# stream information
print(f"\nFound {len(detectionStreams)} SensorLocation.Channel:", file=sys.stdout)
for i in detectionStreams:
print(f" + {i}", file=sys.stdout)
print(f"\nFound {len(nslc)} streams:", file=sys.stdout)
for i in sorted(nslc):
print(f" + {i}", file=sys.stdout)
return True
class PickPlayback(seiscomp.client.Application):
def __init__(self, argc, argv):
super().__init__(argc, argv)
self.speed = 1.0
self.timing = "creationTime"
self.jump = 0.0
self.print = False
self.printList = False
self.group = "PICK"
self.ampGroup = "AMPLITUDE"
self.orgGroup = "LOCATION"
self.fileNames = None
self.mode = "historic"
self.authors = None
self.objects = None
self.setMessagingUsername("pbpick")
self.setMessagingEnabled(True)
self.setPrimaryMessagingGroup("PICK")
self.setDatabaseEnabled(False, False)
def createCommandLineDescription(self):
self.commandline().addGroup("Playback")
self.commandline().addStringOption(
"Playback",
"authors",
"Author of objects to filter before playing back. Objects from all other "
"authors are ignored. Separate multiple authors by comma.",
)
self.commandline().addDoubleOption(
"Playback", "jump,j", "Minutes to skip objects in the beginning."
)
self.commandline().addOption(
"Playback",
"list",
"Just list important pick information from the read XML file and then "
"exit without playing back. The sorting of the list depends on '--timing'."
"Information include: phase hint, pick time, stream ID, author, time to "
"previous pick, delay.",
)
self.commandline().addStringOption(
"Playback",
"mode",
"Playback mode: 'historic' or 'realTime'. "
"'realTime' mimics current situation. Default: 'historic'.",
)
self.commandline().addStringOption(
"Playback",
"object,o",
"Limit the playback to the given list of objects. Supported values are: \n"
"pick, amplitude, origin.",
)
self.commandline().addOption(
"Playback",
"print",
"Just print some statistics of the read XML file and then "
"exit without playing back. The list of stream codes (NSLC) is printed to "
"stdout. All other information is printed to stderr. The information can "
"be used for filtering waveforms (scart) or inventory (invextr), for "
"creating global bindings or applying author filtering, e.g., in "
"dump_picks.",
)
self.commandline().addDoubleOption(
"Playback", "speed", "Speed of playback.\n1: true speed."
)
self.commandline().addStringOption(
"Playback",
"timing",
"Timing reference: pickTime or creationTime. Default: creationTime. "
"'pickTime' plays back in order of actual times of objects, "
"'creationTime' considers their creation times instead. Use 'pickTime' if "
"creation times are not representative of the order of objects, e.g., when "
"created in playbacks. 'creationTime' should be considered for playing "
"back origins since their actual origin time values are always before "
"picks and amplitudes.",
)
def printUsage(self):
print(
f"""Usage:
{os.path.basename(__file__)} [options] [XML file][:PICK:AMPLITUDE:LOCATION]
Play back pick, amplitude and origin objects from one or more XML files in SCML format
sending them to the SeisComP messaging in timely order. Default message groups:
* PICK for picks,
* AMPLITUDE for amplitudes.
* LOCATION for origins,"""
)
super().printUsage()
print(
f"""Examples:
Play back picks and other objects in file 'pick.xml' at true speed jumping the
first 2 minutes
{os.path.basename(__file__)} -j 2 picks.xml
Play back picks and other objects from 2 XML files sending the picks, amplitudes
and origins ordered by creation time to different message groups but amplitudes
to the same default group (AMPLITUDE).
{os.path.basename(__file__)} origins.xml l1origins.xml:L1PICK:AMPLITUDE:L1LOCATION
Just print statistics and stream information
{os.path.basename(__file__)} --print picks.xml
"""
)
def init(self):
if not super().init():
return False
return True
def validateParameters(self):
if not super().validateParameters():
return False
try:
self.authors = self.commandline().optionString("authors").split(",")
except RuntimeError:
pass
try:
self.mode = self.commandline().optionString("mode")
except RuntimeError:
pass
try:
self.objects = self.commandline().optionString("object")
except RuntimeError:
pass
if self.mode not in ("historic", "realTime"):
print(f"Unknown mode: {self.mode}", file=sys.stderr)
return False
try:
self.print = self.commandline().hasOption("print")
except RuntimeError:
pass
try:
self.printList = self.commandline().hasOption("list")
except RuntimeError:
pass
try:
self.speed = self.commandline().optionDouble("speed")
except RuntimeError:
pass
try:
self.timing = self.commandline().optionString("timing")
except RuntimeError:
pass
try:
self.jump = self.commandline().optionDouble("jump")
except RuntimeError:
pass
if self.timing not in ("pickTime", "creationTime"):
print(f"Unknown timing: {self.timing}", file=sys.stderr)
return False
try:
self.group = self.commandline().optionString("primary-group")
except RuntimeError:
pass
files = self.commandline().unrecognizedOptions()
if not files:
print("At least one XML file must be given!", file=sys.stderr)
return False
print(files, file=sys.stderr)
self.fileNames = list(files)
if self.print or self.printList:
self.setMessagingEnabled(False)
return True
def run(self):
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
objects = []
eps = []
minTime = None
maxTime = None
print("Input:", file=sys.stdout)
for fileName in self.fileNames:
group = self.group
ampGroup = self.ampGroup
orgGroup = self.orgGroup
toks = fileName.split(":")
if len(toks) == 2:
fileName = toks[0]
group = toks[1]
elif len(toks) == 3:
fileName = toks[0]
group = toks[1]
ampGroup = toks[2]
elif len(toks) == 4:
fileName = toks[0]
group = toks[1]
ampGroup = toks[2]
orgGroup = toks[3]
print(
f" + file: {fileName}",
file=sys.stdout,
)
ar = seiscomp.io.XMLArchive()
if not ar.open(fileName):
print(f"Could not open {fileName}", file=sys.stderr)
return False
obj = ar.readObject()
ar.close()
if obj is None:
print("Empty document", file=sys.stderr)
return False
ep = seiscomp.datamodel.EventParameters.Cast(obj)
if self.print:
printStatistics(ep)
if not self.printList:
return True
eps.append(ep)
if ep is None:
print(
f"Expected event parameters, got {obj.className()}", file=sys.stderr
)
return False
# read picks
cntPick = ep.pickCount()
if cntPick == 0:
print(f"No picks found in file {fileName}", file=sys.stderr)
if self.objects is not None and "pick" not in self.objects:
print(
f"Skipping picks. Supported objects: {self.objects}",
file=sys.stderr,
)
cntPick = 0
for i in range(cntPick):
pick = ep.pick(i)
if self.authors is not None:
try:
if (
pick.creationInfo().author() not in self.authors
and not self.printList
):
print(
f"Skipping pick {pick.publicID()}: "
f"{pick.creationInfo().author()} not in author list",
file=sys.stderr,
)
continue
except ValueError:
if not self.printList:
print(
f"Skipping pick {pick.publicID()}: "
f"author is not available",
file=sys.stderr,
)
continue
if self.timing == "creationTime":
try:
pick.creationInfo().creationTime()
except Exception:
if not self.printList:
print(
f"Skipping pick {pick.publicID()}: no creation time",
file=sys.stderr,
)
continue
# filter by time
if minTime and pick.time().value() < minTime:
continue
if maxTime and pick.time().value() >= maxTime:
continue
objects.append((pick, group))
# read amplitudes and add to objects
cntAmp = ep.amplitudeCount()
if cntAmp == 0:
print("No Amplitudes found", file=sys.stderr)
if self.objects is not None and "amplitude" not in self.objects:
print(
f"Skipping amplitudes. Supported objects: {self.objects}",
file=sys.stderr,
)
cntAmp = 0
for i in range(cntAmp):
amp = ep.amplitude(i)
if self.authors is not None:
try:
if (
amp.creationInfo().author() not in self.authors
and not self.printList
):
print(
f"Skipping amplitude {amp.publicID()}: "
f"{amp.creationInfo().author()} not in author list",
file=sys.stderr,
)
continue
except ValueError:
if not self.printList:
print(
f"Skipping amplitude {amp.publicID()}: "
f"author is not available",
file=sys.stderr,
)
continue
if self.timing == "creationTime":
try:
amp.creationInfo().creationTime()
except Exception:
print(
f"Skipping amplitude {amp.publicID()}: no creation time",
file=sys.stderr,
)
continue
objects.append((amp, ampGroup))
# read origins and add to objects
cntOrgs = ep.originCount()
if cntOrgs == 0:
print("No Origins found", file=sys.stderr)
if self.objects is not None and "origin" not in self.objects:
print(
f"Skipping origins. Supported objects: {self.objects}",
file=sys.stderr,
)
cntOrgs = 0
for i in range(cntOrgs):
oo = ep.origin(i)
if self.authors is not None:
try:
if (
oo.creationInfo().author() not in self.authors
and not self.printList
):
print(
f"Skipping origin {oo.publicID()}: "
f"{oo.creationInfo().author()} not in author list",
file=sys.stderr,
)
continue
except ValueError:
if not self.printList:
print(
f"Skipping origin {oo.publicID()}: "
f"author is not available",
file=sys.stderr,
)
continue
if self.timing == "creationTime":
try:
oo.creationInfo().creationTime()
except Exception:
try:
string = oo.publicID().split("/")[1].split(".")[:2]
timeString = string[0] + "." + string[1]
timeFormat = "%Y%m%d%H%M%S.%f"
t = seiscomp.core.Time()
t.fromString(str(timeString), timeFormat)
ci = seiscomp.datamodel.CreationInfo()
ci.setCreationTime(t)
oo.setCreationInfo(ci)
print(
f"creation time not found in origin {oo.publicID()}: "
f"assuming {oo.creationInfo().creationTime()} from "
"originID ",
file=sys.stderr,
)
except Exception:
if not self.printList:
print(
f"Skipping origin {oo.publicID()}: no creation time",
file=sys.stderr,
)
continue
objects.append((oo, orgGroup))
print(
f" + considering {cntPick} picks, {cntAmp} amplitudes, {cntOrgs} origins",
file=sys.stdout,
)
if self.print or self.printList:
print(" + do not send objects to messaging")
else:
print(
f""" + sending objects to groups
+ picks: {group}
+ amplitudes: {ampGroup}
+ origins: {orgGroup}""",
file=sys.stdout,
)
if self.timing == "pickTime":
try:
objects.sort(key=timing_pickTime)
except ValueError:
print("Time value not set in at least 1 object", file=sys.stderr)
if not self.printList:
return False
elif self.timing == "creationTime":
try:
objects.sort(key=timing_creationTime)
except ValueError:
print("Creation time not set in at least 1 object", file=sys.stderr)
if not self.printList:
return False
else:
print(f"Unknown timing: {self.timing}", file=sys.stderr)
return False
print("Setup:", file=sys.stdout)
print(f" + author filter: {self.authors}", file=sys.stdout)
print(f" + timing/sorting: {self.timing}", file=sys.stdout)
if self.printList:
listPicks(self, objects)
return True
seiscomp.datamodel.Notifier.Enable()
firstTime = None
lastTime = None
refTime = None
addSeconds = 0.0
sys.stdout.flush()
for obj, group in objects:
po = seiscomp.datamodel.Pick.Cast(obj)
ao = seiscomp.datamodel.Amplitude.Cast(obj)
oo = seiscomp.datamodel.Origin.Cast(obj)
if self.isExitRequested():
break
if self.timing == "pickTime":
if ao:
refTime = obj.timeWindow().reference()
elif po:
refTime = obj.time().value()
elif oo:
refTime = obj.time().value()
else:
print(
"Object neither pick nor amplitude or origin- ignoring",
file=sys.stderr,
)
return False
else:
refTime = obj.creationInfo().creationTime()
if not firstTime:
firstTime = refTime
print(f" + first time: {firstTime}", file=sys.stderr)
print(f" + playback mode: {self.mode}", file=sys.stderr)
print(f" + speed factor: {self.speed}", file=sys.stderr)
if self.mode == "realTime":
now = seiscomp.core.Time.GMT()
addSeconds = (now - firstTime).toDouble()
print(
f" + adding {addSeconds: .3f} s to: pick time, amplitude "
"reference time, origin time, creation time",
file=sys.stderr,
)
print("Playback progress:", file=sys.stderr)
objectType = "pick"
if ao:
objectType = "amplitude"
if oo:
objectType = "origin"
print(
f" + {obj.publicID()} {objectType}: {group} - reference time: {refTime}",
end="",
file=sys.stderr,
)
# add addSeconds to all times in real-time mode
if self.mode == "realTime":
objectInfo = obj.creationInfo()
creationTime = objectInfo.creationTime() + seiscomp.core.TimeSpan(
addSeconds
)
obj.creationInfo().setCreationTime(creationTime)
if ao:
objectInfo = obj.timeWindow()
amplitudeTime = objectInfo.reference() + seiscomp.core.TimeSpan(
addSeconds
)
obj.timeWindow().setReference(amplitudeTime)
print(
"\n + real-time mode - using modified reference time: "
f"{obj.timeWindow().reference()}, creation time: {creationTime}",
end="",
file=sys.stderr,
)
elif po or oo:
objectTime = obj.time()
objectTime.setValue(
objectTime.value() + seiscomp.core.TimeSpan(addSeconds)
)
obj.setTime(objectTime)
print(
f"\n + real-time mode - using modified {objectType} time: "
f"{obj.time().value()}, creation time: {creationTime}",
end="",
file=sys.stderr,
)
else:
print(
"\n + object not pick, amplitude or origin - ignoring",
file=sys.stderr,
)
return False
delay = 0
if lastTime:
delay = (refTime - lastTime).toDouble() / self.speed
if (refTime - firstTime).toDouble() / 60.0 >= self.jump:
delay = max(delay, 0)
print(f" - time to sending: {delay:.4f} s", file=sys.stderr)
time.sleep(delay)
lastTime = refTime
nc = seiscomp.datamodel.NotifierCreator(seiscomp.datamodel.OP_ADD)
obj.accept(nc)
msg = seiscomp.datamodel.Notifier.GetMessage()
self.connection().send(group, msg)
else:
print(" - skipping", file=sys.stderr)
sys.stdout.flush()
print("")
return True
def main(argv):
app = PickPlayback(len(argv), argv)
return app()
if __name__ == "__main__":
sys.exit(main(sys.argv))