diff --git a/QtPyLoT.py b/QtPyLoT.py new file mode 100755 index 00000000..f0cc571f --- /dev/null +++ b/QtPyLoT.py @@ -0,0 +1,1177 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +PyLoT: Main program +=================== +PyLoT is a seismic data processing software capable of picking seismic +phases (symmetric and asymmetric error assignment), exporting these to +several common phase data formats and post process the data, e.g. locating +events, via external localization software. +Additionally PyLoT is meant as an interface to autoPyLoT which can +automatically pick seismic phases, if the parameters have properly been +chosen for the particular data set. + +Some icons are out of a free of charge icon set, which can be found here: +https://www.iconfinder.com/iconsets/flavour + +:author: + Sebastian Wehling-Benatelli +:copyright: + The PyLoT Development Team (https://ariadne.geophysik.rub.de/trac/PyLoT) +:license: + GNU Lesser General Public License, Version 3 + (http://www.gnu.org/copyleft/lesser.html) +""" + +import os +import sys + +import matplotlib + +matplotlib.use('Qt4Agg') +matplotlib.rcParams['backend.qt4'] = 'PySide' + +from PySide.QtCore import QCoreApplication, QSettings, Signal, QFile, \ + QFileInfo, Qt +from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \ + QWidget, QHBoxLayout, QStyle, QKeySequence, QLabel, QFrame, QAction, \ + QDialog, QErrorMessage, QApplication, QPixmap, QMessageBox, QSplashScreen, \ + QActionGroup, QListWidget, QDockWidget, QLineEdit +import numpy as np +from obspy import UTCDateTime + +from pylot.core.analysis.magnitude import RichterMagnitude, MomentMagnitude +from pylot.core.io.data import Data +from pylot.core.io.inputs import FilterOptions, AutoPickParameter +from pylot.core.pick.autopick import autopickevent +from pylot.core.pick.compare import Comparison +from pylot.core.pick.utils import symmetrize_error +from pylot.core.io.phases import picksdict_from_picks +import pylot.core.loc.nll as nll +from pylot.core.util.defaults import FILTERDEFAULTS, COMPNAME_MAP, \ + AUTOMATIC_DEFAULTS +from pylot.core.util.errors import FormatError, DatastructureError, \ + OverwriteError, ProcessingError +from pylot.core.util.connection import checkurl +from pylot.core.util.dataprocessing import read_metadata, restitute_data +from pylot.core.util.utils import fnConstructor, getLogin, \ + full_range +from pylot.core.io.location import create_creation_info, create_event +from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \ + WaveformWidget, PropertiesDlg, HelpForm, createAction, PickDlg, \ + getDataType, ComparisonDialog +from pylot.core.util.structure import DATASTRUCTURE +from pylot.core.util.thread import AutoPickThread +from pylot.core.util.version import get_git_version as _getVersionString +import icons_rc + +locateTool = dict(nll=nll) + + +class MainWindow(QMainWindow): + __version__ = _getVersionString() + closing = Signal() + + def __init__(self, parent=None): + super(MainWindow, self).__init__(parent) + + self.createAction = createAction + # read settings + settings = QSettings() + infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in') + self._inputs = AutoPickParameter(infile) + if settings.value("user/FullName", None) is None: + fulluser = QInputDialog.getText(self, "Enter Name:", "Full name") + settings.setValue("user/FullName", fulluser) + settings.setValue("user/Login", getLogin()) + if settings.value("agency_id", None) is None: + agency = QInputDialog.getText(self, + "Enter authority name (e.g. BUG):", + "Authority") + settings.setValue("agency_id", agency) + self.recentfiles = settings.value("data/recentEvents", []) + self.fname = dict(manual=None, auto=None, loc=None) + self.fnames = None + self._stime = None + structure_setting = settings.value("data/Structure", "PILOT") + self.dataStructure = DATASTRUCTURE[structure_setting]() + self.seismicPhase = str(settings.value("phase", "P")) + self.dispComponent = str(settings.value("plotting/dispComponent", "Z")) + if settings.value("data/dataRoot", None) is None: + dirname = QFileDialog().getExistingDirectory( + caption='Choose data root ...') + settings.setValue("data/dataRoot", dirname) + settings.sync() + + self.filteroptions = {} + self.pickDlgs = {} + self.picks = {} + self.autopicks = {} + self.loc = False + + # UI has to be set up before(!) children widgets are about to show up + self.setupUi() + + # initialize event data + if self.recentfiles: + lastEvent = self.getLastEvent() + self.data = Data(self, lastEvent) + else: + self.data = Data(self) + self.autodata = Data(self) + + # load and display waveform data + self.dirty = False + self.load_data() + finv = settings.value("inventoryFile", None) + if finv is not None: + self._metadata = read_metadata(finv) + else: + self._metadata = None + if self.loadWaveformData(): + self.updateFilterOptions() + else: + sys.exit(0) + + def setupUi(self): + + try: + self.startTime = min( + [tr.stats.starttime for tr in self.data.wfdata]) + except: + self.startTime = UTCDateTime() + + pylot_icon = QIcon() + pylot_icon.addPixmap(QPixmap(':/icons/pylot.png')) + + self.setWindowTitle("PyLoT - do seismic processing the python way") + self.setWindowIcon(pylot_icon) + + xlab = self.startTime.strftime('seconds since %Y/%m/%d %H:%M:%S (%Z)') + + _widget = QWidget() + _widget.setCursor(Qt.CrossCursor) + _layout = QHBoxLayout() + + plottitle = "Overview: {0} components ".format(self.getComponent()) + + # create central matplotlib figure canvas widget + self.DataPlot = WaveformWidget(parent=self, xlabel=xlab, ylabel=None, + title=plottitle) + self.DataPlot.mpl_connect('button_press_event', + self.pickOnStation) + self.DataPlot.mpl_connect('axes_enter_event', + lambda event: self.tutor_user()) + _layout.addWidget(self.DataPlot) + + manupicksicon = self.style().standardIcon(QStyle.SP_DialogYesButton) + autopicksicon = self.style().standardIcon(QStyle.SP_DialogNoButton) + locactionicon = self.style().standardIcon(QStyle.SP_DirOpenIcon) + loadpiloticon = self.style().standardIcon(QStyle.SP_ComputerIcon) + quitIcon = self.style().standardIcon(QStyle.SP_MediaStop) + saveIcon = self.style().standardIcon(QStyle.SP_DriveHDIcon) + helpIcon = self.style().standardIcon(QStyle.SP_DialogHelpButton) + newIcon = self.style().standardIcon(QStyle.SP_FileIcon) + + # create resource icons + p_icon = QIcon() + p_icon.addPixmap(QPixmap(':/icons/key_P.png')) + s_icon = QIcon() + s_icon.addPixmap(QPixmap(':/icons/key_S.png')) + print_icon = QIcon() + print_icon.addPixmap(QPixmap(':/icons/printer.png')) + filter_icon = QIcon() + filter_icon.addPixmap(QPixmap(':/icons/filter.png')) + z_icon = QIcon() + z_icon.addPixmap(QPixmap(':/icons/key_Z.png')) + n_icon = QIcon() + n_icon.addPixmap(QPixmap(':/icons/key_N.png')) + e_icon = QIcon() + e_icon.addPixmap(QPixmap(':/icons/key_E.png')) + auto_icon = QIcon() + auto_icon.addPixmap(QPixmap(':/icons/sync.png')) + locate_icon = QIcon() + locate_icon.addPixmap(QPixmap(':/icons/locate.png')) + compare_icon = QIcon() + compare_icon.addPixmap(QPixmap(':/icons/compare.png')) + + newEventAction = self.createAction(self, "&New event ...", + self.createNewEvent, + QKeySequence.New, newIcon, + "Create a new event.") + openmanualpicksaction = self.createAction(self, "Load &picks ...", + self.load_data, + QKeySequence.Open, + manupicksicon, + "Load pick data for " + "the actual event.") + openmanualpicksaction.setData(None) + openautopicksaction = self.createAction(self, "Load &automatic picks " + "...", + self.load_autopicks, + "Ctrl+A", + autopicksicon, + "Load automatic pick data " + "for the actual event.") + openautopicksaction.setData(None) + + loadlocationaction = self.createAction(self, "Load &location ...", + self.load_loc, "Ctrl+L", + locactionicon, + "Load location information on " + "the actual event.") + loadpilotevent = self.createAction(self, "Load PILOT &event ...", + self.load_pilotevent, "Ctrl+E", + loadpiloticon, + "Load PILOT event from information " + "Matlab binary collections.") + saveEventAction = self.createAction(self, "&Save event ...", + self.saveData, QKeySequence.Save, + saveIcon, "Save actual event data.") + openWFDataAction = self.createAction(self, "Open &waveforms ...", + self.loadWaveformData, + "Ctrl+W", QIcon(":/wfIcon.png"), + "Open waveform data (event will " + "be closed)") + prefsEventAction = self.createAction(self, "Preferences", + self.PyLoTprefs, + QKeySequence.Preferences, + QIcon(None), + "Edit PyLoT app preferences.") + quitAction = self.createAction(self, "&Quit", + QCoreApplication.instance().quit, + QKeySequence.Close, quitIcon, + "Close event and quit PyLoT") + self.filterAction = self.createAction(self, "&Filter ...", + self.filterWaveformData, + "Ctrl+F", filter_icon, + """Toggle un-/filtered waveforms + to be displayed, according to the + desired seismic phase.""", True) + filterEditAction = self.createAction(self, "&Filter parameter ...", + self.adjustFilterOptions, + "Alt+F", QIcon(None), + """Adjust filter parameters.""") + self.selectPAction = self.createAction(self, "&P", self.alterPhase, + "Alt+P", + p_icon, + "Toggle P phase.", True) + self.selectSAction = self.createAction(self, "&S", self.alterPhase, + "Alt+S", + s_icon, + "Toggle S phase", True) + self.compare_action = self.createAction(self, "&Compare picks...", + self.comparePicks, "Alt+C", + compare_icon, "Comparison of " + "manual and " + "automatic pick " + "data.", False) + printAction = self.createAction(self, "&Print event ...", + self.show_event_information, QKeySequence.Print, + print_icon, + "Print waveform overview.") + helpAction = self.createAction(self, "&Help ...", self.helpHelp, + QKeySequence.HelpContents, helpIcon, + """Show either the documentation + homepage (internet connection available), + or shipped documentation files.""") + self.fileMenu = self.menuBar().addMenu('&File') + self.fileMenuActions = (newEventAction, openmanualpicksaction, + saveEventAction, openWFDataAction, None, + prefsEventAction, quitAction) + self.fileMenu.aboutToShow.connect(self.updateFileMenu) + self.updateFileMenu() + + self.editMenu = self.menuBar().addMenu('&Edit') + editActions = (self.filterAction, filterEditAction, None, + self.selectPAction, self.selectSAction, None, + printAction) + self.addActions(self.editMenu, editActions) + + self.helpMenu = self.menuBar().addMenu('&Help') + helpActions = (helpAction,) + self.addActions(self.helpMenu, helpActions) + + fileToolBar = self.addToolBar("FileTools") + fileToolActions = (newEventAction, openmanualpicksaction, + openautopicksaction, loadlocationaction, + loadpilotevent, saveEventAction) + fileToolBar.setObjectName("FileTools") + self.addActions(fileToolBar, fileToolActions) + + # phaseToolBar = self.addToolBar("PhaseTools") + # phaseToolActions = (self.selectPAction, self.selectSAction) + # phaseToolBar.setObjectName("PhaseTools") + # self.addActions(phaseToolBar, phaseToolActions) + + # create button group for component selection + + componentGroup = QActionGroup(self) + componentGroup.setExclusive(True) + + z_action = self.createAction(parent=componentGroup, text='Z', + slot=self.plotZ, shortcut='Alt+Z', + icon=z_icon, tip='Display the vertical (Z)' + ' component.', + checkable=True) + z_action.setChecked(True) + + n_action = self.createAction(parent=componentGroup, text='N', + slot=self.plotN, shortcut='Alt+N', + icon=n_icon, + tip='Display the north-south (N) ' + 'component.', checkable=True) + + e_action = self.createAction(parent=componentGroup, text='E', + slot=self.plotE, shortcut='Alt+E', + icon=e_icon, + tip='Display the east-west (E) component.', + checkable=True) + + componentToolBar = self.addToolBar("ComponentSelection") + componentActions = (z_action, n_action, e_action) + componentToolBar.setObjectName("PhaseTools") + self.addActions(componentToolBar, componentActions) + + auto_pick = self.createAction(parent=self, text='autoPick', + slot=self.autoPick, shortcut='Alt+Ctrl+A', + icon=auto_icon, tip='Automatically pick' + ' the entire dataset' + ' displayed!') + + autoPickToolBar = self.addToolBar("autoPyLoT") + autoPickActions = (auto_pick, self.compare_action) + self.addActions(autoPickToolBar, autoPickActions) + + # pickToolBar = self.addToolBar("PickTools") + # pickToolActions = (selectStation, ) + # pickToolBar.setObjectName("PickTools") + # self.addActions(pickToolBar, pickToolActions) + + locateEvent = self.createAction(parent=self, text='locate_event', + slot=self.locate_event, + shortcut='Alt+Ctrl+L', + icon=locate_icon, + tip='Locate the event using ' + 'the picked arrivals.') + + locationToolBar = self.addToolBar("LocationTools") + locationToolActions = (locateEvent,) + locationToolBar.setObjectName("LocationTools") + self.addActions(locationToolBar, locationToolActions) + + self.eventLabel = QLabel() + self.eventLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) + status = self.statusBar() + status.setSizeGripEnabled(False) + status.addPermanentWidget(self.eventLabel) + status.showMessage("Ready", 500) + + _widget.setLayout(_layout) + _widget.showFullScreen() + + self.setCentralWidget(_widget) + + + @property + def metadata(self): + return self._metadata + + + @metadata.setter + def metadata(self, value): + self._metadata = value + + + def updateFileMenu(self): + + self.fileMenu.clear() + for action in self.fileMenuActions[:-1]: + if action is None: + self.fileMenu.addSeparator() + else: + self.fileMenu.addAction(action) + try: + current = self.data.getID() + except AttributeError: + current = None + recentEvents = [] + for eventID in self.recentfiles: + fname = fnConstructor(eventID) + if eventID != current and QFile.exists(fname): + recentEvents.append(eventID) + recentEvents.reverse() + self.recentfiles = recentEvents[0:5] + settings = QSettings() + settings.setValue() + if recentEvents: + for i, eventID in enumerate(recentEvents): + fname = fnConstructor(eventID) + action = QAction(self.windowIcon(), + "&{0} {1}".format(i + 1, + QFileInfo(fname).fileName()), + self) + action.setData(fname) + self.connect(action, Signal("triggered()"), + self.load_data) + self.fileMenu.addAction(action) + self.fileMenu.addSeparator() + self.fileMenu.addAction(self.fileMenuActions[-1]) + + @property + def inputs(self): + return self._inputs + + def getRoot(self): + settings = QSettings() + return settings.value("data/dataRoot") + + def load_autopicks(self, fname=None): + self.load_data(fname, type='auto') + + def load_loc(self, fname=None): + type = getDataType(self) + self.load_data(fname, type=type, loc=True) + + def load_pilotevent(self): + filt = "PILOT location files (*LOC*.mat)" + caption = "Select PILOT location file" + fn_loc = QFileDialog().getOpenFileName(self, caption=caption, + filter=filt, dir=self.getRoot()) + fn_loc = fn_loc[0] + loc_dir = os.path.split(fn_loc)[0] + filt = "PILOT phases files (*PHASES*.mat)" + caption = "Select PILOT phases file" + fn_phases = QFileDialog().getOpenFileName(self, caption=caption, + filter=filt, dir=loc_dir) + fn_phases = fn_phases[0] + + type = getDataType(self) + + fname_dict = dict(phasfn=fn_phases, locfn=fn_loc) + self.load_data(fname_dict, type=type) + + def load_data(self, fname=None, type='manual', loc=False): + if not self.okToContinue(): + return + if fname is None: + action = self.sender() + if isinstance(action, QAction): + fname = self.filename_from_action(action) + self.set_fname(fname, type) + data = dict(auto=self.autodata, manual=self.data) + data[type] += Data(self, evtdata=fname) + if not loc: + self.updatePicks(type=type) + self.drawPicks(picktype=type) + self.draw() + + def getLastEvent(self): + return self.recentfiles[0] + + def add_recentfile(self, event): + self.recentfiles.insert(0, event) + + def getWFFnames(self): + try: + evt = self.get_data().get_evt_data() + if evt.picks: + for pick in evt.picks: + try: + if pick.waveform_id is not None: + fname = pick.waveform_id.getSEEDstring() + if fname not in self.fnames: + self.fnames.append(fname) + except: + continue + else: + if self.dataStructure: + searchPath = self.dataStructure.expandDataPath() + fnames = QFileDialog.getOpenFileNames(self, + "Select waveform " + "files:", + dir=searchPath) + self.fnames = fnames[0] + + else: + raise DatastructureError('not specified') + if not self.fnames: + return None + return self.fnames + except DatastructureError as e: + print(e) + props = PropertiesDlg(self) + if props.exec_() == QDialog.Accepted: + return self.getWFFnames() + else: + return + + def filename_from_action(self, action): + if action.data() is None: + filt = "Supported file formats" \ + " (*.mat *.qml *.xml *.kor *.evt)" + caption = "Open an event file" + fname = QFileDialog().getOpenFileName(self, caption=caption, + filter=filt, + dir=self.getRoot()) + fname = fname[0] + else: + fname = str(action.data().toString()) + return fname + + def get_fnames(self, type='manual'): + return self.fname[type] + + def set_fname(self, fname, type): + if self.get_fnames(type) is not None: + self.add_recentfile(self.get_fnames(type)) + self.fname[type] = fname + + def getEventFileName(self, type='manual'): + if self.get_fnames(type) is None: + self.set_fname(self.get_data().getEventFileName(), type) + return self.get_fnames(type) + + def saveData(self): + + def getSavePath(e): + print('warning: {0}'.format(e)) + directory = os.path.realpath(self.getRoot()) + file_filter = "QuakeML file (*.xml);;VELEST observation file " \ + "format (*.cnv);;NonLinLoc observation file (*.obs)" + title = 'Save event data ...' + fname, selected_filter = QFileDialog.getSaveFileName(self, + title, + directory, + file_filter) + + fbasename, exform = os.path.splitext(fname) + + if not exform and selected_filter: + exform = selected_filter.split('*')[1][:-1] + + return fbasename, exform + + settings = QSettings() + fbasename = self.getEventFileName() + exform = settings.value('data/exportFormat', 'QUAKEML') + try: + self.get_data().applyEVTData(self.getPicks()) + except OverwriteError: + msgBox = QMessageBox() + msgBox.setText("Picks have been modified!") + msgBox.setInformativeText( + "Do you want to save the changes and overwrite the picks?") + msgBox.setDetailedText(self.get_data().getPicksStr()) + msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Save) + ret = msgBox.exec_() + if ret == QMessageBox.Save: + self.get_data().resetPicks() + return self.saveData() + elif ret == QMessageBox.Cancel: + return False + try: + self.get_data().exportEvent(fbasename, exform) + except FormatError as e: + fbasename, exform = getSavePath(e) + except AttributeError as e: + fbasename, exform = getSavePath(e) + + # catch all possible cases before going on + if not fbasename: + return False + # warn overwriting + elif os.path.exists(fbasename + exform): + ans = QMessageBox.question(self, self.tr("Overwrite file..."), + self.tr("File already exists: {0}\n".format(fbasename + exform) + \ + "Overwrite file anyway?"), + QMessageBox.Cancel, QMessageBox.Yes | QMessageBox.No, + QMessageBox.Cancel) + # only negative answers have to be caught + if ans == QMessageBox.No: + self.saveData() + elif ans == QMessageBox.Cancel: + return False + + # export to given path + self.get_data().exportEvent(fbasename, exform) + # all files save (ui clean) + self.setDirty(False) + self.update_status('Event saved as %s' % (fbasename + exform)) + return True + + def getComponent(self): + return self.dispComponent + + def setComponent(self, component): + self.dispComponent = component + + def get_data(self, type='manual'): + if type == 'auto': + return self.autodata + return self.data + + def getPicks(self, type='manual'): + rdict = dict(auto=self.autopicks, manual=self.picks) + return rdict[type] + + def getPicksOnStation(self, station, type='manual'): + try: + return self.getPicks(type)[station] + except KeyError: + return None + + def comparePicks(self): + if self.check4Comparison(): + co = Comparison(auto=self.getPicks('auto'), manu=self.getPicks()) + compare_dlg = ComparisonDialog(co, self) + compare_dlg.exec_() + + def getPlotWidget(self): + return self.DataPlot + + @staticmethod + def getWFID(gui_event): + + ycoord = gui_event.ydata + + try: + statID = int(round(ycoord)) + except TypeError as e: + if 'a float is required' in e.message: + return None + else: + raise e + + return statID + + def getStationID(self, station): + for wfID in self.getPlotWidget().getPlotDict().keys(): + actual_station = self.getPlotWidget().getPlotDict()[wfID][0] + if station == actual_station: + return wfID + return None + + def getStime(self): + return self._stime + + def addActions(self, target, actions): + for action in actions: + if action is None: + target.addSeparator() + else: + target.addAction(action) + + def okToContinue(self): + if self.dirty: + return self.saveData() + return True + + def loadWaveformData(self): + if self.fnames and self.okToContinue(): + self.setDirty(True) + ans = self.data.setWFData(self.fnames) + elif self.fnames is None and self.okToContinue(): + ans = self.data.setWFData(self.getWFFnames()) + else: + ans = False + self._stime = full_range(self.get_data().getWFData())[0] + if ans: + self.plotWaveformData() + return ans + else: + return ans + + def plotWaveformData(self): + zne_text = {'Z': 'vertical', 'N': 'north-south', 'E': 'east-west'} + comp = self.getComponent() + title = 'section: {0} components'.format(zne_text[comp]) + alter_comp = COMPNAME_MAP[comp] + wfst = self.get_data().getWFData().select(component=comp) + wfst += self.get_data().getWFData().select(component=alter_comp) + self.getPlotWidget().plotWFData(wfdata=wfst, title=title, mapping=False) + self.draw() + plotDict = self.getPlotWidget().getPlotDict() + pos = plotDict.keys() + labels = [plotDict[n][0] for n in pos] + self.getPlotWidget().setYTickLabels(pos, labels) + + def plotZ(self): + self.setComponent('Z') + self.plotWaveformData() + self.drawPicks() + self.draw() + + def plotN(self): + self.setComponent('N') + self.plotWaveformData() + self.drawPicks() + self.draw() + + def plotE(self): + self.setComponent('E') + self.plotWaveformData() + self.drawPicks() + self.draw() + + def pushFilterWF(self, param_args): + self.get_data().filterWFData(param_args) + + def filterWaveformData(self): + if self.get_data(): + if self.getFilterOptions() and self.filterAction.isChecked(): + kwargs = self.getFilterOptions().parseFilterOptions() + self.pushFilterWF(kwargs) + elif self.filterAction.isChecked(): + self.adjustFilterOptions() + else: + self.get_data().resetWFData() + self.plotWaveformData() + self.drawPicks() + self.draw() + + def adjustFilterOptions(self): + fstring = "Filter Options ({0})".format(self.getSeismicPhase()) + filterDlg = FilterOptionsDialog(titleString=fstring, + parent=self) + if filterDlg.exec_(): + filteroptions = filterDlg.getFilterOptions() + self.setFilterOptions(filteroptions) + if self.filterAction.isChecked(): + kwargs = self.getFilterOptions().parseFilterOptions() + self.pushFilterWF(kwargs) + self.plotWaveformData() + + def getFilterOptions(self): + try: + return self.filteroptions[self.getSeismicPhase()] + except AttributeError as e: + print(e) + return FilterOptions(None, None, None) + + def getFilters(self): + return self.filteroptions + + def setFilterOptions(self, filterOptions, seismicPhase=None): + if seismicPhase is None: + self.getFilters()[self.getSeismicPhase()] = filterOptions + else: + self.getFilters()[seismicPhase] = filterOptions + + def updateFilterOptions(self): + try: + settings = QSettings() + if settings.value("filterdefaults", + None) is None and not self.getFilters(): + for key, value in FILTERDEFAULTS.items(): + self.setFilterOptions(FilterOptions(**value), key) + elif settings.value("filterdefaults", None) is not None: + for key, value in settings.value("filterdefaults"): + self.setFilterOptions(FilterOptions(**value), key) + except Exception as e: + self.update_status('Error ...') + emsg = QErrorMessage(self) + emsg.showMessage('Error: {0}'.format(e)) + else: + self.update_status('Filter loaded ... ' + '[{0}: {1} Hz]'.format( + self.getFilterOptions().getFilterType(), + self.getFilterOptions().getFreq())) + if self.filterAction.isChecked(): + self.filterWaveformData() + + def getSeismicPhase(self): + return self.seismicPhase + + def getStationName(self, wfID): + return self.getPlotWidget().getPlotDict()[wfID][0] + + def alterPhase(self): + pass + + def setSeismicPhase(self, phase): + self.seismicPhase = self.seismicPhaseButtonGroup.getValue() + self.update_status('Seismic phase changed to ' + '{0}'.format(self.getSeismicPhase())) + + def pickOnStation(self, gui_event): + + wfID = self.getWFID(gui_event) + + if wfID is None: return + + station = self.getStationName(wfID) + self.update_status('picking on station {0}'.format(station)) + data = self.get_data().getWFData() + pickDlg = PickDlg(self, data=data.select(station=station), + station=station, + picks=self.getPicksOnStation(station)) + if pickDlg.exec_(): + self.setDirty(True) + self.update_status('picks accepted ({0})'.format(station)) + replot = self.addPicks(station, pickDlg.getPicks()) + if replot: + self.plotWaveformData() + self.drawPicks() + self.draw() + else: + self.drawPicks(station) + self.draw() + else: + self.update_status('picks discarded ({0})'.format(station)) + if not self.get_loc_flag() and self.check4Loc(): + self.set_loc_flag(True) + elif self.get_loc_flag() and not self.check4Loc(): + self.set_loc_flag(False) + + def addListItem(self, text): + self.listWidget.addItem(text) + self.listWidget.scrollToBottom() + + def autoPick(self): + self.listWidget = QListWidget() + self.setDirty(True) + self.logDockWidget = QDockWidget("AutoPickLog", self) + self.logDockWidget.setObjectName("LogDockWidget") + self.logDockWidget.setAllowedAreas( + Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) + self.logDockWidget.setWidget(self.listWidget) + self.addDockWidget(Qt.LeftDockWidgetArea, self.logDockWidget) + self.addListItem('loading default values for local data ...') + # may become obsolete if generalized input parameter a read from disc + # during initialization + # TODO double check for obsolete read in of parameters + autopick_parameter = AutoPickParameter(AUTOMATIC_DEFAULTS) + self.addListItem(str(autopick_parameter)) + + # Create the worker thread and run it + self.thread = AutoPickThread(parent=self, + func=autopickevent, + data=self.get_data().getWFData(), + param=autopick_parameter) + self.thread.message.connect(self.addListItem) + self.thread.start() + self.thread.finished.connect(self.finalizeAutoPick) + + def finalizeAutoPick(self): + self.drawPicks(picktype='auto') + self.draw() + self.thread.quit() + + def addPicks(self, station, picks, type='manual'): + stat_picks = self.getPicksOnStation(station, type) + rval = False + if not stat_picks: + stat_picks = picks + else: + msgBox = QMessageBox() + msgBox.setText("The picks for station {0} have been " + "changed.".format(station)) + msgBox.setDetailedText("Old picks:\n" + "{old_picks}\n\n" + "New picks:\n" + "{new_picks}".format(old_picks=stat_picks, + new_picks=picks)) + msgBox.setInformativeText("Do you want to save your changes?") + msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Save) + ret = msgBox.exec_() + if ret == QMessageBox.Save: + stat_picks = picks + rval = True + elif ret == QMessageBox.Cancel: + pass + else: + raise Exception('FATAL: Should never occur!') + self.getPicks(type=type)[station] = stat_picks + return rval + + def updatePicks(self, type='manual'): + picks = picksdict_from_picks(evt=self.get_data(type).get_evt_data()) + if type == 'manual': + self.picks.update(picks) + elif type == 'auto': + self.autopicks.update(picks) + self.check4Comparison() + + def drawPicks(self, station=None, picktype='manual'): + # if picks to draw not specified, draw all picks available + if not station: + for station in self.getPicks(type=picktype): + self.drawPicks(station, picktype=picktype) + return + # plotting picks + plotID = self.getStationID(station) + if not plotID: + return + ax = self.getPlotWidget().axes + ylims = np.array([-.5, +.5]) + plotID + phase_col = { + 'P': ('c', 'c--', 'b-', 'bv', 'b^'), + 'S': ('m', 'm--', 'r-', 'rv', 'r^') + } + + stat_picks = self.getPicks(type=picktype)[station] + + stime = self.getStime() + + for phase in stat_picks: + picks = stat_picks[phase] + if type(stat_picks[phase]) is not dict: + return + colors = phase_col[phase[0].upper()] + + mpp = picks['mpp'] - stime + epp = picks['epp'] - stime + lpp = picks['lpp'] - stime + spe = picks['spe'] + if not spe: + spe = symmetrize_error(mpp - epp, lpp - mpp) + + if picktype == 'manual': + ax.fill_between([epp, lpp], ylims[0], ylims[1], + alpha=.5, color=colors[0]) + ax.plot([mpp - spe, mpp - spe], ylims, colors[1], + [mpp, mpp], ylims, colors[2], + [mpp + spe, mpp + spe], ylims, colors[1]) + elif picktype == 'auto': + ax.plot(mpp, ylims[1], colors[3], + mpp, ylims[0], colors[4]) + else: + raise TypeError('Unknow picktype {0}'.format(picktype)) + + def locate_event(self): + """ + locate event using the manually picked phases + :return: + """ + if not self.okToContinue(): + return + settings = QSettings() + # get location tool hook + loctool = settings.value("loc/tool", "nll") + lt = locateTool[loctool] + # get working directory + locroot = settings.value("{0}/rootPath".format(loctool), None) + if locroot is None: + self.PyLoTprefs() + self.locate_event() + + 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) + if ans[0]: + infile = ans[0] + else: + QMessageBox.information(self, + self.tr('No infile selected'), + self.tr('Inputfile necessary for localization.')) + return + 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.get_data().applyEVTData(lt.read_location(locpath), type='event') + self.get_data().applyEVTData(self.calc_magnitude(), type='event') + + + def calc_magnitude(self, type='ML'): + def set_inv(settings): + fninv, _ = QFileDialog.getOpenFileName(self, self.tr( + "Select inventory..."), self.tr("Select file")) + if not fninv: + return False + ans = QMessageBox.question(self, self.tr("Make default..."), + self.tr( + "New inventory filename set.\n" + \ + "Do you want to make it the default value?"), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No) + if ans == QMessageBox.Yes: + settings.setValue("inventoryFile", fninv) + settings.sync() + self.metadata = read_metadata(fninv) + return True + + settings = QSettings() + fninv = settings.value("inventoryFile", None) + + if fninv is None and not self.metadata: + if not set_inv(settings): + return None + elif fninv is not None and not self.metadata: + ans = QMessageBox.question(self, self.tr("Use default..."), + self.tr( + "Do you want to use the default value?"), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes) + if ans == QMessageBox.No: + if not set_inv(settings): + return None + else: + self.metadata = read_metadata(fninv) + + wf_copy = self.get_data().getWFData().copy() + [corr_wf, rest_flag] = restitute_data(wf_copy, *self.metadata) + if not rest_flag: + raise ProcessingError('Restitution of waveform data failed!') + if type == 'ML': + local_mag = RichterMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('sstop'), verbosity = True) + return local_mag.updated_event() + elif type == 'Mw': + moment_mag = MomentMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('vp'), self.inputs.get('Qp'), self.inputs.get('rho'), verbosity = True) + return moment_mag.updated_event() + else: + return None + + def check4Loc(self): + return self.picksNum() > 4 + + def check4Comparison(self): + mpicks = self.getPicks() + apicks = self.getPicks('auto') + for station, phases in mpicks.items(): + try: + aphases = apicks[station] + for phase in phases.keys(): + if phase in aphases.keys(): + return True + except KeyError: + continue + return False + + def picksNum(self, type='manual'): + num = 0 + for phases in self.getPicks(type).values(): + num += len(phases) + return num + + def get_loc_flag(self): + return self.loc + + def set_loc_flag(self, value): + self.loc = value + + def check_loc_plt(self): + evt = self.get_data().get_evt_data() + if evt.origins and evt.magnitudes: + return True + return False + + def update_status(self, message, duration=5000): + self.statusBar().showMessage(message, duration) + if self.get_data() is not None: + if not self.get_data().isNew(): + self.setWindowTitle( + "PyLoT - processing event %s[*]" % self.get_data().getID()) + elif self.get_data().isNew(): + self.setWindowTitle("PyLoT - New event [*]") + else: + self.setWindowTitle( + "PyLoT - seismic processing the python way[*]") + self.setWindowModified(self.dirty) + + def tutor_user(self): + self.update_status('select trace to pick on station ...', 10000) + + def show_event_information(self): + pass + + def createNewEvent(self): + if self.okToContinue(): + new = NewEventDlg() + if new.exec_() != QDialog.Rejected: + evtpar = new.getValues() + cinfo = create_creation_info(agency_id=self.agency) + event = create_event(evtpar['origintime'], cinfo) + self.data = Data(self, evtdata=event) + self.setDirty(True) + + def draw(self): + self.getPlotWidget().draw() + + def setDirty(self, value): + self.dirty = value + + def closeEvent(self, event): + if self.okToContinue(): + self.closing.emit() + QMainWindow.closeEvent(self, event) + + def PyLoTprefs(self): + props = PropertiesDlg(self) + if props.exec_(): + return + + def helpHelp(self): + if checkurl(): + form = HelpForm( + 'https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/wiki') + else: + form = HelpForm(':/help.html') + form.show() + + +def main(): + # create the Qt application + pylot_app = QApplication(sys.argv) + pixmap = QPixmap(":/splash/splash.png") + splash = QSplashScreen(pixmap) + splash.show() + + app_icon = QIcon() + app_icon.addPixmap(QPixmap(':/icons/pylot.png')) + + # create the main window + pylot_form = MainWindow() + splash.showMessage('Loading. Please wait ...') + pylot_app.processEvents() + + # set Application Information + pylot_app.setOrganizationName("Ruhr-University Bochum / MAGS2") + pylot_app.setOrganizationDomain("rub.de") + pylot_app.processEvents() + pylot_app.setApplicationName("PyLoT") + pylot_app.setApplicationVersion(pylot_form.__version__) + pylot_app.setWindowIcon(app_icon) + pylot_app.processEvents() + + # Show main window and run the app + pylot_form.showMaximized() + pylot_app.processEvents() + + splash.finish(pylot_form) + pylot_app.exec_() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/README.md b/README.md index 2d3db0ec..be3e6e49 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,92 @@ # PyLoT -Python picking and Location Tool + +version: 0.1a + +The Python picking and Localisation Tool + +This python library contains a graphical user interfaces for picking +seismic phases. This software needs [ObsPy][ObsPy] +and the PySide Qt4 bindings for python to be installed first. + +PILOT has originally been developed in Mathworks' MatLab. In order to +distribute PILOT without facing portability problems, it has been decided +to redevelop the software package in Python. The great work of the ObsPy +group allows easy handling of a bunch of seismic data and PyLoT will +benefit a lot compared to the former MatLab version. + +The development of PyLoT is part of the joint research project MAGS2. + +##Installation + +At the moment there is no automatic installation procedure available for PyLoT. +Best way to install is to clone the repository and add the path to your Python path. + +####prerequisites: + +In order to run PyLoT you need to install: + +- python +- scipy +- numpy +- matplotlib +- obspy +- pyside + +####some handwork + +PyLoT needs a properties folder on your system to work. It should be situated in your home directory: + + mkdir ~/.pylot + +In the next step you have to copy some files to this directory: + + cp path-to-pylot/inputs/pylot.in ~/.pylot/ + +for local distance seismicity + + cp path-to-pylot/inputs/autoPyLoT_local.in ~/.pylot/autoPyLoT.in + +for regional distance seismicity + + cp path-to-pylot/inputs/autoPyLoT_regional.in ~/.pylot/autoPyLoT.in + +and some extra information on filtering, error estimates (just needed for reading old PILOT data) and the Richter magnitude scaling relation + + cp path-to-pylot/inputs/filter.in path-to-pylot/inputs/PILOT_TimeErrors.in path-to-pylot/inputs/richter_scaling.data ~/.pylot/ + +You may need to do some modifications to these files. Especially folder names should be reviewed. + +PyLoT has been tested on Mac OSX (10.11) and Debian Linux 8. + + +##release notes: +============== + +#### Features + +- consistent manual phase picking through predefined SNR dependant zoom level +- uniform uncertainty estimation from waveform's properties for automatic and manual picks +- pdf representation and comparison of picks taking the uncertainty intrinsically into account +- Richter and moment magnitude estimation +- location determination with external installation of [NonLinLoc](http://alomax.free.fr/nlloc/index.html) + +#### Known issues + +- Magnitude estimation from manual PyLoT takes some time (instrument correction) + +We hope to solve these with the next release. + +####staff: +====== + +original author(s): L. Kueperkoch, S. Wehling-Benatelli, M. Bischoff (PILOT) + +developer(s): S. Wehling-Benatelli, L. Kueperkoch, K. Olbert, M. Bischoff, + C. Wollin, M. Rische, M. Paffrath + +others: A. Bruestle, T. Meier, W. Friederich + + +[ObsPy]: http://github.com/obspy/obspy/wiki + +October 2016 diff --git a/autoPyLoT.py b/autoPyLoT.py new file mode 100755 index 00000000..8c81b51d --- /dev/null +++ b/autoPyLoT.py @@ -0,0 +1,270 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import print_function + +import argparse +import glob +import os + +from obspy import read_events + +import pylot.core.loc.hsat as hsat +import pylot.core.loc.nll as nll +from pylot.core.analysis.magnitude import MomentMagnitude, RichterMagnitude +from pylot.core.io.data import Data +from pylot.core.io.inputs import AutoPickParameter +from pylot.core.pick.autopick import autopickevent, iteratepicker +from pylot.core.util.dataprocessing import restitute_data, read_metadata, \ + remove_underscores +from pylot.core.util.structure import DATASTRUCTURE +from pylot.core.util.version import get_git_version as _getVersionString + +__version__ = _getVersionString() + + +def autoPyLoT(inputfile): + """ + Determine phase onsets automatically utilizing the automatic picking + algorithms by Kueperkoch et al. 2010/2012. + + :param inputfile: path to the input file containing all parameter + information for automatic picking (for formatting details, see. + `~pylot.core.io.inputs.AutoPickParameter` + :type inputfile: str + :return: + + .. rubric:: Example + + """ + splash = '''************************************\n + *********autoPyLoT starting*********\n + The Python picking and Location Tool\n + Version {version} 2015\n + \n + Authors:\n + S. Wehling-Benatelli (Ruhr-Universität Bochum)\n + L. Küperkoch (BESTEC GmbH, Landau i. d. Pfalz)\n + K. Olbert (Christian-Albrechts Universität zu Kiel)\n + ***********************************'''.format(version=_getVersionString()) + print(splash) + + # reading parameter file + + parameter = AutoPickParameter(inputfile) + + data = Data() + + evt = None + # getting information on data structure + + if parameter.hasParam('datastructure'): + datastructure = DATASTRUCTURE[parameter.get('datastructure')]() + dsfields = {'root': parameter.get('rootpath'), + 'dpath': parameter.get('datapath'), + 'dbase': parameter.get('database')} + + exf = ['root', 'dpath', 'dbase'] + + if parameter.hasParam('eventID'): + dsfields['eventID'] = parameter.get('eventID') + exf.append('eventID') + + datastructure.modifyFields(**dsfields) + datastructure.setExpandFields(exf) + + # check if default location routine NLLoc is available + if parameter.hasParam('nllocbin'): + locflag = 1 + # get NLLoc-root path + nllocroot = parameter.get('nllocroot') + # get path to NLLoc executable + nllocbin = parameter.get('nllocbin') + nlloccall = '%s/NLLoc' % nllocbin + # get name of phase file + phasef = parameter.get('phasefile') + phasefile = '%s/obs/%s' % (nllocroot, phasef) + # get name of NLLoc-control file + ctrf = parameter.get('ctrfile') + ctrfile = '%s/run/%s' % (nllocroot, ctrf) + # pattern of NLLoc ttimes from location grid + ttpat = parameter.get('ttpatter') + # pattern of NLLoc-output file + nllocoutpatter = parameter.get('outpatter') + maxnumit = 3 # maximum number of iterations for re-picking + else: + locflag = 0 + print(" !!! ") + print("!!No location routine available, autoPyLoT is running in non-location mode!!") + print("!!No source parameter estimation possible!!") + print(" !!! ") + + datapath = datastructure.expandDataPath() + if not parameter.hasParam('eventID'): + # multiple event processing + # read each event in database + events = [events for events in glob.glob(os.path.join(datapath, '*')) if os.path.isdir(events)] + else: + # single event processing + events = glob.glob(os.path.join(datapath, parameter.get('eventID'))) + for event in events: + data.setWFData(glob.glob(os.path.join(datapath, event, '*'))) + evID = os.path.split(event)[-1] + print('Working on event %s' % event) + print(data) + wfdat = data.getWFData() # all available streams + wfdat = remove_underscores(wfdat) + metadata = read_metadata(parameter.get('invdir')) + corr_dat, rest_flag = restitute_data(wfdat.copy(), *metadata) + ########################################################## + # !automated picking starts here! + picks = autopickevent(wfdat, parameter) + ########################################################## + # locating + if locflag == 1: + # write phases to NLLoc-phase file + nll.export(picks, phasefile) + + # For locating the event the NLLoc-control file has to be modified! + nllocout = '%s_%s' % (evID, nllocoutpatter) + # create comment line for NLLoc-control file + nll.modify_inputs(ctrf, nllocroot, nllocout, phasef, + ttpat) + + # locate the event + nll.locate(ctrfile) + + # !iterative picking if traces remained unpicked or occupied with bad picks! + # get theoretical onset times for picks with weights >= 4 + # in order to reprocess them using smaller time windows around theoretical onset + # get stations with bad onsets + badpicks = [] + for key in picks: + if picks[key]['P']['weight'] >= 4 or picks[key]['S']['weight'] >= 4: + badpicks.append([key, picks[key]['P']['mpp']]) + + # TODO keep code DRY (Don't Repeat Yourself) the following part is written twice + # suggestion: delete block and modify the later similar block to work properly + + if len(badpicks) == 0: + print("autoPyLoT: No bad onsets found, thus no iterative picking necessary!") + # get NLLoc-location file + locsearch = '%s/loc/%s.????????.??????.grid?.loc.hyp' % (nllocroot, nllocout) + if len(glob.glob(locsearch)) > 0: + # get latest NLLoc-location file if several are available + nllocfile = max(glob.glob(locsearch), key=os.path.getctime) + evt = read_events(nllocfile)[0] + # calculating seismic moment Mo and moment magnitude Mw + moment_mag = MomentMagnitude(corr_dat, evt, parameter.get('vp'), + parameter.get('Qp'), + parameter.get('rho'), True, 0) + # update pick with moment property values (w0, fc, Mo) + for station, props in moment_mag.moment_props.items(): + picks[station]['P'].update(props) + evt = moment_mag.updated_event() + local_mag = RichterMagnitude(corr_dat, evt, + parameter.get('sstop'), True, 0) + for station, amplitude in local_mag.amplitudes.items(): + picks[station]['S']['Ao'] = amplitude.generic_amplitude + evt = local_mag.updated_event() + else: + print("autoPyLoT: No NLLoc-location file available!") + print("No source parameter estimation possible!") + else: + # get theoretical P-onset times from NLLoc-location file + locsearch = '%s/loc/%s.????????.??????.grid?.loc.hyp' % (nllocroot, nllocout) + if len(glob.glob(locsearch)) > 0: + # get latest file if several are available + nllocfile = max(glob.glob(locsearch), key=os.path.getctime) + nlloccounter = 0 + while len(badpicks) > 0 and nlloccounter <= maxnumit: + nlloccounter += 1 + if nlloccounter > maxnumit: + print("autoPyLoT: Number of maximum iterations reached, stop iterative picking!") + break + print("autoPyLoT: Starting with iteration No. %d ..." % nlloccounter) + picks = iteratepicker(wfdat, nllocfile, picks, badpicks, parameter) + # write phases to NLLoc-phase file + nll.export(picks, phasefile) + # remove actual NLLoc-location file to keep only the last + os.remove(nllocfile) + # locate the event + nll.locate(ctrfile) + print("autoPyLoT: Iteration No. %d finished." % nlloccounter) + # get updated NLLoc-location file + nllocfile = max(glob.glob(locsearch), key=os.path.getctime) + # check for bad picks + badpicks = [] + for key in picks: + if picks[key]['P']['weight'] >= 4 or picks[key]['S']['weight'] >= 4: + badpicks.append([key, picks[key]['P']['mpp']]) + print("autoPyLoT: After iteration No. %d: %d bad onsets found ..." % (nlloccounter, \ + len(badpicks))) + if len(badpicks) == 0: + print("autoPyLoT: No more bad onsets found, stop iterative picking!") + nlloccounter = maxnumit + evt = read_events(nllocfile)[0] + # calculating seismic moment Mo and moment magnitude Mw + moment_mag = MomentMagnitude(corr_dat, evt, parameter.get('vp'), + parameter.get('Qp'), + parameter.get('rho'), True, 0) + # update pick with moment property values (w0, fc, Mo) + for station, props in moment_mag.moment_props.items(): + picks[station]['P'].update(props) + evt = moment_mag.updated_event() + local_mag = RichterMagnitude(corr_dat, evt, + parameter.get('sstop'), True, 0) + for station, amplitude in local_mag.amplitudes.items(): + picks[station]['S']['Ao'] = amplitude.generic_amplitude + evt = local_mag.updated_event() + net_mw = moment_mag.net_magnitude() + print("Network moment magnitude: %4.1f" % net_mw.mag) + else: + print("autoPyLoT: No NLLoc-location file available! Stop iteration!") + ########################################################## + # write phase files for various location routines + # HYPO71 + hypo71file = '%s/autoPyLoT_HYPO71.pha' % event + hsat.export(picks, hypo71file) + data.applyEVTData(picks) + if evt is not None: + data.applyEVTData(evt, 'event') + fnqml = '%s/autoPyLoT' % event + data.exportEvent(fnqml) + + endsplash = '''------------------------------------------\n' + -----Finished event %s!-----\n' + ------------------------------------------'''.format \ + (version=_getVersionString()) % evID + print(endsplash) + if locflag == 0: + print("autoPyLoT was running in non-location mode!") + + endsp = '''####################################\n + ************************************\n + *********autoPyLoT terminates*******\n + The Python picking and Location Tool\n + ************************************'''.format(version=_getVersionString()) + print(endsp) + + +if __name__ == "__main__": + from pylot.core.util.defaults import AUTOMATIC_DEFAULTS + # parse arguments + parser = argparse.ArgumentParser( + description='''autoPyLoT automatically picks phase onset times using higher order statistics, + autoregressive prediction and AIC''') + + parser.add_argument('-i', '-I', '--inputfile', type=str, + action='store', + help='''full path to the file containing the input + parameters for autoPyLoT''', + default=AUTOMATIC_DEFAULTS + ) + parser.add_argument('-v', '-V', '--version', action='version', + version='autoPyLoT ' + __version__, + help='show version information and exit') + + cla = parser.parse_args() + + autoPyLoT(str(cla.inputfile)) diff --git a/help/index.html b/help/index.html new file mode 100644 index 00000000..2ce2ef02 --- /dev/null +++ b/help/index.html @@ -0,0 +1,17 @@ +PyLoT - the Python picking and Localisation Tool + +

PyLoT is a program which is capable of picking seismic phases, +exporting these as numerous standard phase format and localize the corresponding +seismic event with external software as, e.g.:

+ +

Read more on the +PyLoT WikiPage.

+

Bug reports are very much appreciated and can also be delivered on our +PyLoT TracPage after +successful registration.

+ diff --git a/icons.qrc b/icons.qrc new file mode 100644 index 00000000..96b8126c --- /dev/null +++ b/icons.qrc @@ -0,0 +1,30 @@ + + + icons/pylot.ico + icons/pylot.png + icons/locate.png + icons/printer.png + icons/delete.png + icons/compare.png + icons/key_E.png + icons/key_N.png + icons/key_P.png + icons/key_Q.png + icons/key_R.png + icons/key_S.png + icons/key_T.png + icons/key_U.png + icons/key_V.png + icons/key_W.png + icons/key_Z.png + icons/filter.png + icons/sync.png + icons/zoom_0.png + icons/zoom_in.png + icons/zoom_out.png + splash/splash.png + + + help/index.html + + diff --git a/icons/compare.png b/icons/compare.png new file mode 100644 index 00000000..be28ca74 Binary files /dev/null and b/icons/compare.png differ diff --git a/icons/delete.png b/icons/delete.png new file mode 100755 index 00000000..74c37cf9 Binary files /dev/null and b/icons/delete.png differ diff --git a/icons/filter.png b/icons/filter.png new file mode 100644 index 00000000..1098d3ac Binary files /dev/null and b/icons/filter.png differ diff --git a/icons/key_E.png b/icons/key_E.png new file mode 100755 index 00000000..eb51a67d Binary files /dev/null and b/icons/key_E.png differ diff --git a/icons/key_N.png b/icons/key_N.png new file mode 100755 index 00000000..4a8e81ea Binary files /dev/null and b/icons/key_N.png differ diff --git a/icons/key_P.png b/icons/key_P.png new file mode 100755 index 00000000..46704bf4 Binary files /dev/null and b/icons/key_P.png differ diff --git a/icons/key_Q.png b/icons/key_Q.png new file mode 100755 index 00000000..6334af3d Binary files /dev/null and b/icons/key_Q.png differ diff --git a/icons/key_R.png b/icons/key_R.png new file mode 100755 index 00000000..2b5cb32a Binary files /dev/null and b/icons/key_R.png differ diff --git a/icons/key_S.png b/icons/key_S.png new file mode 100755 index 00000000..63de7ec7 Binary files /dev/null and b/icons/key_S.png differ diff --git a/icons/key_T.png b/icons/key_T.png new file mode 100755 index 00000000..7b725f4f Binary files /dev/null and b/icons/key_T.png differ diff --git a/icons/key_U.png b/icons/key_U.png new file mode 100755 index 00000000..87bac471 Binary files /dev/null and b/icons/key_U.png differ diff --git a/icons/key_V.png b/icons/key_V.png new file mode 100755 index 00000000..d51827b8 Binary files /dev/null and b/icons/key_V.png differ diff --git a/icons/key_W.png b/icons/key_W.png new file mode 100755 index 00000000..ee218f85 Binary files /dev/null and b/icons/key_W.png differ diff --git a/icons/key_Z.png b/icons/key_Z.png new file mode 100755 index 00000000..11099417 Binary files /dev/null and b/icons/key_Z.png differ diff --git a/icons/locate.png b/icons/locate.png new file mode 100755 index 00000000..6e628e44 Binary files /dev/null and b/icons/locate.png differ diff --git a/icons/printer.png b/icons/printer.png new file mode 100644 index 00000000..c5b92eb5 Binary files /dev/null and b/icons/printer.png differ diff --git a/icons/pylot.ico b/icons/pylot.ico new file mode 100644 index 00000000..81bd4cf5 Binary files /dev/null and b/icons/pylot.ico differ diff --git a/icons/pylot.png b/icons/pylot.png new file mode 100644 index 00000000..10f1fc94 Binary files /dev/null and b/icons/pylot.png differ diff --git a/icons/sync.png b/icons/sync.png new file mode 100755 index 00000000..fa9ea46d Binary files /dev/null and b/icons/sync.png differ diff --git a/icons/zoom_0.png b/icons/zoom_0.png new file mode 100755 index 00000000..e1617bc9 Binary files /dev/null and b/icons/zoom_0.png differ diff --git a/icons/zoom_in.png b/icons/zoom_in.png new file mode 100755 index 00000000..65de2a5b Binary files /dev/null and b/icons/zoom_in.png differ diff --git a/icons/zoom_out.png b/icons/zoom_out.png new file mode 100755 index 00000000..338e7b1e Binary files /dev/null and b/icons/zoom_out.png differ diff --git a/icons_rc.py b/icons_rc.py new file mode 100644 index 00000000..b5aef8d7 --- /dev/null +++ b/icons_rc.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created: Fr. Jun 3 09:37:04 2016 +# by: The Resource Compiler for PySide (Qt v4.8.6) +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore + +qt_resource_data = "\x00\x00\x9e\x04\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x02\x80\x00\x00\x01\x00\x08\x06\x00\x00\x00#\x95\xc7f\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00\x17\x12\x00\x00\x17\x12\x01g\x9f\xd2R\x00\x00\x00\x07tIME\x07\xdf\x07\x07\x090\x11\xbal\xcd}\x00\x00 \x00IDATx\xda\xec}w\x94\x5c\xe5y\xfe3w\xee\xf4\x99\x9d\xb2\xb3;\xdb\x9bV\xbb*+\xd1-\x0c\xa2\x83\xb0\x0d\x1cSbL\xe28v0v|lp\x0eq\x09\x04\x88c\x1cl\x9c\xe08\x80K\x82c\xe3\xb8!sL\x5c\xe4P\x02\x18\x04\x085TWm{/\xb3\xd3{\xbf\xf3\xfbC\xbf\xf7\xf3\x9d\xbb\xb3\xb3\xbb\xd2\xae\xb4\xda\xfd\x9est\xa4\x1d\xcd\xcc\xce|\xed}\xbe\xe7m\xaa|>\x9f\x07\x07\x07\x07\x07\x07\xc79\x8c|>\x8fT*\x85\xed\xdb\xb7#\x95J!\x97\xcb\xe1\xb6\xdbn\x83$I\x10\x04\x81\x0f\x10\x07\x87\x02|Wppppp\x9c\xf3P\xa9T\xc8\xe7\xf3\xc8\xe7\xf3P\xa9T\xc8\xe5r|P888\x01\xe4\xe0\xe0\xe0\xe0X\xee\xe0\x0e-\x0e\x0eN\x009888888888\x01\xe4\xe0\xe0\xe0\xe0X\xee\xe0* \x07\x07'\x80\x1c\x1c\x1c\x1c\x1c\x9c\xfcqppp\x02\xc8\xc1\xc1\xc1\xc1\xc1I \x07\x07\x07'\x80\x1c\x1c\x1c\x1c\x1c\x9c\xfcqpp\x02\xc8\xc1\xc1\xc1\xc1\xc1\xc1\x09 \x07\x07'\x80\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x9c\x00rppppp,%P!h\x0e\x0e\x0eN\x009888888888\x01\xe4\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\x04\x90\x83\x83\x83\x83\x83\xe3\x9c\x07w\x01spp\x02\xc8\xc1\xc1\xc1\xc1\xc1\x01\x95J\xc5\x07\x81\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\xe5\x0aI\x92\xb8\x02\xc8\xc1\xc1\x09 \x07\x07\x07\x07\xc7J\x05W\xfe888\x01\xe4\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\x04\x90\x83\x83\x83\x83c\xb9A\xe9\xfe\x95$\x89\x0f\x0a\x07\x07'\x80\x1c\x1c\x1c\x1c\x1c\xcb\x9d\x00\xcaI\x1f\x8f\x07\xe4\xe0\xe0\x04\x90\x83\x83\x83\x83c\x05\x10@\xe5\xcf<\x16\x90\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x15D\x02\xb9\x02\xc8\xc1\xc1\x09 \x07\x07\x07\x07\xc7\x0a \x7fr\xc5\x8f\x13@\x0e\x0eN\x0098888V\x08\x09\xe4\x04\x90\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x15D\xfe\xe4\xa4\x8fg\x01spp\x02\xc8\xc1\xc1\xc1\xc1\xb1\x02\x08 A\xa5Rq\x05\x90\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x95F\x029\x01\xe4\xe0\xe0\x04\x90\x83\x83\x83\x83c\x05\x91?N\x00988\x01\xe4\xe0\xe0\xe0\xe0X!\x04\x90\xc7\x00rpp\x02\xc8\xc1\xc1\xc1\xc1\xb1\x02\x09 \x95\x82\xe1\x0a \x07\x07'\x80\x1c\x1c\x1c\x1c\x1c+\x0cR\x9e+\x80\x1c\x1c\x9c\x00rpppp\xac(p\x05\x90\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x15F\xfc\xf2\x12'\x80\x1c\x1c\x9c\x00rpppp\xacH\x22\xc8\xc1\xc1\xc1\x09 \x07\x07\x07\x07\x07'\x80\x1c\x1c\x9c\x00\xf2!\xe0\xe0\xe0\xe0\xe0Xn\xe0\x9d@888\x01\xe4\xe0\xe0\xe0\xe0X\x81\xe0\x04\x90\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x15\x86\x5c.\xc7\x07\x81\x83\x83\x13@\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0eN\x0098888\x96-x+8\x0e\x0eN\x0098888V\x18x\x0c \x07\x07'\x80\x1c\x1c\x1c\x1c\x1c+\x0c\x5c\x01\xe4\xe0\xe0\x04\x90\x83\x83\x83\x83c\x85\x81+\x80\x1c\x1c\x9c\x00rpppp\xac \xf0:\x80\x1c\x1c\x9c\x00rpppp\xac@p\x02\xc8\xc1\xc1\x09 \x07\x07\x07\x07\xc7\x0a\x03\x8f\x01\xe4\xe0\xe0\x04\x90\x83\x83\x83\x83\x83\x13@\x0e\x0e\x0eN\x0098888\x963\xb8\x0b\x98\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x15\x04\x95J\x85L&\xc3\x07\x82\x83\x83\x13@\x0e\x0e\x0e\x0e\x8e\x95\x04\xde\x0b\x98\x83cf\x88\x8b\xf5\xc6$\xbds\x09~\xf1o\xb9\xf2\xbf\xf3\xf9<\xfb7\x07\xc7b\xedm\xf9:+\xb5\xc7\xe9y\xf4\xb7r\xbdrpp\xfc\x09\x14\xb3\xc8K\xd8,\xdf\xb3S~n\xe6\xf3y\xa8\xd5\xeai\xf6\xfc\x9c$\x80\xf9|\x1e\xd9l\x16*\x95\x0a\x92$A\x92\xa4i\xc6\x82c\xe1\x17\x92\xdc\xb0\x0a\x82P\xf0X\xb1\xe7pp\x9c*\xe9\xa3}M\xc6\xaa\xd4\xfe\xce\xe7\xf3\xc8\xe5r\x10\x04\x81\xad?A\x10\xa0V\xab\xa1R\xa9\x0a\x0e>\x0e\x8e\x85\x14\x1e\x94\x84j\xa9\x93>I\x92\x98ZI{\x8a\xdb\xcd\xe5\xb76%I\x82J\xa5*8\x17\xe9L\x14\x04\x81\xfd9\xe7\x08`6\x9bE6\x9b\x85$I\xc8d2H\xa5R\xecg2\x14\x1c\x0b\x07Z\xb8$IB:\x9d\x86$Il\x7fg\xb3Y\xf6\xff\xc5@\x97\xc1|>\x0f\xadV\x0b\xbd^\x0f\xbd^\x0f\xadV\x0b\x00\x9c\x04r,\x0a\x01\xa4\xbf\x97\xba\x0b8\x9b\xcd\x02\x002\x99\x0c\xb2\xd9,2\x99\x0c\xd2\xe94r\xb9\x5c\xc1e\x8bcy\x81\xe6W\xa3\xd1@\x14E\x98\xcdfh\xb5Zh\xb5\xda3J\xfc\xc5\x85\xfa2\xf4'\x1e\x8f#\x1c\x0e#\x1c\x0e#\x16\x8b!\x99L\x22\x97\xcbq\x02\xb8\xc0 \x05EN\xfch1i\xb5Zh4\x1ah4\x1ah\xb5Z\xf6\x98(\x8aP\xab\xd5\x10E\x11*\x95\x0a\x1a\x8d\xa6@1\xe4\xe0(\xa5R\xa4\xd3i\xa4\xd3i\xb6\xbf#\x91\x08\x92\xc9$\xbb\xe8\x15\x83Z\xadfF\xd8h4\xc2n\xb7\xa3\xbc\xbc\x1cv\xbb\x1d\x1a\x8d\x86\x0f,\xc7\xa2\x13\xac\x5c.\xb7\xa4/\x1a\x99L\x06\x89D\x02\x89D\x02\xa1P\x08\xe1p\x18\x89D\x02\x99L\x86\x13\xc0eL\x00\xb3\xd9,DQ\x84\xd5jEyy9\x9cN',\x16\x0b4\x1a\xcd\x19[\xaf\xe2B~\xa1X,\x86`0\x88\xee\xeenx\xbd^\xf6E\xe4j\x15\xc7\xc2\x19\xe4h4\xca\x16\x92\x5ce\x15E\x11\x06\x83\x01&\x93\x09v\xbb\x1dF\xa3\x11F\xa3\x11z\xbd\x1e:\x9d\x0ez\xbd\x9e\x91C\xadV\x0bA\x10\x18)\xe4\xf3\xc4QLU\xc9\xe5rH\xa7\xd3\x08\x06\x83\xe8\xeb\xeb\xc3\xe8\xe8(\xbbP\x90\xb2\x5c\x0c\xf1x\x1c{\xf7\xeeE,\x16Css3\xd6\xaf_\x8f\x96\x96\x16\x08\x82\x00\xbb\xdd^\xf2\xb5\x1c\x1c\xa7z9\x96\x9f\x93K\x95\x00\x92r\x9eN\xa7\x11\x8dF1>>\x8e\xae\xae.\xe6\xa5!\xdb\xc9\xb1\xfc\xce\xd2\xfd\xfb\xf7cxx\x18v\xbb\x1d\xabW\xaf\xc6\xfa\xf5\xeb!\x08\x02\x13e\xce\x19\x02H_(\x9b\xcd\x22\x16\x8badd\x04\x95\x95\x95\xb8\xfa\xea\xab\x0bT*\x8e\x85_Hr\xf7\x1b\x11\xc0L&\x83\x89\x89\x09\xc4\xe3qLNN\xe2\xd8\xb1c\xc8f\xb30\x1a\x8dp:\x9d\xb0\xdb\xed0\x18\x0c\xd0\xeb\xf50\x99L0\x9b\xcd0\x18\x0c\xd0\xe9t\xd0h4|\xce8f\xdc\xe3\xf1x\x1c\x13\x13\x13p8\x1c\xd8\xb4i\x13[+\xa5\x0e\xabl6\x8b\x1bo\xbc\x11\xdb\xb7o\xc7\x0f~\xf0\x03\x08\x82\x00\xadV\x0b\x9b\xcd\x06\x8b\xc5\xc2\x14i\x0e\x8e\x85Z\xab\xca\x8b\xf2RU\xd1\xc8\x0d\x98L&\x11\x0c\x06\x11\x0e\x87q\xe7\x9dw2e\x88\x0b'\xcbw\x8dn\xd9\xb2\x05G\x8e\x1c\xc1\xd3O?\x8d#G\x8e \x16\x8b\xa1\xac\xac\x0cF\xa3\x11:\x9d\xee\x8c\xb9\x81O\xfb\xe4\xa5\xa0\xc6t:\x8dx<\x8e\xa9\xa9)\xe8\xf5z\x98\xcdf\xee\xf6]dP\x1c\x95r\x9c+++\x0b~\x0e\x06\x83p\xbb\xdd\xd8\xbf\x7f?\xba\xba\xba \x08\x02\x0c\x06\x03l6\x1b\x1c\x0e\x07\xacV+\xca\xca\xca`6\x9ba4\x1a\xd9\xe2#U\x90\x83\x1fX\x14\xd7\xeb\xf3\xf9PYY\x09\x8b\xc52\xeb\xfe\xa6\xb8\xbf\xae\xae.<\xf7\xdcs\xe8\xe9\xe9\xc1\xaaU\xabP__\x8fh4\x0a\xbb\xdd\xce\xd60\x07\xc7b`\xa9\xc7\xd1e\xb3Y$\x93I\x84\xc3a\xf8\xfd~\x18\x0c\x06fS9\x96'T*\x15\xa6\xa6\xa6\xf0\x9b\xdf\xfc\x06===\x98\x9a\x9a\x82\xdb\xedF0\x18\x84\xcb\xe5bb\xce9A\x00\x05A@:\x9dF2\x99D,\x16C\x22\x91\x80N\xa7c_\x94\xe3\xcc,\xa8b\xb7`\xca$\xb3\xd9l\xb0\xd9lhooG>\x9fGgg'zzz\xb0o\xdf>\x08\x82\x80\xd6\xd6VTTT\xc0\xe9t\xc2\xe1p\xb0\x9b\x08\xbd7w\xd3\xadl\xe4r9d2\x19\xc4\xe3q\xc4b1\xa4\xd3\xe9Y\xf7w>\x9f\x87 \x08\x18\x1d\x1d\xc57\xbf\xf9M\x9c8q\x02\x1e\x8f\x07\x91H\x04\xf1x\x1c\x99L\xa6d\xf2\x08\x07\xc7\xa9^V\x94?/U!\x82\x12&\x13\x89\x04\xdb[\xb4\xaf\xf8y\xbb<\xd7\xa5J\xa5B8\x1c\xc6/~\xf1\x0b\xec\xdc\xb9\x13###,|.\x1e\x8f\xb3\x98\xd53\x85\x05q\x01\x93\x8b\x88\xbe\xc4\x5c\x82\xbb\xb9:\xb8p\xe4Oy[\x98\xa9\xd6\x1a=o\xe3\xc6\x8d\xd8\xb8q#6o\xde\x8c\xb7\xdez\x0b/\xbe\xf8\x22\xea\xea\xea\xd0\xda\xda\x0a\x97\xcb\x85\xaa\xaa*8\x9dN\xe6\x1e\xa6\x0cb\xee\x16^\xb9kL\x92$\xc4\xe3qD\xa3\xd1Y\x89\x1f\x91\xbf}\xfb\xf6\xe1\xa7?\xfd)\x0e\x1e<\x08\xaf\xd7\x8bL&\xc3\xfePl\x167t\x1c\x8bmx\x97\xaa\xad!\x17o&\x93A \x10(i7\x17\xab\xae\xee|\xf6\xdfB\xfe\xee\x95\xb4\xef\xe5\x84\xde\xeb\xf5\xe2\xfb\xdf\xff>\xde|\xf3M\xf4\xf7\xf7#\x16\x8bA\xadV\xb3\x90\xae3\x9d0\xbb \x04\x90\x0cD4\x1a\xc5\xd8\xd8\x18\xbc^/_\x00gx\x81\x9d\xca\xf3\x5c.\x17\xee\xb8\xe3\x0e\xdcz\xeb\xad\xf8\xe7\x7f\xfeg\xbc\xf2\xca+hkkCss3\x9a\x9b\x9bQUU\x05\x87\xc3\x01\xb3\xd9\xcc\xe6\x9a\x97\xedX\xb9F4\x99Lbjj\x0aUUU%\xd7X<\x1e\xc7[o\xbd\x85\x17^x\x01\x87\x0f\x1f\x86\xdf\xefG<\x1e\x9fv\x18\xf23\x80c\xb1\x94\x16\xc2Rv\x01+\xe3\xe7\xa7\xa6\xa6f=\xbb\x17\xf2\xbb\x9c\xa9\xb1Q\xce\x89\xbc4\xd4\xd9\xba\xcc\x9e\xa95\xa1R\xa9\x90L&\x11\x8dF\xd1\xdd\xdd\x8d\xd7_\x7f\x1dG\x8f\x1eEWW\x17b\xb1\x18KP\x22q\xe5L\x8b,\xe2B.\xa6\x5c.\x87H$R\x92\x00F\x22\x11\xe4\xf3yLNN2\xe6;\xd3\xfbQ\x8d\xa4RFi)\xdd\xeeN\xf5\xf3\x9c\xcakT*\x15R\xa9\x14\xaa\xaa\xaa\x10\x0e\x87a\xb5Z!I\x12\xca\xca\xca\x90\xcb\xe5`6\x9b\xa1R\xa9\xa0\xd5jg,\xd2+7\xc4_\xfb\xda\xd7\xb0{\xf7n\xfc\xe4'?A\x7f\x7f?.\xb8\xe0\x02\xb4\xb6\xb6\xa2\xbe\xbe\x1e.\x97\x0b6\x9b\x8d\x05\xebs\x12\xb8\xf2@\xfb;\x1e\x8f\xc3\xe7\xf3\x15}N.\x97\xc3\xc0\xc0\x00^y\xe5\x15\xec\xd9\xb3\x07'N\x9c\xc0\xc8\xc8\x08b\xb1\x18;p)\xcb\x8d\x07\xb8s\xccv\x86.\xc4\x85s\xa9\x87\x19\x90\x1b8\x1e\x8fc||\xbc\xe8\xf9\x1c\x8b\xc50<<\x0c\xb7\xdb\x8d\xde\xde\xde\x19\xc9\xd3\x5c\xec\x88\xdc\x15y*\x9f\xf5T^W,\xc9\xebTm\xe5b\xb8H\xe7\xf3YJ=\xaf\x98\xc7M\x10\x04D\xa3Q\x04\x02\x01\x8c\x8f\x8fcdd\x04\x83\x83\x83\x88F\xa3,\x0c\x86\x88\xdf\xd9h\xda .\xd4\x00\x92\x01\x88\xc7\xe3,FH\x8e\xd1\xd1Q\xf4\xf7\xf7\xa3\xbb\xbb\x1b\xf1x\x1c\x1e\x8f\xa7(\xdb\xa5\x85\x9fJ\xa5\x90N\xa7Y\x11c\xe5\x00+[K-$\xb9:]\xe26\xd7\x00\xce\xf9\xc8\xfa\xb4\xf9\xe4\xef-\xaf\x01HcAn[\x8b\xc5\x82\xf2\xf2r\x18\x8dF\x94\x95\x95\xa1\xac\xac\x0cv\xbb\x1dv\xbb\x1d:\x9d\x8e\x1d\xac\xf4\x1e\xd9l\x16\x9b6m\xc2\xea\xd5\xab\xf1\xf4\xd3O\xe3\xc5\x17_\xc4\xfb\xdf\xff~\x04\x83A\xa4\xd3id\xb3Y\xf6ZN\x02W.\xb2\xd9,\x82\xc1`\x01\xe9K\xa7\xd3\x98\x9a\x9a\xc2\xfe\xfd\xfb\xb1\x7f\xff~\xf4\xf6\xf6bhh\x08n\xb7\x1b\x89D\xa2\xc0\xad!\xdf\xb3<\x0c\x84C\xde\x05\x83J\x9f(\xcf\xceP(\x04\xb7\xdb\x8d@ P\xd0UF\xaf\xd7\xa3\xbd\xbd\xbd\x80`\xccG\x01<\x1d2\xb4P \xfb\x96\xc9d\x0ab\x00\x01\xc0\xef\xf7\xa3\xbf\xbf\x1f;v\xec@\x7f\x7f?\xc2\xe10\x82\xc1`A\xfd\xd6S\xb5u\x0bE\x8a\xe5cX\xec\xf7SR\x8b\xb2\xc5\x9d\xb2\x1d\x9arLf\xfa^D\x98\x8b\xa9\x8a\xa5~.5N\x0b\xd5v\xaf\xd8{\xe4r9$\x12\x09V?\xd5\xe7\xf31\x8e$O\xf6\xa0j\x0ag\xfaL\x5c\xd0\xfa\x0b\xc4\xce'&&\xd8\x97\x0f\x06\x83\xe8\xe9\xe9\xc1\xc1\x83\x071::\x0a\x9f\xcf\x87d2\x09\xaf\xd7[@\xee\x94\x93F\x93\x12\x8f\xc7\xd9\x82 \xb9\x5c.\x87'\x93\xc9\x05'p\x8bI\x0eO\xf5\x90\xa0X<\xe5\xe3t\xf8i4\x1a\xe8t:\x18\x0c\x06$\x12\x098\x1c\x0eH\x92\x04\xb3\xd9\x0c\x93\xc9\xc4J\xc0TVV\xc2f\xb3\xc1n\xb7\xa3\xa2\xa2\x02&\x93\x09\x06\x83\x01\x82 \x97\xcb!\x95J\xe1\xc6\x1bo\x84\xc5b\xc1\x0b/\xbcPP\xec7\x97\xcb\xc1\xe1p\xc0h4\xb2R\x05\x1c+\x03tI\xc8f\xb3\xf0z\xbdH&\x93\xd0\xe9tH&\x93\xe8\xec\xec\xc4\xee\xdd\xbb\xd1\xdb\xdb\x8b\x91\x91\x11LNN\xc2\xe7\xf3!\x12\x89\xb0RE\xb4v\xc9\xc0s\xf2\xc7\x89\x1f)$\xa2(B\x14Ex<\x1e\xbc\xfd\xf6\xdbx\xeb\xad\xb7\xf0\xf6\xdboc``\xa0 ^T\xbefr\xb9\x1cZ[[\xf1\xd6[o\xc1\xe9t\xce\xe8:+\xe5j\xccI9\xa8p\xf6[\x12\x92]\x8bF\xa3\xe8\xec\xec\x84 \x08\x18\x1f\x1fGoo/\x06\x06\x06\xd0\xdd\xdd\x8d@ \x80d2\x89D\x22\x01\xe0d\xf1\xe8\xb3I\x5c\xf3\xf9<#\xecr\xb5\x8bHL>\x9f\x87\xc9d\x9aF\xc0\x95s\xa1\xd5j\x0b\xc6\x9fBMJ\xa9k\xc5\xbc\x834\x1er\x01\xe9L\x9d1\xf4{\x8b\xfd>\xba\xacd\xb3YV\xec;\x95JM[\xcf\xf2\x8bM\xb1\xb8\xfe%O\x00I\xae\xcf\xe7\xf3\xac\x04L:\x9df$.\x9dN#\x16\x8b\xc1\xe7\xf3\xb1\xfaG\xca~\x87$\x8bR\xab\xa9L&\x03\x8dF\xc3\xda\xe3H\x92\x04A\x10\x90J\xa5\xd8k\xa8v\xddb\xa8t\xa7\xb3\x80\x16r\xf2h,\x09T`\x9bz\x0aR\xc9\x16Q\x14!I\x12L&\x13<\x1e\x0f4\x1a\x0d\xab+\xd4\xd3\xd3\x03\x87\xc3\x01\xbd^\x8f\xf2\xf2r\xd4\xd7\xd7\xc3j\xb5\xa2\xb6\xb6\x16N\xa7\x13\xa2(\xb2[h:\x9d\x86F\xa3\xc1\xc5\x17_\x8c\xbd{\xf7\x16\x14\x9c&\xc5P\xa7\xd3-\xf9\x0a\xfb\x1c\x0b{\xc0\xc9\x0f)\xba|%\x12\x09$\x93I\xa6\x12+[X)\x0fF\xa5r\xcf\x13\x8bV\x06\x94\xe7m,\x16\x83\xc7\xe3\xc1;\xef\xbc\x83_\xfd\xeaWx\xe9\xa5\x97\x0a\xd6\x09u6\x92\xaf!\xe5:\x9a\xcb\x99-\xef\xb1+\x7fN\x22\x91\xc0\x1f\xff\xf8G\xd4\xd4\xd4\xa0\xa3\xa3cV;\xb2\xd8\xe3\x92\xcb\xe5\x10\x8dF\xf1\xf2\xcb/#\x16\x8b!\x93\xc9\xa0\xaf\xaf\x0f\x1e\x8f\x07^\xaf\x97u\x06\xc9d2KfNi\xaf\x13\x91#\x11\x81:R%\x12\x09&\x14P\xe7)R\x04\xe9\x5c\xa1\xccW\xf9x(\xc3\x96H\x80\x90\x17\xa4\xa7\x0eC\xc4\x17\xe8w\x93\xa7\xecL\xf7\xd4-\xc5\x15\xe8<$\x1e\x93\xc9d\x0a\xfa>\xcb/\xc6g\x1a\x0bF\x00\xc9(\x00@\x7f\x7f?\xf6\xec\xd9\x03I\x92\xe0\xf3\xf9\xd0\xdf\xdf\x8f\xfe\xfe~LNN\x22\x1c\x0e#\x9b\xcd\xb2\xa0pb\xef\xc4\xe0\x05A`\x841\x97\xcb\xb1Vr\xc4\xa2\xcf\x04\xab_\xca\xea\x04\xa9n\xb4\x80\xa8`(\x19g\xb5Z\x8dX,\x06\x95J\xc5\x5c\xbd\x1a\x8d\x06\x16\x8b\x05\x81@\x00eee\x98\x9c\x9cD__\x1f\xecv;\x1c\x0e\x07\x0c\x06\x03V\xaf^\xcdnc\xe1p\x18\xa2(B\xaf\xd7\xa3\xb5\xb5\x15G\x8f\x1ee\xad\xfeh\xa1\x92;\x98\xc7r\xad\x1c\xd0\x9as\xbb\xdd8r\xe4\x08\x04A\xc0\xd4\xd4\x14\x06\x07\x071<<\x8c\xf1\xf1q\xf8|>D\xa3\xd1\x02\xd5X\x0ey\xb8\xc2R\xdfk\x1c\x0b\xb3fH-\x02\x80\xd7_\x7f\x1d\xdb\xb6m\xc3\xee\xdd\xbb\xb1k\xd7\xae\x82K-\x11\x05\xf9e\xa2\x14\xc6\xc6\xc6fue\x16S\xa0T*\x15N\x9c8\x81t:\x8d\xf1\xf1q477\xc3n\xb7\x9fUrL\xf5\x00\xdf|\xf3M\x18\x0c\x06\xa6\xa2\xc7b1\xa4R)\xa4R\xa9\xb3\x9681\x1b\x04A`\xaa\x1d\xa9\x80\x94\xd8\xa0\xd5j\x19\x09$\xe2M\x82\x05\x915\xa5\xfd \x92K]R\xe89\xd4\x8aR\x9e<#\x9fc\xb9r\xb6XY\xd3\xf3\x15|\xe8\xf7\xcb\x9b5\x14\x9b\xc7\xb3U\xfaG\x5c\x8cA\x10E\x91)D\xd4aB\xde\xda\x86\x9e\xa7V\xab\x11\x8f\xc7\xd9\xe4\x91\x9f\x0f\xe5\xe5\xe5\xf0\xf9|0\x9b\xcd\xa8\xa8\xa8\x00\x00x<\x1ev\x135\x9b\xcd\xe8\xec\xecd]\x1bh.m6\x1b\xdb\xcc\x1c+\x07t\xa9 %\x98\x0es\xba\x8c\xc8\xd7\xecL5*9\x96\xbf\xe2\x97\xcdf\x99\x8a\xf7\xf4\xd3O\xe3{\xdf\xfb\x1e\x06\x07\x07\x91J\xa5\xd8\xda\xa0\xb3C\xaenY\xadV\x5cs\xcd5hkkCCC\x03\xfbc\xb5Z\x0b\xde_\xa3\xd1\xa0\xa2\xa2\xa2\xc0\x16\xccu}%\x93I\xf6\xbaR\xc9\x86gb\x9c\x88\x10\xe4r9\xf4\xf5\xf5!\x99L\x22\x95J!\x99L2\xb5\x88D\x10\xb9z\xb6\x14m\x14\x8d)\x9d\x11\xe9t\x1a:\x9d\x0e\xd9l\x96\xf5\xa8'\x8f\x16u\xbc(f\xd3\x88\xf0\xd2\xbf3\x99\x0c\xb2\xd9,\xe3\x04t\xb1P\xd6\xbc\x95\x7f\x9e\xa5r\xd6(\x95k%9%\xe1\xe6L\xb9}\x17\x8d\x00\xcewP\x22\x91\x08\x93ri\xc1\xd3m\x87\xfc\xe4rW\x92\xfc\xef3Y,q\xa9\x93Be\xf6\x10-(\x22\xe3Z\xad\x96\xf5\x07\xa6q\xa6\x9f\xa9\x83\x8b\xc9d\x82\xcb\xe5B(\x14b\xad\xe2\xd4j5\x8cF#\xdb|\x07\x0f\x1edq\x86\xd4K\x98nv\x5c\x05\xe4\x06\x9f\x83C~&\x85\xc3a<\xf5\xd4Sx\xf4\xd1G\xd9Y\xa44\x86z\xbd\x1e\x95\x95\x95\xb8\xf3\xce;q\xc3\x0d7\xe0\xf2\xcb/\x9f\xd6\x1d\x86\xc8Q\xb15\xb6\x10Y\xc2\x14^s6\xce1y\x22\x9f \x08\x18\x1b\x1b\x83Z\xadf\x1e0y\xa8\xd4R\xdc\xefJ;$'\xaa\xe4\x0e&\xb2N\xa4\x8cD\x84L&3\xcdU+O\xf2 \xc50\x9b\xcd2AH9&\xa7\x9b\x88y6\xc6l\xa6X\xfe\xb3\xa1\x02\x8a\x8b\xb5\xf9U*\x15\xeb\x0c\x92L&\xd9d\xc9\xcbI\xe8t:\xa4\xd3\xe9\x82\xe0H\x8au(\x15GT,\x93h\xa5\xa9\x7f\xf2\x9b\xa0|\x1c\xe4A\xa7\x14\x17\x91\xc9d \x8a\x22\x0b\xdc\xcff\xb3\xcc}\xeb\xf5z\xa1\xd7\xeb\xd9\x1cP\x1b\xbfd2\x09\x8dF\x83p8\xcc$\xfd`0\x88}\xfb\xf6\x15\xf4\x10\xa6x\x0d\xde6n\xf9\x13f\xf2\x18\x8b\x5c.\xc7\x08!\x05\xd2\x92{\x97\xdc\xec\x14\x9bID\x906\xb3Z\xadF&\x93\x81Z\xad\xc6\xd0\xd0\x10\x8e\x1e=\xcaJ\xcb\x18\x8dFF\xfe\xb8+\x98+\x7f\x1c+\x0ft~h\xb5Z<\xf2\xc8#\xf8\xe1\x0f\x7f\x08\xb7\xdb\x0d\x00,\x06L\x92$\xacY\xb3\x06\x8f<\xf2\x086o\xde\x8c\x86\x86\x86\xa2kh\xb1\xcf\x10eAe\x95J\x05)/\xcd\xba\xc6\xe5\xee\xec\x85T\xff\xc8\xcbb\xb5ZY\xf8\x8d\xd2\xce\xcd&\x06\x9cm\x05\xb0X\xfb=\xa5r)\x0f\xfb\x22\xc5\x8b\xc2Fh.\x94\xe4\x8f\x04!\xb2M\xc5\xde\x93\x88\xe5\xb9$>(\xe7\xd4`0\xc0f\xb3\xc1h4N\xcb\x8a>\xa7\x08\xa0\xbc\xae\x8d\xdb\xedFUU\x15\xe2\xf18K@ \xd7$\x11\x0br\xf5\xc6b\xb1\x82x\x07\xb9\xaa\xa0\xcc>,\xa64\xae$\x05p\xa6\x1aF\xc5T\x18\xf9\xd8\x90\x22H\xb7(\x9dN\xc7\xe4v\x8b\xc5\x02\x95J\x85H$\x02\xbd^\xcfn\xc5\x92$\xb1\x22\xbe\xf2\xf2\x1d\x9d\x9d\x9dp:\x9d\xacw0\x91@\xee\x0a\xe6d\x90c\xe5\x81\x5c\x97\xd7]w\x1d\x06\x06\x06\x0a\x5cz\xe9t\x1a\x97]v\x19\xbe\xff\xfd\xef\xe3\xbc\xf3\xce\x9b\xb1T\xc6\x99\x5c\x83rwr>\x9fG^*}\xc9>r\xe4\x08\xc6\xc6\xc6\xb0a\xc3\x06\xd4\xd6\xd6.\xd89G\x99\xb1F\xa3\x11.\x97\x0b\xf5\xf5\xf5\x05\x04j&\x22\xb8\x14I\x8c2\x16Oi\x93\xc8\x0bEv%\x95J\xb1\xd2f\xa4\xd6\x91:H\xc9\x1f\x14\x06&O\x08*V6\xee\x5c%\x7f\x04\xb5Z\x0d\x97\xcb\x05\x8b\xc5rn\x13@\x22\x81\x82 \xa0\xa1\xa1\x81\x05\xd7\xca\xbf4e\xf1\xa4R)\xa6\x14\x92\x8f_\x1e\xf3\x07\x00z\xbd\x1e\xf5\xf5\xf5X\xb5j\x15+[B\x81\xe6\xf2E\xb7\x9cI\x87|<\xe8\xdf\xd9l\x16\xa1P\x88e[\xd2f\x19\x1d\x1dE4\x1aE8\x1cf\x8f\xd1\xa6Q\xb6\xdf\xa1C\x862\xb8r\xb9\x1cL&\x13{\x1e5)'\xd7\xb1^\xafg\x85J\x83\xc1 \x0e\x1f>\x8c\xea\xeajTWW\xc3b\xb1\xb0\x98A^\x16fe+A\x8be\xd89\x96\x16\xc8\xdd\x9bJ\xa5\xf0\x9d\xef|\x07\x0f>\xf8 \x04A`e\xbb\x00\xe0\xaa\xab\xae\xc2?\xfc\xc3?`\xcb\x96-,\xd6\xeel\x94[Q\xaeKe\xd8B)\x050\x99Lb||\x9c\x950\xab\xad\xad]P2 \x08\x02\xccf3jkkq\xe9\xa5\x97\xe2\xa7?\xfd)\xc6\xc7\xc7\x0bB\xa1\xe4b\xc7R =r\xd7\xab<\xb35\x93\xc9 \x99Lbxx\x18\x03\x03\x03\x18\x19\x19A$\x12a\x17\x82L&\xc3\x1a\x09P9\x18\x22\x85\xc0\xc9Lp*\x1cO|\x80\x94?9\xf9\xd3h4\x8c\x17\xd4\xd5\xd5\xa1\xbc\xbc\x9c%\xa5\x15k\x16q\xb6/\xc5\xca\x905\xb9\xdb\xdfn\xb7\xa3\xaa\xaa\x0av\xbb\x1dV\xab\x95y\xde\xcei\x17\xb0\xb2\xb1\xb1\xcdf\xc3\xc0\xc0\x00#\x17\x82 0\xb7/\xc9\xbdJ\xe5\xcf\xe5ra\xf3\xe6\xcdX\xb7n\x1d\x9cN'\x1b\x1cr7\xcao\x99+A\x01T\xc6\xaa\xd0x\x11y\xa6\x04\x9ah4\x0a\xb7\xdb\x8d`0\xc8Z\xcf\xf4\xf5\xf5\x15,D\xb9\x9cN\x8a \x118yk\x1aJ\xceQ\xab\xd5,k\x8e2\x81\xfb\xfa\xfa\xd0\xd5\xd5\x85\x9a\x9a\x1a8\x9dN\x94\x95\x95\xb1ZP\xdc\xf0\xaf\x1c\x92W\x8a\xf0qEpy\x93\xbf\xa3G\x8f\xe2\xe3\x1f\xff8\x0e\x1c8p\xd2\x98\x88\x22\xd2\xe94\xd6\xacY\x83'\x9f|\x127\xdcpCA\xf8\x892\xb9c1\x8cm1\xa3\xaf\xfcY^/\x0e\xc0\x8c\x0a\xa0\xbc$\x8d \x08\xec\x02\xbc\x90g\xbbZ\xadf\x09xZ\xad\x16eee\xa8\xac\xacd\xd51\x8ay\xc0\x96\xc2\xbeR\xc6\xda\x11YK\xa7\xd3X\xb7n\x1d&''\xd1\xdd\xdd\x8d\xfd\xfb\xf7\xa3\xbf\xbf\x9f}\x07\xaa\xe8\x91\xcdf\x99`@\xff&\xce@\xb1\x7fD\x80\xe5j\xa2\xcdf\xc3\xd5W_\x8d\x8e\x8e\x0eTVV\xc2j\xb5\xb2J#d\xb7\x96\xd2\x19\xa4\x8c\xcf\x97\xab~T-\xc5j\xb5\x16\x10\xc0sZ\x01\x94\x7fqb\xf8\xb4\x89\x08\xf2\xac\x1e\xba9\xc8\x0d\x8b\xddn\xc7\x07>\xf0\x01\xac[\xb7\x0e---p\xb9\x5c\x05\x04C\x9e\xf6\x7f\xb6\xea\xe7\x9c\xe9\x03\x97\xc6\x94\xfe-'p\x14\xcfG1\x13\xd1h\x14\xd1h\x14\xc1`\x10>\x9f\x0f\x13\x13\x138|\xf80:;;Y\x11m\xda\xb4\x82 \xb0\xc2\x9a\xc9d\xb2\xe0\xa6I%d\xe8\x00\x94\xbb\x81s\xb9\x1c\x0e\x1f>\xcc\xfa\x05;\x9dN\xe6\x0a\xe6*\xe0\xf2\xc5\xd9l\xe4\xceq\xf6A1\xc1/\xbf\xfc2\xfe\xec\xcf\xfe\x8c\x91\x22:\xe7\xef\xbf\xff~<\xf1\xc4\x13\x05\x86\xf8L\x9f\x07\xb3%&)\xeb\xc9\x96Z\xcbD\x16\x17\x83\x00\xd2\xb8Q\xd7\x0c\xadV\x0b\xab\xd5\xcaD\x11%i\x90\x17d_*kAN\x02\xc9\xae\x93\x08A\x84&\x9f\xcfctt\x94\xa9w\xe4\xe6\xa4\xf8P\xbd^\xcf:w\x10\x1f\x90\x8b\x14\xf4\xddM&\x13n\xbf\xfdv\xb4\xb7\xb7\xa3\xb9\xb9\x99\xb9M\xa9\xde\xadR%]*c\xa4\xbc\x84\xd0\xe5\x83l%%\x80P\x99\x9c39\xbf\x8b\x1aqK\x0bV\xde\xdaE\xb9`\xc8%,\x1f\xa4\xcd\x9b7c\xcd\x9a5X\xbf~=\x93x\x0d\x06\x03\x0b\x10V\xca\xe0\xcb\xd5\x18\xcd\x14`+\x1f/\xda(tsR\x12A\x9f\xcf\x07\x8f\xc7\x83\xa6\xa6&\x9cw\xdeyx\xe7\x9dw\xd0\xd7\xd7\xc7Z\xea\x91\xbb\x86\x12v\xe8\xbd\x89\x94S\x89\x04y\x0b>\xfa{jj\x0a===hllDuu5/\x0e\xcd\xc1\xb1\x8c\x89\x1f\x19\xd7\x1f\xfc\xe0\x07\xf8\xdc\xe7>W@\xec\x9a\x9b\x9b\xf1\xb3\x9f\xfd\x0c\x9b6mb\xc1\xfcg\x03\x92$\xa1\xaf\xaf\x0f555\x05IjJ\xdbS\xaa=Y\xb1\xf3V\x10\x04\xd6\x86m!]\x8bt\xd1&\x85\x94b\xb3g\x22\x0f\xf2\xdf\x7f\xb6\xec\x9e\xd2\xcd*\xff\x9b*|P\xd7)I\x92\x10\x0a\x85\xe0\xf5z\x0b\x08\x9a^\xaf\x07\x00\x18\x8d\xc6\x82\xf6v\xca,_\xb9\xbd\xdb\xb2e\x0bZZZ\xb0f\xcd\x1a455\xb1\xdfA\xea\xdfR\xb49J\x05P\xe9\x0a&[)o\xe8p&!.\xf6\xa1Q,iA\x1e\xd8)\x7f\x8e$I\xa8\xae\xae\xc6\xda\xb5k\xd1\xd8\xd8\x88\xba\xba:TUU\xc1b\xb1\xb0\xf6g\xf2\xc5\xb6\x12\xdaH\xcd\xa5\x06\x94\xfc@\xa3MDD\xb0\xba\xba\x1a\xc1`\x10\x1e\x8f\x07UUU\xa8\xae\xae\xc6\xae]\xbb\xb0w\xef^\x84B!6\x1f\xf4|Z\x94\xb4A\xe5E*S\xa9T\x81\x0b>\x16\x8b\xa1\xbf\xbf\x1f\xa3\xa3\xa3hjj\x82\xcb\xe5\x82\xc9d\xe2\xb1\x80+\x84\x10p\xac\x1c\x90\xd1\xfa\xd4\xa7>\x85\x1f\xff\xf8\xc7\xac\x96[.\x97\xc3=\xf7\xdc\x83g\x9ey\x86]H\xcf\x06\xf9#\x92v\xe4\xc8\x11<\xf0\xc0\x03\xa8\xab\xabCmm-V\xb5\xb4\xe0\xaf?\xf5)\x5c\x7f\xddu\x8c\xfc\xc9E\x07\x22-\xb3\xa9\x89\xe4)Y\xac\xb1%5H\xab\xd5\x16\xcdt]\xaa(\x16_Iud\xa9\x8e\xdf\xf8\xf88\xea\xea\xea055\x05\xa3\xd1XP9\x82TARe\x95\x8d\x1f\x08\xabW\xafFSS\x13\x9a\x9b\x9b\xd1\xd8\xd8\x88\xca\xcaJ\xa6\xfe)\xc3\xc1\xce\x15^\xa0$\x83g\x03\xe2\x99\xfe\xc2\xa4\x08*\xb3Wi\x00\xd6\xae]\x0b\xbb\xdd\x8e\xca\xcaJ\xd8l6\x98\xcd\xe6\x82\xfa8+\xf5\xe0-\x059\xd9\xd2j\xb5\x8c\x10f\xb3YX,\x16X\xadV\xd8l6\x94\x97\x97\xc3f\xb3A\xab\xd5\xa2\xbc\xbc\x1c\xff\xf7\x7f\xffW@\x02\xd3\xe94+\xd0I2}>\x9f/\x88\xd1\xa4\xc3\x8a\xc8\xe0\xe0\xe0 \xc6\xc7\xc7\xe1v\xbbY\xa2\x8e\x9c\xacs,_,\x85:[\x1cgf\x9eU*\x15\xee\xba\xeb.<\xff\xfc\xf3\xec\x02\x98L&\xf1\xcc3\xcf\xe0\xd3\x9f\xfe\xf4\x9f\x0c\xcaY*\x05E\xc6\xf4\x8f\x7f\xfc#\x00`tt\x14\xa3\xa3\xa3\xd8\xbd{7~\xfb\xbb\xdf\xa1\xa9\xa9\x09\x9f\xfb\xdc\xe7\xf0\xf9\xcf\x7f\x9e\x95\xb5\x9a\xcbE\x86\xce\xd23q\x9e\x9d\x8bY\xad\xcaq\xa1\xd86\xe0d(\x97\xcb\xe5b\x7f\x02\x81\x00\xb3\xe7\x0e\x87\x83%0R\xbc#\x91\xf1b\xed\xd2jjj\xe0r\xb9PUU\x05\x87\xc3\x01\x8b\xc5\x02\x83\xc1pN\xf3\x82\xa50\xcf\xe2\x99>H\xe80Q\xa6\xe2\x13(\xde\xcfl6\xc3d2\x9d\xb5&\xdd\xe7\xf2\xa2\x22\x05\x8e\xba\x80h4\x1a\xe8\xf5z\x18\x8dF\xe8\xf5z\xd6\xbe+\x9f\xcf\xe3\xa5\x97^b}\x99\x95-\x87\xe4\xe4\x9c$jy\xcb\x1aA\x10\x10\x0e\x87\xe1v\xbb\xe1\xf1x\x10\x0a\x85PQQ\xc1:\x85ppp\x9c\xdb\xa0K\xfa\xddw\xdf\xcd\xc8\x1f\xa9,o\xbf\xfd66o\xde\xccz\xbd.\x05\xec\xd8\xb1\xa3\xa0\xf6,p\xb2\xf2DOO\x0f\xee\xbd\xf7^\xdc\x7f\xff\xfdx\xf8\xe1\x87\xb1v\xedZ\xe8t\xbaY\x0b\x06\xcf\xd4\xb7\xb5\x94\x9a\xc3m\xd0I{a2\x99`\xb1X`\xb3\xd9\xd8\xdfv\xbb\x9d\xf5]\xa6\xb2dF\xa3\x91\xcd\x13\xcd\x87\xbc\xc4\x9bV\xab\x85\xc3\xe1`\xbc@\xa7\xd3\xb1\xe2\xe2\x1cK\x90\x00\xca'Q\xf98\xc5\xac\xc9\xff\xc8AA\xa2D\xfcVB\x92\xc7b\x93A\xea\xd8!\xef\x0dL\x99\xd8\xf1x\x1c\xaf\xbe\xfa*#\x7f\xca\xc0^j\xe5C\x89 D\x02\xe57\xfe\xa3G\x8f\xe2\xfc\xf3\xcfG0\x18D\x22\x91@YY\x19\x0b\x9c\xe6X\xfe\x04a>\x8fs\x9c{g\xc8_\xfe\xe5_b\xeb\xd6\xad,h\xbf\xac\xac\x0c\xef\xbe\xfb.\xd6\xad[\xc7\x12\xfdf[\x1f\x8b}\x86Sm\xd9\xef~\xf7\xbbx\xf1\xc5\x17\xd1\xdf\xdf\x8f\xde\xde^\x1c9r\x84\x91A\xbd^\x8fl6\x8b\xaf~\xf5\xab0\x9b\xcd\xb8\xe9\xa6\x9bp\xfb\xed\xb7\xb3\xb8\xbbb%D\x94\xa5\xc9\x94\xe7\x1a\xfdL\xf1\xd4\xfc\xe2\xfb\xa7\xf9 \x976u9\xb1\xdb\xed\xa8\xad\xad\x85\xc1``%\xc6\xa8\x12\x88<\xa9\xb1Xm[\x12/\xc8\x8eq^\xb0\x04\x09\xa0r\x03\x15K`\xa0\x85A\x05\xa1\x95\xc5\x9c\xc9}H$\x83O\xf2\xc2\x80\xb2\x8d\xe8\xdf\xf9|\x1e\xf1x\x1c\xe1p\x18\x93\x93\x938t\xe8\x10\x80\xc2\x12\x09\xf2lk\x22{\xf1x\xbc\xe0\xff\x04A\xc0\xe4\xe4$B\xa1\x10\x82\xc1 \xe2\xf18#\xf9\x9c\x00.op\x17\xf0\xf2\xc7'>\xf1\x09l\xdd\xba\x95\x15\xe9\xd5\xe9t8t\xe8\x10\x1a\x1b\x1bg\x8d\xb7\x92\xb7\x06;S\xeb\xb1\xaa\xaa\x0a\x97]v\x19\xae\xb9\xe6\x1aD\x22\x11\x04\x83A\x04\x02\x01\xfc\xe2\x17\xbf\xc0\x81\x03\x07\xd8%8\x1a\x8d\xe2W\xbf\xfa\x15^{\xed5<\xf0\xc0\x03\xf8\xd2\x97\xbe\xc4\xea\x9d\xceDdU*\x15|>\x1f\x92\xc9$\x02\x81\x00\x9a\x9b\x9ba\xb1X\xe0\xf1x\xf0\xf5\xaf\x7f\x1dO=\xf5\xd4\x92RC\x97\x0a\x11$\x8f\x13\x95\xbb\xa1\xf8Q\xaf\xd7\x8bL&\x03\xb7\xdb\xcdb3\x8b\xad\x17y\x1b9\xce\x07\x16x~\x16\xe3\xc6(\x872\xd1CN\x0e\x8b\xc9\xebt\xa8\xcc\xd4\xfd\x83\xe3\xf4H\xa0V\xab\x85^\xafGEE\x05\x1a\x1b\x1b\xb1j\xd5*\xb4\xb7\xb7\xa3\xbc\xbc\xbc\xb06\xd6\xff\x9f\x17\xbau\xa9\xd5j\xe4r\xb9\xa2\x1d?(\xf3.\x12\x890\x02\xc8\x15\xa0\xe5\x0f>\xc7\xcb{^\xbf\xfb\xdd\xef\xe2\xa7?\xfd);\xc7\x8dF#zzzPSS3\xa7\xcb\xb9 \x08x\xf5\xd5W\xb1k\xd7\xae3\xb6^\xa8\xb2D\x22\x91\x80Z\xad\x86\xd3\xe9\xc4\x1dw\xdc\x81\xfd\xfb\xf7\xe3\xbd\xf7\xde\xc3\xc6\x8d\x1bY\xfc\x1fp\xb2\xa8\xfd\x97\xbf\xfcel\xd9\xb2\xa5\xa4\x1d#\xbb\xb4o\xdf>\x1c?~\x1c}}}\x18\x1e\x1e\xc6\x81\x03\x07\xd0\xd6\xd6\x86\xef}\xef{x\xf2\xc9'9\xf9S\xcc?\x09=\x82 \xa0\xae\xae\x0e\xabV\xadB[[\x1b\xf3\xf0%\x12\x09X,\x16&P\xc8\xcb\xc5\x15\xe3\x16\xe4\xd5\xe2\xbc`\x89\x12\xc0\xb9lt\xb9\xebW9\x91\x9c\xf8-.\xa8\xf8\xa4\xd9lf$\xb0\xb9\xb9\x19\xf5\xf5\xf5Lu\xa59\xd0\xeb\xf5H&\x93\xac\x04\x0c\x91\xbfb\xca\xad\xc7\xe3A8\x1cF4\x1ae\xdd]8\x96\x0ff\xda\x8fs\xc9R\xe78w@1\xc0\xaf\xbf\xfe:\xee\xbb\xef>f\xbc\x8dF#v\xef\xde\x8d\xda\xda\xda\x92nN\xf9Zx\xea\xa9\xa7\xb0e\xcb\x16\x5cv\xd9ep\xbb\xddg\xa5t\x09)J\xd9l\x16\x17]t\x11\xf6\xed\xdb\x87\xe7\x9e{\x0e\xeb\xd7\xafg\xdfW\x14E\xbc\xfa\xea\xabhoo\xc7\xdbo\xbf\x0d\x00\x05$Q\x0e*\xb1%I\x12|~\x1fn\xbc\xf1F\x04\x83A\xe4\xf3y|\xe5+_\xc1\xce\x9d;\xf9\x22\x92\xd9y9\x014\x99L\xa8\xa9\xa9\x81\xd1hD}}=DQdmH\x95\xf6\xbf\xd8:\x91W\x0b\xe1g\xce\x12$\x80r\xf5h.\x13T\xcc\xa8PM;>\xc1\x8b\x07J\x10\xb1Z\xadp:\x9d\xa8\xad\xadEcccAp-\xb9J,\x16\x0b\x8cF#s\xf5\xd0\x86Vf\x7fMLL \x1e\x8f#\x91H\xb02\x0b|\x0e\x97\x8f\x224\x9f\xb9\xe4\xf3~n\x9f\x0d]]]\xb8\xf9\xe6\x9b\x99\xfbN\x92$\xfc\xcf\xff\xfc\x0f\xd6\xacY3\xeb\xdcR\xec\xf0\x87?\xfca\xfc\xed\xdf\xfe-T*\x15t:\x1d.\xbd\xf4R$\x12\x89E\xbf\xd8\x17\xf3(E\xa3\xd1\x82f\x04W\x5cq\x05\xbe\xf1\x8do\xe0\x8b_\xfc\x22\x8b\x0b\x14E\x11\xc3\xc3\xc3\xd8\xb2e\x0b\xfe\xe5_\xfe\x05\x1a\x8dfZ\xfc\x9f\xfc;\xaa\xd5j\xd8]\xe1/\x0f\x00\x00 \x00IDAT\xac6<\xf5\xd4S\xec2\x9cN\xa7q\xd3M7!\x1c\x0e\x17\xd4\xbe]\xa9{\x83\x92<\x88\xd4\x95\x97\x97\xa3\xa6\xa6\x06\xf9|\x1e\xc1`\xb0\xa0\x86\x9f\x92\x03\xcc\xa4\xc0r,a\x02(\xdf \xf2\x94\xf6\x996\xd1L\x87\x01/$\xbc\xf8j\x8e(\x8a\xd0\xeb\xf5(//\x87\xcb\xe5Buu5\x1c\x0e\x07{\xdcl6\x17\xa4\xe6\xd3\xebf:\xc0\xd2\xe94\xc2\xe10\xe2\xf18S\x009\x11X>\xeb\x85\xf6%\xc7\xf2\xc7G>\xf2\x11\x96\x91\x99H$\xf0\xd4SO\xe1\xc6\x1bo,j\x98\x8b\x91\xad\xcd\x9b7\xe3\xf7\xbf\xff=#\x94\xa9T\x0a\x7f\xfd\xd7\x7f\x0d\x83\xc1\xb0\xe8g\x82\xf2\xfd%I\xc2\xe0\xe0 \xdez\xeb-\xec\xdd\xbb\x97\xd53\x15\x04\x01W]u\x15~\xf2\x93\x9f\xa0\xbd\xbd\x9dy9R\xa9\x14\xfe\xfe\xef\xff\x1e\x0f=\xf4\x10\x8b{\x9ciO\xa4\xd3i\xdcu\xd7]\xb8\xef\xbe\xfb\x98\xda\x15\x08\x04p\xe5\x95W2b\xa9\xfc,+)n\x96\xba\xc6\xd0w\x0e\x85B\x08\x85B\xa8\xaa\xaaBCC\x03\xcb\xfe-v\xc9,\x96@\xca\xed\xc99B\x00\xe5\x13z*\x0b\xbeTaN\x8e\x85\xbb\x9dQ\x86\xb0\xddnGEE\x05\xacV+\xabF/\x8a\x22l6\x1bk\xe7C\xedvH\xceW\xcek<\x1e\x87\xcf\xe7\x9b\xd6\xf6o\xb6\xf5!/\x05\xa4|Ly\xa0\xf3\x03\xe0\xd4\xf7a\xb11\x9ei\xbc\x8b\xa9(3\xdd\xcc9\x96\xcf:\x01\x80\xc7\x1e{\x0c\x9d\x9d\x9d\xcc\x13\xf3\xc9O~\x12\xf7\xddw\x1f\x0b\xd0/\xf5Z\xaf\xd7\x8b\xcb.\xbb\x0c;w\xeed\xcf\xb5Z\xadx\xeb\xad\xb7\xf0\x8f\xff\xf8\x8f\x05\xdd\x84\xce\x04\xf9\x93?\x9e\xc9dX\xa9*\xf9z\x06\x80\x17^x\x01\x0f?\xfcpA\xd7\x8fo|\xe3\x1b\xf8\xf2\x97\xbf\xccb\x9fg\xda\x0f\x99L\x06O>\xf9$\xb6l\xd9\x82\x5c.\x07\x8dF\x83C\x87\x0e\xe1[\xdf\xfa\x16\x0b\x99\x01N\xba\x8e\xfb\xfa\xfap\xe8\xd0!\xd6\xf9b%\xd8\x18\xea\xf3\xabR\xa9PSS\x83\xb6\xb66\x96\x8c8\x1fn\xc0\x15\xc0s\x8c\x00\x16c\xf1\x9c\xc9/-UG\x1e\x0fh\xb3\xd9PQQ\x81|>\xcfZ(\xe9\xf5z\x16#Cs\xa9\x8c\x13\x94oPR\xff(\xad\x9f68\x15\xa5\xa6^\x91\xd4\x09F\xfe\x98\xfcg\x0a\xe4\xa6\xee$\xd45F\xdeA\x86\xa3\xf8!)o\xcc>\xdb\x18\xd3\xf8R_n\xf98+\xc30\x94\x8a\xfdL$\x91\xe3\xdcD&\x93A__\x1f\x1e~\xf8a\xf6X{{;\xfe\xf3?\xff\x93\xd5b+u\x96D\xa3Q\x5c}\xf5\xd5\xe8\xec\xecd\xc5\xe8\xdb\xda\xdap\xf4\xe8Q\x5cq\xc5\x15\xec\x1c9\x955M\xeb9\x9dN\x97\xdc\xfbs\x89S\x0d\x06\x83\xcc;A\xeei\xbf\xdf\x8f\xaf\x7f\xfd\xeb\xf8\xcdo~\x03\x00\xac+\xc7\x13O<\x81G\x1ey\x04v\xbb\xbd\xe8\xa5\x96\x08_:\x9d\xc6\xb6m\xdbPSS\x83L&\x03A\x10\xf0\xe8\xa3\x8f\xa2\xab\xab\x8b}\xa6@ \x80\xae\xae.\x0c\x0f\x0f#\x14\x0a\xad(;#o\x137\xd7\x02\xdc\xa5\xe6\x90c\xe1pFR\x96x\xb9\x88\xa5M\x02\x0d\x06\x03,\x16\x0b\xccf3\xacV+\xacV+\xeb\xb3\x98N\xa7\xe1\xf3\xf9\x10\x8b\xc5\xd8\x0d\xac\xd8A\x9b\xcb\xe5\x90L&\x19\xf9\x937\xf3&2A\x07@\xa9,\xf0b\x87\x87<\xfbK\xde\xa1\x84g\xdc\x15\xee1\x1ag\x22x\xf2\xdaZ\xb3\x1d\xac\xf22L\x1a\x8d\x06:\x9d\x8e\x198\xf9\xf3h.\xe4e~\xf2\xf9\xfcY\xeb\xfd\xca\xb10\xe4O\xab\xd5\xe2\xe6\x9bofk@\x92$|\xf7\xbb\xdf-I\xfc\x08\xa9T\x0a\x1f\xf8\xc0\x07p\xec\xd81\xd62\xed\xa2\x8b.\xc2\xae]\xbb\xd8\x1e\x9do'\x0d\xba@\x16\x8b7>U\x15\x10\x00\xa2\xd1\xe84%\x89>\xe3\xad\xb7\xde\x8a\x83\x07\x0f\xe2\xfc\xf3\xcfg$\xf6\xbf\xff\xfb\xbf\x99\x0b[\xa9\xdc\xd1\xfe\xa21\xfa\xe1\x0f\x7f\x88\x9bn\xba\x09\x92$!\x1e\x8f\xe3\xcb_\xfe2~\xfd\xeb_\xb3\xf2Y\xa4bq{\xc89\xc1\x8a\x22\x80\x1cK\x9b\x00\xeat:\x98L&\x98\xcdf\x88\xa2\x08\xbb\xdd^P\x0f\x8bj8\xe5r9v\x80)\x89C.\x97C,\x16c7u\xf9\xe3\x92$\xb1\xe4\x10\xca\xa2\xa3\xdb|\xa9\x9b\x9d\xbc96e\x22\xd2\xe7\xa4 m\xder\xeeO*\x09\x91py2\x0e\x11\xee\x99\x0e\x5c*\x08\x9e\xc9d\x0a\xaa\xf7\x93\x0a,\x8f\xff$\xe2\xa7\xd7\xeb\x0b\x94`y\xed\xb3\xd9\xdc4\xdc\x03\xb0\xf4\xd6\x8eF\xa3\xc1\x13O<\x81\x13'N@\xab\xd5\x22\x9dN\xe3\x9e{\xee\xc1\xf5\xd7__r\x8f\xd1\xff\xdds\xcf=\xd8\xb1c\x073\xec\x97\x5cr\x09\xf6\xec\xd9\xc3\x8a\xfd\x9e\x0ab\xb1\x18\x02\x81\x00\x8e\x1c9\x82\x1d;v`\xdf\xbe}\xe8\xea\xea\xc2\xc7?\xfeq<\xfa\xe8\xa3\xa7\xdcy#\x93\xc9L\xfb>\xf2\xb3j\xfd\xfa\xf5\xe8\xea\xeaB{{;[\xaf[\xb7nE}}=\xae\xbc\xf2\xca\x0276\x9d\x83\xf4\xf3\x87>\xf4!\xdcz\xeb\xad\xf8\xedo\x7f\x0b\xb5Z\x8dm\xdb\xb6\xe1\xd5W_\xc5\x87>\xf4!\xd6\xee\x8c\x8aK\xaf4;\xc3\xc3GV \x01\xe4\xedq\x96>\x04A`\xad\xe2L&\x13$IbE^EQD(\x14B.\x97c\x07\x98\xbc\xf2\xbfrn\xe5\xaa\x13\x1d\x90*\x95\x0a\xc9d\x12\xd1h\x14\xe1p\x18^\xaf\x17>\x9f\x0f\xa1P\x08\xc9drNY\x85\x00\xa0\xd3\xe9X\xb2\x0a\xf5\x89\xd6\xe9t+~\x8d\xc9\xdd\xbe\xd1h\x14\x81@\x00~\xbf\x1f\x13\x13\x13\xac3K)\xd0\xeb\xf2\xf9<\xccf3\xea\xea\xeaP[[\xcb\x02\xb8\xa9\xd9\xba\x9c\xf4'\x93I\xb6\x0e(K4\x95J1e\x96\xe3\xdc2\xce\xb1X\x0c\x8f?\xfe8#\x7f\x8d\x8d\x8d\xf8\xe1\x0f\x7fX\xb202\x91\xfe\xa7\x9f~\x1a?\xff\xf9\xcfY\xb0\x7fKK\x0b^{\xed5\xe4\xf3y\x18\x0c\x869}\x06\xf9\x05b\xe7\xce\x9d\xd8\xb6m\x1b^{\xed5\xec\xdd\xbbw\xdas\x87\x87\x87g,!6\x177a)\x22B%c\xda\xda\xda\xf0\xea\xab\xaf\xe2\x86\x1bn`\x17\x96'\x9f|\x12k\xd7\xae\x85\xd5je\x04R\xe9\xc1\x08\x06\x83\xb8\xfb\xee\xbb\xf1\xca+\xaf0\x97\xf1_\xfd\xd5_\xc1\xe7\xf3!\x1a\x8d\xb2D\x11^\x22\x8bcE\x10@~\xdb_\xfa\x87\xbf\xbc\xca:\xb9=jjj`\xb7\xdb\x11\x0c\x06\x11\x8b\xc5\x98\xb2D\x07d\xb1\x04\x0e:\x10)\xbe\x86\x0eiR\x04\x03\x81\x00\x04A\x80V\xab\x85\xc1`\x80\xc3\xe1`\x8d\xc1\x8b)\x0c\xa4j\xa5\xd3itww#\x14\x0a\xe1\xbd\xf7\xdeCkk+#\x1f\xd4\xd2n\xa5\x13\xc0l6\x8bD\x22\x01\x9f\xcf\x87\xb1\xb11\x0c\x0f\x0f\xa3\xbe\xbe\x9e5M'\xb7y1\x8c\x8f\x8f\xa3\xb7\xb7\x17{\xf6\xecA<\x1e\x87V\xab\xc5\x95W^\x09\xa3\xd1\x08\xb3\xd9\x5c\xe0\x02T\xb6f\xcc\xe7\xf3\x88D\x22\x10E\x11f\xb3\x99\xb9\x9e\xe7c\x909\xce>\xbe\xf5\xado\xc1\xe7\xf3\xb1y}\xf6\xd9ggu\xeb\x8b\xa2\x88]\xbbv\xe1\x0b_\xf8\x02\xdb\x8b\x00\xb0g\xcf\x1e\x94\x95\x95\xcd\xf9w\xa7\xd3ih\xb5Z\xec\xda\xb5\x0b\xf7\xde{/\xba\xbb\xbb\x11\x89D\x0a.\xa7\x94l\x01\x00###L\xad>\x15\xcc\xd4\x83^N\x02\x01\xe0\xfa\xeb\xaf\xc73\xcf<\x83\xcf|\xe63,\xce\xef\xa1\x87\x1e\xc2\x8f\x7f\xfcc\xc4\xe3q\x88\xa2X\xe0\xc1\xc8\xe5r\xd8\xb9s'4\x1a\x0d>\xfb\xd9\xcf\xe2;\xdf\xf9\x0e\xb4Z-\xfc~?\xfe\xf5_\xff\x15\xb7\xdcr\x0b\xc6\xc7\xc7\x91\xcf\xe7\xe1\xf1xPYY\x09\xa3\xd1\xb8b\xce..\x04-s\x02\xa8\xec!;\xdbm\x8bci\x90\x07r\xb3R\xdd?\x8dF\x83\xf7\xbd\xef}\xe8\xea\xeab\xaa[(\x14\x82\xc3\xe1\xc0\xc8\xc8H\xc1\x5c\x17K\x04\x90\xf7\xfe\xa4\xf5\x90N\xa7\x91L&\xd1\xdd\xdd\x8d\x8f}\xecc\xf3\xfe\x9c\x17^x!\xa2\xd1(\xee\xb9\xe7\x1e\xa6J\x10y\x5c\xc9\xbd7i\xbc\xd3\xe94\xa2\xd1(\xbc^/\xba\xbb\xbbq\xf3\xcd7\xa3\xae\xaenN\xef\xb1f\xcd\x1a\xc4b1x\xbd^tvv\xc2\xe7\xf3\xe1\xf2\xcb/G8\x1cFee%\xfb=\xf2\xf9T\x1e\xec\x82 \x14\x0a\xf1,\xbds\x0c\x92$abb\x02?\xfe\xf1\x8f\xd9<_{\xed\xb5\xb8\xf4\xd2Kg5\xda\x99L\x06w\xddu\x17DQ\x84$IH&\x93\xd8\xbbw/l6\xdb\xbcT\xf9h4\x8a\xbf\xf8\x8b\xbf\xc0+\xaf\xbc2MA\xa6z\xa5uuu\xd8\xbcy3\x1a\x1b\x1bq\xe1\x85\x17\xb2\x8b\xdfL{\xa2\xd4\xef\x9ek\x11\xe1\x5c.\x87O\x7f\xfa\xd3x\xf1\xc5\x17\x99Kwrr\x12\xff\xfe\xef\xff\x8e\xaf|\xe5+\x88D\x22\x05\x1e\x91\x91\x91\x11\xd6&\xf3\xba\xeb\xae\xc3\xd6\xad[111\x01Q\x14\xf1\xf8\xe3\x8f\xe3\xa2\x8b.b\x97\xb0\x89\x89\x09\xf8|>\xb4\xb5\xb5a\xd5\xaaU\xcb\xde\x8b\xc1\xfb\x85\xaf\x10\x05P\xbe\x88\x95\xea\x1f\x9f\xec\xa5y+#\xb7\x04\x05\x5c\x9b\xcdf\xb4\xb5\xb5\xa1\xae\xae\x0e\xdd\xdd\xdd\x88\xc7\xe3\xd0\xeb\xf5\xd3\x0e\xdc\x992\xbc\x8b)r\x94!\xec\xf3\xf9\xf0\xf6\xdbo\xe3\x8a+\xae\x98\xf3\xe5\x80n\xd7\xdb\xb6m\x83\xdf\xefG4\x1a\x85\xc3\xe1@EE\x05\xca\xca\xcaX\xab\xba\x95z\xd1\xc8f\xb3H\xa5R\x08\x04\x02\x18\x1c\x1c\x84\xddnG]]\x1d+\xbfP\x8a\xf8'\x93I\xfc\xe4'?\xc1\xb3\xcf>\x8b\xde\xde^\x04\x02\x01\x5cs\xcd5\xacN\x9a\xbc\xdb\x8b8\x96>\xf9S\xa9Tx\xe1\x85\x17066\xc6\xce\x80\xcf\x7f\xfe\xf3sr\xdd>\xfe\xf8\xe3\x18\x1a\x1ab??\xf6\xd8c\xb8\xf8\xe2\x8bY\x9c\xf0l{Z\xa5R\xe1\xd9g\x9f\xc5\xa7?\xfdi\xe4r9\x18\x0c\x06\x16\xae\xd0\xda\xda\x8a\x1bn\xb8\x017\xddt\x13\xae\xbf\xfe\xfa9'\x18\xcd\xc5\xc6\x90\x87b\xb6\xe7\xaa\xd5j\xc4b1|\xeb[\xdf\xc2\xde\xbd{166\x06A\x10\xf0\xc6\x1bo\xe0\xc6\x1boD{{{\xc1\xfb\xd0\x05\x88\x92`>\xf3\x99\xcf\xe0k_\xfb\x1a\xb2\xd9,\xfc~?\x9e}\xf6Y|\xecc\x1fC*\x95\x82J\xa5bY\xd7\xabV\xad\xe2\x8b\x91\xe3\xac\xe1\x8c\x97\x81\xe1\xd9?K\xebfFA\xc9j\xb5\x1a\xe9t\x9ae\xc0\xe9t:\xe6\xa6\xb0Z\xad\x88\xc7\xe3\xecfL%E\x94\x07)\x95O(X`\xff_]\xa4\xf7\xdf\xb6m\x1b\xb2\xd9\xec\xb4l8\xb9{Q\x99iz\xf0\xe0A\xec\xd8\xb1\x03^\xaf\x17\xbd\xbd\xbd\x18\x1c\x1c\xc4\xf8\xf88\x02\x81\x00\x8bG[\x89\xa0\xb9\x88D\x22\xf0x<\xf0x<\xb8\xe5\x96[\x0aJ\xf7(\xdd\xb64']]]x\xe0\x81\x07\xf0o\xff\xf6o8~\xfc8\xc2\xe10\x0c\x06\x03\x9a\x9b\x9ba\xb5Za4\x1a\xa7\xc5z*/x\x84p8<\xef\x0b^\xb1\xf7\xe48s\xc8d2P\xa9Tx\xe4\x91G\xd8\xfa\xb8\xf0\xc2\x0bq\xfb\xed\xb7\x97\x9c\xcbl6\x8b\xf1\xf1q<\xfa\xe8\xa3\xec\xb1\xf7\xbd\xef}x\xf0\xc1\x07Y\xdch)P7\x90?\xff\xf3?\xc7\xddw\xdf\x8d\x5c.\x07\xadV\x8bD\x22\x81\xc6\xc6F<\xf7\xdcs\xd8\xb1c\x07\xbe\xff\xfd\xef\xe3\xa6\x9bnb1\xa6\xf2\x18\xd4\xd3!\x81\xa9T\x8a%\xab\xcd\x16\xa2\xa4\xd3\xe9\x90\xc9d\xf0\x85/|\x81\x85\xc7\xa4\xd3i\xfc\xf2\x97\xbfD2\x99d\x09V\xf2\xefE\xfb\xeb\xe2\x8b/\xc6\xa6M\x9b\xd8{\xed\xd8\xb1\x03~\xbf\xbf\xa0\xd6 \x9d\xa9\x00\x10\x89D\xd0\xdf\xdf\x0f\xaf\xd7\xcb\xc5\x12\x8e\xe5A\x00y\x0c\xd0\xd2W\x00Ia\xa3@\xfe\x8d\x1b7\x02\x00\x0c\x06\x03k\x17\xe7\xf5z\x91\xcdf\xa1\xd7\xeb\x0b\x5c\xbcJ\x90\x0b\x99\x0a\x7f\xca{\x0aS\xad\xc1\x81\x81\x01\xfc\xd3?\xfd\x13N\x9c81k\xdfgA\x10p\xe4\xc8\x11\xec\xde\xbd\x1b]]]\x88D\x22\xf0\xfb\xfd\xe8\xe9\xe9\xc1\xd8\xd8\x18|>\x1f\xe2\xf1x\xc1A\xbc\x92\xc8;eW\x87\xc3a\x0c\x0f\x0fc\xc3\x86\x0d\x05m\xfb\x8a\xcd\xb7\xdf\xef\xc7\xcf\x7f\xfes<\xf8\xe0\x83\xf8\xedo\x7f\x8b\xd1\xd1Qf\x10\x9b\x9a\x9a\xd0\xd4\xd4\x04\xbb\xdd\x0e\x93\xc9\xc4\xe6\xb2\xd8\xd8\xcac\xfd\x94*!\xc7\xd2_;:\x9d\x0e\xbf\xff\xfd\xef\x11\x0e\x87\xa1\xd3\xe9\x90\xcdf\xf1\xd5\xaf~uV\xf5L\x14E|\xf1\x8b_d\xff\xd6h4x\xec\xb1\xc7\xe6\xd4\xbf]\x92$\x18\x0c\x06\x9c\x7f\xfe\xf9\xd8\xbau+{~:\x9d\xc6\xc3\x0f?\x8c\x81\x81\x01|\xf4\xa3\x1fe\xa1\x07\xf2s`.=\xe2\xe7\x12s.I\x12FGG\xe7\x1c\x9f.I\x12:::p\xf5\xd5W\xb3\xdf\xf1\xde{\xef\xa1\xab\xabkZ\xc2\x9b\x1ceee\xf8\xc8G>\xc2>\xfb\xc0\xc0\x00\xc6\xc6\xc6\x8a\xc6\x1f\xe6\xf3y\x0c\x0c\x0c\xe0\xc4\x89\x13\xe8\xef\xefG:\x9d\xe6\x8b\x94\xe3\x8c\x80\x97\x81Y\xe1\x86\x80n\xd7\xa4&Q\x00\xb7$I\xa8\xa8\xa8@6\x9b\x85\xd9l\x86Z\xadF&\x93\x99V\xdbO~\x90\xe9\xf5z\x88\xa2\xc8\x12\x07\xa8\xcc\x8cV\xab\x85\xc5bAee%\x9a\x9b\x9b\xf1\xc6\x1bo`\xff\xfe\xfdhoo\xc7\xb5\xd7^\x8b\x86\x86\x06TUU\xc1h4\x22\x9dN#\x9dN#\x91H`hh\x08\xef\xbd\xf7\x1e\x8e\x1f?\x8e@ \x80x<\x8et:\x8d\xfe\xfe~\x8c\x8c\x8c\xa0\xa9\xa9\x09\x15\x15\x15\xac|\xcdJ\xaa\x0bH\xf3E\xf1{\xa9T\x0a\x1b6l(J\xce\xf3\xf9<\xbc^/\x8e\x1d;\x86\xe7\x9f\x7f\x1e{\xf6\xec\xc1\xd0\xd0\x10\x22\x91\x08S\x82DQ\xc4E\x17]\x84\xea\xeaj\xb8\x5c.F\x00\xe5FWi\xe4\x94\xe5,\xb8\x9awn\x80\xc2>\x1ey\xe4\x11\xb6\xaf\xadV+n\xb9\xe5\x16\x96\x94Q\x0cj\xb5\x1a\x13\x13\x13\xd8\xbau+\xf4z=\x92\xc9$n\xbb\xed\xb6Y\xcb\xc5\xc8\x09\xe4e\x97]\x86\xc3\x87\x0fC\x14Ed\xb3Y\xb4\xb4\xb4\xe0\x97\xbf\xfc%6m\xda\x84t:}Z1\xbdsq\xeb\x0e\x0e\x0e\xe2\xfe\xfb\xefGMM\x0d\xbe\xf9\xcdo\x96\xfc}*\x95\x8a\x85X\xdc}\xf7\xdd\xd8\xb1c\x07+L\xff\xcc3\xcf\xe0G?\xfa\xd1\x8c%]\xe2\xf18>\xfa\xd1\x8f\xe2\xf1\xc7\x1fG(\x14\x82$I\xf8\xfd\xef\x7f\x8f\x07\x1ex\x80e\xcc\xcb?w8\x1cF6\x9bE0\x18D2\x99\x5c\xb6u5\x8b\x85\x85\xf1K\xe32!\x80|\x22\xcf]\x02H\x99u\xf2\xfaW\xf9|\x1eF\xa3\x91\xd5\x0a\xa3\x8e\x11\xf2b\xce\xf4>\x1a\x8d\x06\x16\x8b\xa5\xa0e\x1c\xbd\x8fF\xa3AYY\x19\xaa\xab\xab\xd1\xdc\xdc\x8c\x9e\x9e\x1e\x1c\x8f\x91\x91\x11\x84B!LNN\x22\x99L\xb2\xcf499\x89\xbe\xbe>444\xc0\xe5r\xc1f\xb3\x15(\x8f+\xc1\x80Sfv(\x14\xc2\xd4\xd4\x14\xaa\xaa\xaa\xa6)'4?\x07\x0f\x1e\xc4K/\xbd\x84\x1d;v\xa0\xaf\xaf\x0f^\xaf\x17\x91H\xa4\xa0\xd9}[[\x1b:::P]]\x0d\x9b\xcd\x06\x83\xc1\xc0\xc63\x97\xcbM\xeb\xef]l\xbf\xf3\xf0\x8es\x03j\xb5\x1a\xfb\xf7\xef\xc7\xe1\xc3\x87\xa1\xd3\xe9\x90J\xa5\xf0\xf8\xe3\x8f\x9f4\x083\x5c\xa2(\x1c\xe3S\x9f\xfaTA\xf8\xc6\xcf~\xf63d2\x99\x92D\x8aH\xe5\xad\xb7\xde\x8a}\xfb\xf61bu\xfd\xf5\xd7\xe3\xb9\xe7\x9e\x83\xd3\xe9D6\x9b\x9dS\xd1\xe9\xd3\xd93^\xaf\x17\xf7\xde{/\xd2\xe94zzz\xf0\xd8c\x8f\x15t>Q\x92\xd5\xae\xae.LLL\x00\x00\xca\xcb\xcbq\xe7\x9dw\xe2\xd9g\x9f\x85V\xab\xc5\xe0\xe0 v\xef\xde\x8d\xcb/\xbf|\xc6\xd7\xd7\xd5\xd5\xe1\xd6[o\xc5\x7f\xfd\xd7\x7fA\x14E\xbc\xf3\xce;\x88D\x22\xd0j\xb5E\xdb/R\x9f\xe2\x85\xaa\x13\xc8\xcb\xb0q\xcc\x86\x05s\x01\x173\x06|\xf1-}\x15I\xde\xb5C\xa3\xd1\xa0\xad\xad\x8d\x110jh\x1e\x8f\xc7Y\xbc\x9d\xb2\x06\x96<\x19\xc0`0L\xeb\x19L\x06\xc7h4\xc2\xe1p\xa0\xbe\xbe\x1e\xad\xad\xad\xb0\xd9l\x98\x9a\x9a\xc2\xf0\xf00\x0e\x1e<\x887\xdex\x03\xbbw\xef\xc6\x9e={\xb0\x7f\xff~\xec\xdc\xb9\x13\x03\x03\x03\x08\x87\xc3\x88\xc5b\x05\xbf+\x9dN\xa3\xb7\xb7\x17\xa3\xa3\xa3p\xbb\xdd\x08\x85B\xb3\xb6\x89Z\x8e\xea\x1f\xd5\xfd\xf3z\xbd\xb8\xe2\x8a+\x8a\x1a\xde]\xbbv\xe1\xe7?\xff9\xdex\xe3\x0d\xf4\xf6\xf6\xc2\xe3\xf1 \x12\x89\xb09'R}\xfd\xf5\xd7\xc3\xe9t\xa2\xb2\xb2\x92\x95\x8eQ\xba\x92\x95Y\xfe3\xa9&\x1cK\x1f?\xfb\xd9\xcf\x00\x9c\x8c\x89\xd3\xe9t\xb8\xe3\x8e;X\x1b\xb3\xa2\x86B\x10p\xfc\xf8ql\xdf\xbe\x9d\xc5\xf3\xde{\xef\xbd0\x99L%\xe7\x9cb\xfc\xbe\xf4\xa5/\xe1\xa5\x97^b\xcf\xbd\xf2\xca+\xf1\xd2K/\xc1\xe9t\x96$\x9e\x0b)BX\xadV\xac_\xbf\x9e\xfd|\xe0\xc0\x01<\xff\xfc\xf3E_322\x82\xbe\xbe>6&\xa1P\x08w\xdcq\x07\x8b\x0bT\xab\xd5\xf8\x8f\xff\xf8\x0fx\xbd^$\x12\x09\xa6\xa4\xcb\xf7\xc1\xa4{\x12\xdf\xf8\xc67\x0a\xbe\xdfK/\xbd\xc4.\xd5r[\xa9\x8c\x87>\x1d\xc4\xe3qvy^\x0a\xe0g\xc2\x0a \x80\xf31\x12\xa5\x16\x87\xbc\x84\x0c\xc7\xe2\xaa\x7fD\x12R\xa9\x14\xfc~?\xee\xb8\xe3\x0eF\xd8\x08\xe1p\x98\xa9x\x94\xe1V\xac\x9f\xa3N\xa7\x83\xddng\xc9\x03r\x02(W\x01].\x17\x9a\x9b\x9b\xd1\xde\xde\xce\x8a\x10\x87\xc3a\xf8\xfd~x\xbd^\x8c\x8f\x8fcbb\x02\xb1X\x8c\xd5\x0fL&\x93\xec}(\xeehhh\x08\xc3\xc3\xc3\x18\x1d\x1den\x13e\xff\xda\xe5:oT\x1f1\x1c\x0ec||\x1c\xd5\xd5\xd5(//\x9f\xb6gz{{\xf1\xe2\x8b/\xe2\xe0\xc1\x83\x18\x19\x19\x81\xc7\xe3A\x22\x91(p\xdfK\x92\x84\x0b/\xbc\x10UUU\xa8\xaa\xaabsH\xf3]l\xaf\x16#\x86\x1c\xe7\x0e$I\xc2\x9e={\xd8\xcf\x1f\xfe\xf0\x87a\xb3\xd9fu\xe1n\xdd\xba\x95\xc5\xdc\x02\xc0\xfd\xf7\xdf_\xb2\xc7/%\x85l\xdf\xbe\x1d\xdf\xfe\xf6\xb7Y\x0cq[[\x1b\xb6o\xdf~F3\xc7\xa9\x06\xe9\x83\x0f>\x88\xd6\xd6Vv\xce\xfd\xeaW\xbf\xc2\xb1c\xc7\xa6\xadc\xea\x19L \xd2{\xf7\xddw\xb3\xe7MMM\xe1\xd7\xbf\xfe5\x0e\x1c8P\x90\xd0A\xc8\xa43\xa8\xa8\xa8\xc0\xa6M\x9b\xd8\xe5\xf9\xcd7\xdf\x84\xc1`(8\x7f\x95\x04\xf0t\xf7S\x7f\x7f?\xf6\xee\xdd\x8b\xce\xce\xce%aG\xb9\xabw\x05\x11@9\xa1S.>\xf9\x02\x98)\xb0w\xa6zc\x1c\x0bo\x04\x88H\x84B!\xc4\xe3q\xd4\xd7\xd7\x17(z\xc1`\x10\xe9t\x1a\xc1`\x10\xe1p\x98e\xff\xca\xfb\xce\x12\x0c\x06\x03*++\xa1\xd5j\xa7\xa9G\xd4\xc7\xd7h4\xa2\xbc\xbc\x1c\xf5\xf5\xf5hllDKK\x0b\xe2\xf18#\x81\xe1p\x18\xd1h\x94\x156\xa6vfT\x80\x9a\x0aVS\xfb\xb2c\xc7\x8eatt\x14\xe3\xe3\xe3\x88D\x22\xec0]\x09\xf3\x16\x8dFY\xe1\xe7\x0f~\xf0\x83\x05\xbdy\x01`ll\x0c\xbf\xfb\xdd\xef\xb0\x7f\xff~\xf8\xfd~6>\xcan-j\xb5\x1a\x17_|1jkk\x0b\xd4?\xb9\xd2[\xea`\xa7B\xd4\x1c\xe7\x0e\x8e\x1f?\xceH\x0f\x00\x5c}\xf5\xd5E\x09\xbf\x1c\x89D\x02\xaf\xbf\xfe:\xfb\xf9\xb6\xdbnCccc\xc9\xd7\xa8T*V\xe7O\xab\xd5\x22\x99L\xc2j\xb5\xe2\xddw\xdfea\x05\x8bE6f\xfa?\xa3\xd1\x88\x87\x1ez\x88\x95.\x12E\x11_\xfa\xd2\x97\x90\xcf\xe7\x91\xc9d\xe0\xf7\xfb155\x85X,V4\xeb\xfd\xe2\x8b/\x86^\xafg!\x18;v\xec@8\x1c\x9e\x96\xb8!\xb7a\x9f\xf8\xc4'\xd8\x99:66\x86\x91\x91\x11F\x9a\xc9s1W\x028[\xa2\x15\x95\x9d\x11E\x11\xd1h\x14\xd1htI]^g\xeb\xfd\xce\xb1\x0c\x14\xc0\xb9L\xba\xbc\x91|\xb1\xd7s\x17\xf2\xe2\x92\x08\xca\xfa\x0d\x87\xc3\x98\x9a\x9aBEE\x05\xaa\xab\xab\x0b6\x22\x95|\xa0\x96mD\xb0\x8a\x19\xfc\x8a\x8a\x0a\x18\x8dFV\x97\x8f\x12\x08\xe4\xf3-\x8a\x22\xca\xca\xcaPUU\x85\x86\x86\x06\xacZ\xb5\x0aF\xa3\x11\x99L\x86\xf5\x08\xa6^\xb6Tf\x81\x0eF\xeaTB\x17\x07\x9dN\x87\xc1\xc1A\x0c\x0d\x0da||\x9c\xb9b\x96\xb3\x0aH\xea\x1f\xc5\xfe\x0d\x0f\x0fc\xe3\xc6\x8d0\x99L\xd3\x9e\xb3\x7f\xff~\xf4\xf7\xf7#\x14\x0a1\x82\x9f\xcdf\xa7\x8dOGG\x07\x9a\x9a\x9a\x98\xfaG\x89<\xc5\x0c\xd9L\xfbu\xa6va\x1cK\x13\x87\x0f\x1fF0\x18\x84J\xa5\x82\xcdfc\x85\x9fK\xad\xbb\x9e\x9e\x1e\xd6\xef\x17\x00n\xbc\xf1\xc69\xc5\xdc>\xf1\xc4\x13\x18\x1f\x1fg\x04\xe9\x97\xbf\xfc%\x1c\x0e\xc7Y\xeb\xe0\x93\xc9\x9cT\xe5\x1e~\xf8a\xe6\xb6\x0d\x87\xc3\xb8\xef\xbe\xfb\xa0\xd1h\xb0w\xef^\xbc\xf7\xde{\xacs\x91\x92\xa0\x94\x97\x97\xe3\x82\x0b.`\x8f\x1dD\x22\x11f\x8c\xe4c\xe4\xf7\xfbq\xf8\xf0aLLL \x10\x080w\xba\xb2\x0b\x82N\xa7\xc3\xc6\x8d\x1bQ]]\x8d\x8a\x8a\x0a\x98L&F\xb2g2\xce\xc5B6\xf8E\xed\xdc\x00\xed\xa7W^y\x85\xcdeMMM\x01\xa1)\xb6\xe6T*\x15\xdez\xeb-\x00'c\xd9\xca\xcb\xcb\xf1\xa1\x0f}h\xd6\xdf511\x81\xef}\xef{\xec\xb1\xbb\xee\xba\x8b\xbdn\xb1\xd4\xbf\xb9\xf4\x16O\xa7\xd3\xe8\xe8\xe8\xc0u\xd7]\xc7\xe2\xf9\x9e\x7f\xfey\xbc\xfa\xea\xab\xac\x9da1\x12\xa6R\xa9\xa0\xd3\xe9p\xd1E\x17\xb1\xc7\xfa\xfb\xfb\xe1\xf7\xfbg\xfc<*\x95\x0a\x06\x83\x01UUUl\xff\x1d\x04\x83A\xdcv\xdbm,\xf1C\x8eP(\x84\xb1\xb11\x0c\x0e\x0e\x22\x1c\x0e#\x91H\xb0\xd8\x18\xb9\x92D\xf3q\xfe\xf9\xe7\xc3b\xb1\xc0f\xb317m\xa9\x182\xa5\x0aX[[\x8b\xe6\xe6f\xf4\xf6\xf6B\x14EV\x9a\x86\xe6]\x14E\xe6N\xa1\x9e\x9c\xf4\xb7(\x8a\x08\x87\xc3\xe8\xef\xefg\xad\xcc\x9cN'+G\xb3\x1c\x08\x09\x19\x85x<\x8e`0\x88\xc1\xc1A\x96Y(\x1f\xe7\xc9\xc9Itww#\x16\x8b\xb1\xb8\xa0T*UP\xb3\x91\x08\xde\xe5\x97_\x8e\x8a\x8a\x0a8\x9dN\x94\x95\x95\xb1q.\xa6\xd8\x92\xa23\x9b\xd2R,\xa4\x83c\xe9\xac!A\x10\x0a2y\x01`\xdd\xbau3v\xf1\xa0\xd7\xec\xdc\xb9\xb3\xe0\xf1\x8b.\xbah\xc6\xe2\xcf\xb4\xc6\x9ey\xe6\x19\x00'=:\xf5\xf5\xf5X\xb7n]\xc9.#\xb3|\xf8\xb90\x89y\xbfm*\x95\xc2e\x97]\x06\x8b\xc5\x82H$\x02\x00\xd8\xb9s'ZZZ\x98\x9d*v~\x85\xc3a\xdc\x7f\xff\xfd\xd0j\xb5\x88\xc7\xe3,\xfe\xae\x94\x22J\x9d\x8e(<\xc5\xe3\xf1\xa0\xbd\xbd\x9d\xb9h\xe5\xe5\xd3\x02\x81\x00\xd4j5\x12\x89\x04\xecv;\x9cN';\xc7i\x8c}>\x1f***\x10\x89DX8\xd5\xd8\xd8\xd8\xb4\xb9\xa40\xaa\x85\xdc\x97\xd4\x13Y\xa7\xd3!\x97\xcbadd\x04\x83\x83\x83\x00\x00\x8b\xc5\x82\xb6\xb66\xbe\xe18\x01\x9cy\xf1\x10\x01\xd4h4\xc8\xe5r0\x18\x0c,K\xd4\xedvcdd\x04\xfd\xfd\xfdp8\x1c,\xa1@\xd9\xe0\x9e\x03\xd3T\x1d\xfa\x9b\x5c\xa0\x99L\x86e\xd5\x86\xc3ax\xbd^\xf4\xf4\xf4\xc0n\xb7\xe3\xf6\xdboGUU\xd5\xb4\xb9\xc9d2\xd8\xb1c\x07v\xef\xde\x8d@ \xc0\x0a1\xa7R\xa9\x82Vp\xf4\xfb\xd7\xad[WP@X\x19\xffW\x0cD\xe4\xacV+\xaa\xab\xab\xd1\xd0\xd0\x80\xd5\xabWchh\x88\xa9}Db\xe5\xb1\x80\x14\x9fCa\x04\xf4\x1d\x0d\x06\x03:;;\xd1\xde\xde\x8e\x91\x91\x11\xe6\xd2\x9c\xcbg9W\xd4?r\xfb\x8c\x8c\x8c\xa0\xbe\xbe\x1eN\xa7\xb3\xe0`\x97$\x09\xfb\xf6\xedCww7+\xfbB\xea\x822\xcbp\xed\xda\xb5hjjBuu5\xecv;t:]\xc9\xd6Ss\xddo\xbc\x17\xf0\xd2?+\x0e\x1e<\xc8\xc8\x0f\xc5\xa5\xcd6g\x87\x0e\x1d\x02p2\xc1\xa0\xae\xaenN\x8a\xce\x1f\xfe\xf0\x07\xd6e\xe4o\xfe\xe6o\x0aT\xc7y\xe3\x14\xd6\xdf|\x5c\xc1\x9b7o\xc6\xb6m\xdb\x00\x00o\xbf\xfd6>\xf9\xc9O\xb2\xf2S3\xbd&\x1e\x8f\xb3R1\xb3\x91\xabL&\x83\xea\xeaj\x98\xcdf\x84\xc3a\xd6\xfb\xf7\xf2\xcb/G4\x1a-\x88\xe3U\xa9T\x18\x19\x19\xc1\xf0\xf00\xb2\xd9,\x9cN'6m\xda\xc4\xcef\x82\xd7\xebE\x7f\x7f?\xe2\xf18\xb3\x89>\x9fo\xda\xe7!\xaf\xcdlc_\xac\xc7{\xb1\xc7(\x16qdd\x04\xeb\xd6\xadCYY\x19\x02\x81\x00\xfb\x0cn\xb7\x9b\xd5YT\x8eY\xb19\xe1\xe7\xc5\x0a#\x80\xe4\x02\xa4\xd6?z\xbd\x1e\xa1P\x08\xa2(\xb2\x1e\x89\xc7\x8f\x1fg\xeeD\xca\x94\xa2 \xf5\xb3U>`)\x1e\xe8\xf2\x929D\xfa\xc8\xddKeU\xa2\xd1(\x82\xc1 <\x1e\x0fB\xa1\x10.\xbf\xfcr\x5ct\xd1EE\x8b\xf9\x0a\x82\x80\xbe\xbe>\xbc\xf3\xce;\x18\x1b\x1bC0\x18D4\x1ae\x99eJW\x82 \x08\xd8\xb0a\x03\xaa\xaa\xaa\xe0p8X\x06\xb0\xb2\x06\xa0r\x93\xd3!e4\x1aQQQ\x81\x86\x86\x06455\xa1\xae\xae\x0e\x03\x03\x03\x05\x87\x18)}r\x12(\xbfDP}\xb1L&\x83\xc3\x87\x0f\xa3\xa9\xa9\x09\xf5\xf5\xf5p8\x1c\xcc\xady.\xabR\xf2\xd8?\xaaQv\xfb\xed\xb7O\xcb\x8e\x9e\x9c\x9c\xc4\xd1\xa3G\xe1\xf1x\x98\xfb\xb7X\xe6\xaf(\x8a\xe8\xe8\xe8@MM\x0d*++QVV\x06\x9dN\xc7\x0e\xf0\xd3q\x17\x15\xdb\x97|\xaf.-\x02\xd8\xdf\xdf\xcf\xce\x0cy\xdb\xc7R\xaf9|\xf80\xbb\x84o\xdc\xb8\xb1\xe4kT*\x15\xc6\xc7\xc7Y\xf7\x1e\x00\xb8\xf9\xe6\x9b\xd9\xc5n\xde\x18\x0d\x03\xbf\xef\x06D\x01@\x91\xdfI\x0f]\xdb\x044\x9b\xe7E(T*\x15b\xb1\x18\xae\xb9\xe6\x1al\xdb\xb6\x0d:\x9d\x0e\xe3\xe3\xe3s\xda\x03\xf3\x11\x22\xb2\xd9,\xacVkA\xb9$\x9f\xcf\x07\x00\xac\x16k\xb1q\xa7\x18\xe7t:=\xed\x1c\xcdd2\x18\x19\x19\x99V[\xb7\x18\xf9\x94\xf2\x12\x90/>|\xf3\xdd\xbb\xb1X\x0c\x83\x83\x83\x88\xc7\xe3\xe8\xee\xeeFmm-s\xfb\x92::\x97\xb9^\x8c\xe4\x0f^?x\x09\x11@r\xdb\xc9'\x99\x8c~,\x16c\x1d\x1d\xa2\xd1(\xecv{A?Z\x8dF\x83\x13'N0\x22\x13\x8f\xc7QSS\xc32\xaf\xe6\xbb\x01\x97\x0b\x94\x07\x13);D\xfc\xa8TK,\x16+\x88\xa3\xd4h4hnn\xc6\x95W^9-XY\xbe\xd9\x8f\x1f?\x8em\xdb\xb6\xb1\xae\x11\x81@\xa0@IR\x06+\xd7\xd5\xd5a\xed\xda\xb5,\x8e\x8c\xdc\xbf\xc5H\xbf\x12\x94\xf9Ku\x01\xa90\xf4\xd8\xd8\xd84\xd7\xb5\xc1`(\xd8\xe0\x82 \xb0\x18R*\xe3@1<\xc3\xc3\xc3hhh`I)\xa2(\x9e\xb3\x8d\xd5\xe5\xb1\x7f\x91H\x04\xe3\xe3\xe3\xacc\x87RQ\xa7\xde\xca\xa4\xda\x16+\xfa\x0c\x00UUUX\xbf~=\x5c.\x17S\xffHe?\x9dCw9\x97\xdfYN\x04p``\x00Z\xad\x16\xe9t\x1a\x8d\x8d\x8dszMgg'\x8b\xdd\xa6Vj\xa5\xd6Kgg'K\xe42\x18\x0c(//?\xf5\x0f\x9e\xce\x01\x13Q@3\x03\x01\x14p\x92\xdc$\xb2\xa7\xe4\x06N\xa7\xd3\xd8\xb0aC\xc1c===hnn>\xed\xf5L\xaf\xa7s\xd3f\xb3\xb1\xc7\x02\x81@\xc1\xe5n&2\x94\xcb\xe5\x10\x8b\xc5X\x17\x95R\xf6`\xa6\xef'\xc3E\x87\xb0\x00\x00 \x00IDATe%\xe4uy\xa80s\xa2\xcf\xc0\xc0\x00\xb4:-\xaa\xab\xaaY\xd8\xc7\xc4\xc4\x04&''\xb1f\xcd\x1aX\xadVF\x5c\x93\xc9$k\x8fG\xb1\x88d\x8f\xb3\xd9,\x92\xc9$\xccf\xf3\xbc/\x80s\x1doyMZ\xb2gT\xab\x96B\x7ff\xb2\x9f\xc41\x96k\xac2\xc5\x89\xce\xf5\xb2%\x9e\xce\x22\x97\xdfJ\xe4\x7fK\x92\x84\xf2\xf2r\x8c\x8d\x8dM{>%#\x10,\x16\x0b3\xea:\x9d\x0e\xe5\xe5\xe50\x9b\xcdp:\x9d\x08\x87\xc3x\xf3\xcd7\x11\x08\x04P[[\x0b\xb3\xd9\xcc\x12\x00V2\x01\xa4\x80`\x22eJ\xa5\xc7\xe1p\xa0\xac\xac\x0c\xcd\xcd\xcd\xa8\xaa\xaaBmm-s\x9f*\x95#\x8a\x19\xe9\xed\xed\xc5\xb6m\xdbp\xe4\xc8\x11\x8c\x8c\x8c\xc0\xef\xf7#\x1a\x8d\xb2\xd2\x02\xf2\xa4\x0a*\x7f\xf0\xfe\xf7\xbf\x1f\x0d\x0d\x0d\xa8\xac\xac\x84\xd9l.(\xcf2\x97uE\xae\xdc\xf2\xf2r\xd4\xd5\xd5\xa1\xad\xad\x0dG\x8f\x1e\x85\xdf\xefg\xc9&\xa4l\xca\xe3\xcb\xe41\xa4\xa4&\x93[\xe6\xe8\xd1\xa3hhh@MM\x0dS\x8d\x89 \x9e\x8b\xf3M\xfb\xc5\xef\xf7#\x10\x08\xe0\xca+\xaf,\xd8\xdc*\x95\x0a^\xaf\x17\x9d\x9d\x9d\x98\x9a\x9ab\x074\x112\xf9\x9cK\x92\x84M\x9b6\xb1\xba\x7ff\xb3\xb9@\x95P\x92E\xa5\xda\x5c\xaa\xec\x07\xc5\xf0\xce\xe7f?\xdb\xfbr,<\x01\x9c\x9a\x9ab\x17\xaa\xe6\xe6\xe6\x92\xc6\x97\x1e\x0b\x87\xc3,\xde\xaf\xb6\xb6\xb6`\xee\x8a\xd9\x84\xd1\xd1QFz\xea\xeb\xeb\x0b.p\xf32\xf6*\x15TR\xfed\x0c\xa0<\xd9\xa9\xe0C\xaaNK\x05\x92$\x09f\xb3\x19f\xb3\x99\xd5\xcc\x1b\x1e\x1eF[[\x1b\x8bC\x9e\x8f=\xd4\xeb\xf5\xd3\xceK\xba\xa8;\x9d\xcei\x040\x91H`rr\xb2d\xdd\x5cj\xc7y*H\xa5R\xc8J\xd9\x821\x97'z\x09\x82\x80\x13'N\xa0\xab\xab\x0bZ\xad\x16\xa3#\xa30\x9b\xcd\x88F\xa3\xec\x1cN&\x93\xb8\xe4\x92K`4\x1a\xa7\x95\x95\x917\x05\xa0\xc7\xe3\x898\xca\xca\xca\x98m\x9a\xc9\xfd{\xaa\x9c\x83\x0a\xff\x93\xfd\xa0nV\xd9l\x16\xb5\xb5\xb5X\xbf~=\xcb\x19\xa0\xf5@\x9d\xa3B\xa1\x10\xf4z=.\xb8\xe0\x82\x82\x18\xd6\xb9\xac\x9bd2\xc9<\x5ct\xf1\x96$\x09\xb1X\x0c\x16\x8b\xa5\xe0}\x8a\xbd\x9f\xfc\xff\xe8\xbbP\x85\x0d\x9b\xcd6\xe3\xe5z\xa6\xcf\xa6t\xd1\x07\x02\x01\xf4\xf4\xf4@\x92$\xd4\xd4\xd4\xa0\xa1\xa1\x81\xbdvrr\x92\x09it\xe6\xe7\xf3y\x88\xa7K\xa2\xe4\x85\x9ciQi\xb5ZLNN\xa2\xa2\xa2\x02\xa9T\xaa\x80\x14P\xe0\xbe|Ah\xb5ZVC\xce\xe1p`\xcd\x9a5\xb8\xfd\xf6\xdbQYY\xc9\xcaY\xe4\xf3y\x84B!\x04\x83\xc1eY\xe6c\xae\xaa*\x15T\xb6\xdb\xed\x8c\x04\x91+\xddf\xb3\xc1d21uL9\xb7\xca\xf8\x0e\xbaA\xed\xde\xbd\x1b/\xbe\xf8\x22\xba\xbb\xbbY/\xdeh4\xca\x02\x9c)+\x8d\x16S.\x97\xc3\x86\x0d\x1bp\xc9%\x970\xf7/\x91\xf3\xf9\xb8[)\x09\xc8f\xb3\xb1X\xc0\xf3\xce;\x0f/\xbf\xfc2\x04A@*\x95b\xea\x94\xbc\x9c\x01\xb9\x82\xa9\x86\x17e\x03\xe7r9\x0c\x0e\x0ebpp\x10\xf5\xf5\xf5\xac\xbb\xc5\xe9(\x5cg\x1b\xe9t\x1a\x91H\x04>\x9f\x0f:\x9d\x0ek\xd7\xae\x9dv@P1l\x8f\xc7\xc3\x92?h\x8f\xc8\xc3\x04jjjX\xe1g\x87\xc31\xad\xed\xdbL\x87\x8d\xb2m\xa3\x92(\xca]\xf4\xb3\x19w\xba\xec\xc9K@q\xb7\xcd\xe2\x83\x92/.\xbc\xf0Bx\xbd^\x0c\x0f\x0f3\x028\xd3\xe5\xa8X\x92\x07)A\xa5\x14\x06\xb7\xdb\xcd.\xab.\x97\x0bf\xb3y\xda\xf3\xe7<\xe7\x1a\x0d \x08\x80\xa0\xa6E\xaf\xf8\xfb\xf4\xc6\x85\xca\xd4\xd4\xd4\xd4\xa0\xbb\xbb\x1b\xc0\xc9,\xddS\x09\x1bQ\xab\xd5\xa8\xa8\xa8\xc0\xd4\xd4T\x81}\xa2\xcb\xac\x5c\x09\x0d\x04\x02\x8c\xa0\x95\xb2e\x92$\xc1\xef\xf7\x17\x9d\x8b\xb9z\x10\x90GQ[\x90\xcf\xe7Y\xb9/\xba\xd4{<\x1ex<\x9e\x82\xe7\x06\x83A\x9c8q\x02k\xd7\xae\x85\xdb\xed\x9e\x95\x90\x0c\x0f\x0d#\x93>\x19\x92\x13\x08\x04\x10\x8dF\x17d\x8f\xabT*\x04\x83A\xb8\xddnv\x86\xc8\xc7\x98\xe2'\xa9\xbe%\xd9\xb8\x9e\x9e\x1e\x8c\x8e\x8e\x16(\xad\xe1p\x18v\xbb\x1d\xa2(\xa2\xb5\xb5\x15&\x93\x09\xbd\xbd\xbd\xe8\xe9\xe9\x81\xd1h\xc4\x05\x17\x5c\x80\xb2\xb22\xf6\x9d\x8e\x1f?\x8e\xc1\xc1A\xf6\xfc\x96\x96\x16\xf4\xf4\xf4```\x00\xd9l\x16:\x9d\x0e\x97^z)L&\x13\xb3\x95\x9d\x9d\x9d\x98\x98\x98\x80\xc1`@{{;\x5c.W\xc1\xda\x9f\x9c\x9c\xc4\x81\x03\x07X7\xa5\xf5\xeb\xd7#\x9b\xcd\xc2\xeb\xf52QDN`'''\x11\x89D \x8a\x22\xf3p\x112\x99\x0c:;;\xd9\xba\x9a\x9a\x9a\x82N\xa7\x83\xcb\xe5BWW\x17\xba\xbb\xbb\x91\xcf\xe71::\x8a\x0b/\xbc\x10f\xb3\xf9$\x97\x90W\x15\x9f\xaf\x14K\x03\x9f\xc9d\xe0v\xbb\x11\x0c\x06\x99\xf4O\x8bK\xe96\xa4/\x1b\x8f\xc7\x19Y$\x03m2\x99\x98q\x22bSYY\x89\x9a\x9a\x1a~\x82\xcfC!\x9cOl\xca\xeb\xaf\xbf\x8e\xed\xdb\xb7\xb3\x96jtS\xa0^\xbc\xf2\xac_\x9a?\x83\xc1\x80K/\xbd\x94\xd5\xa4\xa26l\xf3%\x80\xf4\x19\x92\xc9$\xa2\xd1(\x04A`\x89!\x1e\x8f\x07\x82 0\xb5\xd8h4N\xcb2%7\xb2N\xa7c\xaen\xbf\xdf\x8f\xee\xeenX\xadVh4\x1a\x84\xc3a\xd8l\xb6\x19\x13BJ\x8dU\xa9[9\xfb7\xf2,\xbeF\xe9bQ\x12ny(\x84\xf2&^p0#\x8f\x5c\xf6O1\x9d~\xbf\x1fCCC\xac\xa5\x94\xfc\xb9\xd1h\x14\x87\x0e\x1d\xc2\xe4\xe4$\x02\x81\x00\x8b\x87T\x96\xeb\xc9\xe7\xf3\xd8\xb8q#r\xb9\x1c\x22\x91\x08\x9bgRm\xe5\xdd\x08\xe4\xddz\xe8p\x09\x85B\x98\x9c\x9c,X\x17\xc5\xc2\x8f<\xf20\x8c%Q;\xe0A^#\x14\x84~X\xad\xd6\xe9$\xf4\x14\xd5\xe4\x5c.\x07\xbb\xdd\xce~\x8e\xc5b\xa7D\x00\xc9\x86)\xd73\xd9C\xa7\xd3\xc9\x88\x22e\x1d\xcf\xe5\xf3Ro\xf4\xf9|7\xaa\xd9(I\x12\xeb\x92\x14\x0e\x87Q^^\x0e\xadV\x8b\x9e\x9e\x1e\x0c\x0f\x0f\xcf\xd9;2<<\x8c\x91\x91\x919y\xde\xc6\xc7\xc7\xd9s\xb3\xd9,+F_\xcc^\x95R\xb8\x8a)p\xa4.[,\x16\x88\xa2\x88L&\x03\x87\xc3\x81\x96\x96\x16D\x22\x11\xf4\xf5\xf5\xb1\x8b\xb0\xd9d\xc6\xbb;\xdf\x85\xd7sR-t8\x1c\xec\xfc\xa2$\x1eI\x92011\x01\x8b\xc5\x02\x8f\xc7\x03\xb5Z\x8dP(\x84\xae\xae.ttt\xc0`0\xa0\xaf\xaf\x0f'N\x9c`cz\xf8\xf0atvv\x16x\xd3\xb2\xd9,v\xee\xdc\x89\xf6\xf6v\xf6\x9a\x89\x89\x09\xa8\xd5j\xa4R)\x1c8p\x00\xe7\x9dw\x1etz\x1db\xd1\x18&''\x0bzBG\xa3Q\xd6jQ\xadV\xa3\xaf\xaf\x0fn\xb7\x1b\xf5\xf5\xf5\x08\x87\xc38z\xf4h\x81\x08\xd2\xd9\xd9\x09\xb3\xd9\x8c\xc6\xc6F\xb8\x5c.\x0c\x0e\x0e\xb2\x5c\x0a\xaa\xa4\xf1\xce;\xef\xa0\xba\xba\x1a~\xbf\x9f\xed\xdf@ \x80\xbe\xbe>\xb4\xb4\xb4`jj\x0a\xe2{\xef\xbdw\xda\x12:\x19\x15\xb7\xdb] \x9b\x0b\x82\x80d29\xad\x03\x01\xb9\xecHB\xcdf\xb3L\xb1\x92+\x18\xf2N\x10\xa7BN\x973\x8a\xcdQ\xa9\x22\xbe\xf2\xc5\x9aH$\xd0\xdd\xdd\x8d\xd7^{\x0d]]]\xac\xf4\x8e\xcf\xe7C<\x1eg\xca\x1f\x91\x08e\xa0\xf1%\x97\x5c\xc2\xfaL\xba\xddnD\xa3\xd1\x02\xf7\xef|n\xfatK\x8dF\xa3H&\x93\x10E\x11\xb5\xb5\xb5\x98\x9c\x9cd\xe4\x93.\x15\xa4\x80\x12\x11$\x05\x93n\x83d\xb0\x8e\x1d;\x06\x9b\xcd\xc6n\x8c\x0e\x87\x03:\x9d\xee\xd4\xb3\x10K\xcd\xc3I\xe6\x87\x93\xb6?\xbf\xa0sKk?\x12\x89\xc0`0`\xcd\x9a5\xd3J:\xec\xdb\xb7\x0f\x03\x03\x03\xacc\x0b\x91?e1Y\xa7\xd3\x09\x8b\xc5\x82d2\x09\x9f\xcfW\x90}/\xff]\xc5\xe6-\x97\xcb!\x1e\x8f\xc3\xe3\xf1 \x91H\x14\xb8W(\x0bq\xae\xc9#\xd1h\x14\x1e\x8f\x07###\x88\xc5b\xacn\xe4B\xee\x83\xd39\xcf\x94\xef9/\x17 \xad\x85\x05\xfc\xfc\xf3\xfd\x0c\xb3\xc1\xe3\xf1\xb0\xf3\xa0T\xc1v\xb9\xaa%_GtY\xcbKy\xa8\x04\xd5\x8cdj\xcd\x9a5\xc8d2\xa8\xab\xabC2\x99\x9cV\x1ajN\xdfI\xa5\x82\x86B]\x00H\xb9?\x15\xa0\xcff\xb3\xa8\xac\xac<\xf9\x19\x16`\xdb\xc9\x09\xca\x5c\xd6\xa3<\xfb^\xfe\x98^\xaf\x9f\xf6\x7fd\xcb\xe4*\xde|\xe64\x9dN\xb3\xec\xe1\xb9\x90\x7f\xbd^\x0f\xab\xd5\x0a\xaf\xd7\x0bI\x92p\xe2\xc4\x09v\x8eR\xa1\xfeD\x22\xc1\xe2\x0a\xe5\x7f\x94\x97V\xf2\x10\xcc\xa7&/}\x7f:\xbf)1\xb1\x14\xf1\x9fK\x0b\xbfP(\x84d2\x09\xa7\xd3\x89\xd6\xd6V8\x1c\x0e\xc4b1\xb8\x5c.V\xcagdd\x04\x89D\x02CCC'\xd5\x5c\x9f\x1fz\xbd\x1eMMMhnn\x86\xd7\xeb\xc5\x91#G\x90H$\x98-\xa1\x84I\xb5Z\x8dL&\x03A\x10\xe0\xf3\xf90>>\x0e\xa3\xd1\x88\xbe\xbe>\x08\x82\x00\x83\xc1\xc0B\x8dr\xb9\x1c4\x1a\x0d\xca\xcb\xcbQQQ\x81\xf1\xf1q\xf8\xfd~\xec\xdb\xb7\x8fy\xc9\x8cF#\xf4z=\xc2\x91\x931\xd9\x9d\x9d\x9d\x90$\x09\xf1x\x9c\x952\xa3P\x1c\x9f\xcf\x87h4\xca\x08\x5c,\x16\xc3\xe8\xe8(\xc6\xc7\xc7Y\xc2lee%\x9cN'2\x99\x0c\xb3\xd7\xc7\x8f\x1f\xc7\xe8\xe8(\xa2\xd1(\xab\xefZQQ\xc1jwNMM1\xd2N\x89\xb6\x14\xe2\x15\x0a\x85 RP\xef)\x1dx*\x15rR\x0e\xb9l\x0eSSS\xac\xce\xd3L\x86DyP\x90A\xa6\xbaB\xe1p\x18Z\xad\x16\x1e\x8f\x07\xef\xbd\xf7\x1e\x1c\x0e\x07\x93T\xe9\xbd\xb8\xbbhn\xd2?\xfd-w\xdd\x05\x83Atuu\xe1\xd0\xa1Cl\xe1P\xcd\xc5X,\xc6b\xfe\xe4\x0a\x92R\xb5\xda\xbcy3n\xb8\xe1\x06\xac^\xbd\x1a\x8d\x8d\x8dp:\x9d\xd0\xeb\xf5,\x93t\x0eV\xb2P9P\x01\xd9\xcc\xc9\x1b\xae\xdb\xed\x86\xd3\xe9\x84(\x8a\x98\x9a\x9a\xc2\xc8\xc8\x08ssS\xbc#\x1d\xa2D\x00I)\xa6D\x0f\x8aY\x09\x04\x020\x9b\xcd\xac\xc4\x0c}Nf\xb4\xf2s$\x0bE\x94\xbd\x85\x9c\xa7bcF\xb13\xd1h\x94\xd5c\xbc\xed\xb6\xdb\xa6\xed\xa7P(\x84\xc3\x87\x0fchh\x08\xe1p\x98\xa9\x7fr\x97=\xe1\xfd\xef\x7f?.\xbd\xf4Rttt\xa0\xb1\xb1\x91%\xed(U\x19e\x8c(\x19 \x9f\xcf\x87\xee\xeen\x8c\x8d\x8d!\x14\x0a\xb1\xf7&W\xc2\x5c\xe1r\xb9\xd0\xde\xde\x8eK.\xb9\x04\xd5\xd5\xd5\xc5\x95\x9c%~\xd1Z\xca\x9f\xf5t\x09\xafr\x1d\x96\x95\x95\xb1\x90\x0b\x9a\xbf\xab\xae\xbaj\xc6:y\xc0\xc9\xba\xa0\x0f?\xfc0S\x92\xf5z\xfd\xa9\x8d\xa3J\x05\xa1?\x08\xf1\xf8\x11\xe4E\x81\xb9C#\x91\xc8\x9fTh\xb5l\xfd\x9e\xa2@@FY\xeeY\x98M5\xa63'\x10\x08L\xebU\xae\x0c\x85 \xc1CIv\xe6J\xfe\xe9RN\xaf\xb5\xdb\xed\xac\xf4J1\x98L&TTT0\xe5\x87~\x9fF\xa3a\x15\x1d\xe4kE\xadV\xa3\xb1\xb1\x11\x91H\x84\x95\xb6\xa1\xef\xb2f\xcd\x1a$\x93I\x1c:th\xd6\xd0\x0e\xf9\x19B\xef-\x0f\xe1QzB\x8a\x89\x143\x8d7u\xaf\xaa\xac\xacD}}=\xda\xdb\xdb\xa7\x9d\x1b&\x93\x09eeeH$\x12\x18\x1c\x1cd\x8aYMM\x0d\xd6\xacY\x03\x95J\x85\xba\xba:H\x92\xc4H]:\x9df\xe4O\x14E\xb8\x5c.VP\xbb\xab\xab\x8b\x11D\x97\xcb\x85\xf5\xeb\xd7C\x14E\xd6q\x8a\xcahi\xb5Z\xd4\xd7\xd7chh\x08\x13\x13\x13\xc8\xe5r\xb0Z\xadhii\x81\xc9d\xc2\xe0\xd0 \xfaz\xfb\x18\xc1s\xb9\x5c(++\x83\xd3\xe9Dyy94\x1a\x0d+\xd7F\xbd\xee\xa9\xd4N*\x95\x82^\xafG\xc7\x86\x0eT\xb9\xaa\x98\xfdkhh`\xe1N\xe1p\x18\x82 \xa0\xbc\xbc\x1ck\xd7\xaee\xe3B\xed\x01\x05A\xc0\xaaU\xabP^^\x8e\xa3G\x8f\x22\x1a\x8d\x22\x14\x0a\x9d\xf4\x04\x9cn\xd1F2V\x1a\x8d\x06n\xb7{\xc6x\xab\x99\xfa\x1b\x92\x8cK\xb7\x8c`0\x88\xc9\xc9I\xe6\xdaknnF{{;/0{\x0a\x86@\x92$x\xbd^\x8c\x8d\x8d\xe1\xd8\xb1c\x18\x18\x18`\x01\xb4>\x9f\x0fSSS\x88\xc5bl\xf1Q\xf0\xb2\x92@\xd0|vtt\xe0\xe6\x9bo\xc6\xea\xd5\xab\xd1\xd2\xd2\x02\x97\xcb\x05\xab\xd5zJ\xae_\xe5:\xa0x\xbfx<\x0e\x9f\xcf\x87\x81\x81\x01\x16B@EG)\xf1\x85bH)\x03\x8cjJR'\x0a\xb5Z\x8d\x9e\x9e\x1el\xdc\xb8\x91\xb5\x86\xd3h40\x99Lls\xcc\xd9 -\xa0\xb27\xd3<\xc9\x7f&E\x9dZ\xbey\xbd^\xb8\x5c.vp\x91\xbb\x96b\xffN\x9c8\x81P(\x84P(TP\xaeG\xfe\xbe\xb5\xb5\xb5\xe8\xe8\xe8@ss3\xea\xea\xeaX\xd2\x8e\xbc\xf3\x87\xf2\x10\x97\x7f&*(-/\xf2]\xcc}=\x97\x84\x0e\x0a\xdc\xa6\xc3\x9a\x5c9\xcbI\x89_*\x17\xc0\xd3\xfd\x0e\xa4\x8a\xe9\xf5z\xf6oR\xa3g*\x1e=\xd3\xef?\xe5q2\xa4N\xc6\x01jN\xfe.\xaa\x22!wK\x9f\xcew'\x8c\x8d\x8d\xb1KeEE\xc5\xac1\xe6r\xaf\x83\xfc;\x16ss+\x09\xe0|\xc7C\x1e\xca!I\x12\xaa\xab\xabYu\x8cb\xef\xa3\xd3\xe9`\xb7\xdba0\x18\xa6%\x90\x14K\xc8\xb0Z\xad\xd8\xb0a\x03+\x1bF\xcf\xd7\xe9tLY\x93\x87m\xd1z\x90\x8f\x11\x85\xd8P\x22\x0d\xbd\xbe\xa6\xa6\xa6\xe0s\x9e\xca\xfcP\xfc\x7f*\x95BYY\x19\xea\xea\xeaf\xf4\x80\x99\xcdf\x16\xa7Hjnuuu\xc1\xf3\xeb\xeb\xeba\xb5Z\x99+\xfe\xe8\xd1\xa3H\xa7\xd3hmmEkk+K\xaa#\x95\xb0\xa9\xa9\x09\xabV\xadb\x99\xcd---\xd3~\xb7V\xab\xc5\xea\xd5\xabQSS\x83L&Sp\xae556\xc1l2#\x10\x08\xc0d2\xa1\xaa\xaa\xaa \xf9\x0e\x00\x0c\x06\x03K\x96\x02N\x16hw8\x1c\x88\xc7\xe3\xb0X,'\xd5n\xd9w\xd0\xeb\xf5X\xb5j\x15\xacV+&&&\xa0\xd5j\xd1\xd0\xd0\xc0~gee%6n\xdc\x08\xaf\xd7\x0b\x8b\xc5\x82\xda\xdaZh\xb5Zd2\x19\x9c8q\x82er\x8b\x0b\x91\xea\xae\xcc\xe6\x9bO:7\x19t*2,\x08\x02\xdcn7#$\xc7\x8e\x1d\xc3\xf8\xf88+5B\x81\xeb\x92$\xcd\xe8v\x5c\x8c\x83\xf3l\xba\x9e\x95\xf5\x9f\x8cF#\x8b\x9d\x8b\xc7\xe30\x99L\xcc\xfd\x97H$X\xaf\xd8\xbe\xbe>\x04\x02\x01&gS-\xb9P(\xc4\xdc\xbc\x941ZL\xf5\x93+m\xe7\x9f\x7f>\xee\xbc\xf3N444\xa0\xa1\xa1\x01\xe5\xe5\xe5\x8cP\x9d\xae\x11$y\xdah4\xb2\x1b^[[\x1b\xba\xba\xbaX&\x9a\x5c\xa2\x97\xc7\xa8\xd1\xcd\x8d\x94\x86L&\xc3n\xcd\xc7\x8e\x1dCSS\x13jkkY]@r\x19\xcf\xc3\xaf\xb7h\x0a`1C@.\xf1D\x22\x81@ \x00\xaf\xd7\x8b\xff\xd7\xde\x99G\xc7]\x97\xfb\xff=\xfb\xbeg\xb6\xccdk\xd2\xa4{KiYZ\x0ae\x97\x02r\xa5\x02z\x11\x11\x5c\xa8\x82\x0a\xcaQ\xef\xfdq\xc5\x8b\xc7\x85\xe3\x82\x22\x02\xea\x11\xf1\x82\xe0\x02\x17/(\xdb\xbd*{i\x11*]\xd26M\x97\xecK\x93\xc9Lf\xdf\x7f\x7f\xc0\xf3\xf1;\x93I\x9a\xb4\x93t\x92<\xafs\xe64\xcd2\x99\xcc\xf7\xf3\xfd|\xde\x9f\xe7\xf3<\xefg\xfd\xfa\xf5cDR4\x1aE[[\x1b\xfa\xfa\xfaD\xee\x1f\x89?\xba\x86R\xe1N\xe6\xd1&\x93I\xf4\xe2\x96\x1e5O\xf4\x9a\xc6\xfb\xbe\xf9\x18e\x9bO\xa2\x94\x9e\xc7\xe9t\xa2\xbb\xbb\x1b\xc0{\x89\xeb3~=\xb3y@\x9e\x07d\x80R&\x87\x1c2\xe42YdSi@\xa5\xfeg\x95\xf0qN\xd1\xc9d\x12\xa3\xa3\xa3\xc2\xb8\xda\xe7\xf3\xa1\xaa\xaa\x0a}}}\xe3\xce\xfb\xc5E\x08\xf4\xb9RF\xca'*\x00\x8b\x85\x86\xd3\xe9D,\x16\xc3\x91#G\xa0V\xaba\xb5ZEZ\x07U\xe3Ses0\x18,x\x9d4g\x93\xe0\x90\xcb\xe5\xa2\x1bT\xb1\x08!\xf4z=\xccf\xb38\xa1kiiA\xfb\x81v\xc4\xe2\xff\x8c\x9a\x9a\xcdf\x18\x0c\x86\x02oB\xeabB\xa7\x0d':6\xb4Z-jjj\x84\x10+\x85\xcb\xe5\x12\x913\xaa\xbc.\xae\xb0\x95\xc9d\xa2\x98\xc9`4\x88\xe0\x82\xddn\x87\x5c.\x87\xd3\xe9\xc4\xa9\xa7\x9e\x8aX,\x06\x95J%r\xc8\x8b\xafk)aM\xaf\xad\xb8\xc8\xb5\xba\xba\xba\xa0\x96\xe1X\x91z\xb9\x5c.*\xee'\xdaL\x93\xa3Cq~\xbc\x5c.\x87\xdf\xef\x87\xd7\xeb-\xb8\xfe\xd5\xd5\xd5\xd0\xe9t\x22\xe0\xa2,\xc7\xe2}\x22\x17\x96r\x13h\x80P\xdfYz\xc8\xe5rtww\x8b\x81^UU\x85T*\x05\x85B\x01\x9b\xcdV\xf6\xc8`q\xa9\xf6T\x05\xe0t\x08E\xa9\xb0\xa6|,\xda\x0d%\x12\x09$\x12\x09\x1c=zT\x88\xc2@ \xc4!\xe5\xec\xd0\xcf\x90Ap:\x9d\x16\xc7\x85R\xe1Wl\x1a\x9c\xc9d\xb0n\xdd:\x5cv\xd9eX\xb0`\x01\x1a\x1b\x1bE\x05R\xb9\x8c\xb9\xe9\x18B\xa3\xd1\xc0b\xb1\xc0\xe7\xf3\xa1\xb6\xb6\x16---x\xe3\x8d7Dd\x8f\xc6\x09]#\xda\x11K\x13\x99)\x12\x98\xc9dp\xe8\xd0!\x1c:t\x08~\xbf\x1fN\xa7S\xb4<\xab\xd4h2\xbd\xff\xe4\xfbw\xf4\xe8Q1\xe1\x16\x8f\xcfC\x87\x0ea\xfb\xf6\xed\x08\x04\x02\xc2\xf7\x8f\xc4\xb1t\x822\x9b\xcdX\xb3f\x0d\xdcn\xb7H\xa9(\xe7{p\xa2\xe3\x9d\xc5_\xe5\xb3l\xd92\x1c>|X\x1c+\xcd\xe8\x86X\xad\x00\xbcFa\x04-3\x00\xe9L\x10\xb9l\x0ei\x87\x06:\x8b\xfe}u\xa0\x9c\xb2\xa5\x10u$\xda\xb3gO\xc1\xe7\xcf<\xf3L477#\x18\x0c\x8e\xdb\xdb\xb6T\xbb\xcb\xe2B\x17:^\xa4 \x07\x15~\xe4r9q\xb4>U\x9f<\x8dF\x03\x83\xc1\x80\x85\x0b\x17\xc2h4\xc2d2\xc1b\xb1`\xfb\xf6\xed\xe28Z\xab\xd5\x8a#j\xe9\xdaa\xb3\xd9\xb0j\xd5\xaa)\x1b\xe4\xd3\x111u&\xa9\xaf\xaf\x7f/H\x13\x8f\x89\xb5\xd2\xe9t\xc2\xe5r\x89\x9c\xb5|>/,\xde\xa4]\x9cJ5\x05\x98\xec\xdf\xeev\xbbE\xd7\x94\xf1\xae\xb3\xc3\xe1@KK\x0b\x0e\x1e<(\xe6\xce\x89\xda\x0f\xca \x83\xddn\x1f\xf3\x9a\xacVkIk\x96\x89^\xf3Tr\x5cO\xf4\xeb\xc5\xe3n\xa2kW\xfc\x7f*F\x02\xcal\x04]\x5c\xb41\x99\xc4pir1\x0d^24\x8eD\x220\x99L\xa2\xa4\x99\xbe\xa6R\xa9`4\x1a\x0b\x0c \xcb-\xb4*\x19\xba\x91\xc8u]zdC\xc7\xb8\x94\xd7Bm\xc4\xa4Q>\x12H\xd2hQ\xf1\xce\x86\x8eW\xaf\xb8\xe2\x0a\xac[\xb7\x0e\x8d\x8d\x8d\xa2}\x98\xd4\xf3\xaf\x5c\x0b8M\xc6\x06\x83A\x94\xc0755a\xef\xde\xbd\xc2\xe3\x89Z\xdb\x19\x8dF!\x0a\xe9\xef&s[\xaap\xa4\xe3\xef\xc3\x87\x0f\xa3\xae\xae\x0e~\xbf_\xb4\x16\x9cr\x14p\x86\xa0\xe2\x8dD\x22\x81P(\x84\x81\x81\x01\xacZ\xb5j\xcc\x0d\x9eN\xa7\xf1\xf2\xcb/chh\x08###\x22\xf2K\xc7\xc7\xd2\xe3\xa2u\xeb\xd6\x89]\xa2\xd9l.HR?Q\x91Wj\x93\xc4\x82n\xeeE\x12\xd7\xae]+:e\xbc\xf5\xd6[\x93\x9e\xdb\xcbB\x8d\x19\xb8e-\xbd \xa4GF\xd0\xb7#\x8bx\x22\x01\xcb\xcaf\x98\xab\xdf\x8f\x92\xc8e\x80\xe4\xf8q\xb2\x0b'\xd9`\xd1}e\xb7\xdb\xe1\xf5z\xa1V\xab'\xb4^1\x18\x0c0\x1a\x8d\x18\x1c\x1c\x14\xe3\x9e\x12\xf8\x09\xca\xd1\xa2H\xd4\xc0\xc0\x80\xf8ZUU\xd5q\xcdA4\xe7\x1a\x0c\x86\x82\x96k\x14\xed\xa3\xaf\xc9d2a\x15&\x15\x80\x14\xf9\x9a*\x1e\x8f\xa7\xc0\xc6\xc4f\xb3\x89\x82\x22\x99L\x06\xbd^\x0f\xab\xd5\x0a\xbb\xdd\x8e\xfe\xfe~\xe8t:8\x9dND\x22\x91\x82\xf9\xedx\x03+\x1e\x8f\x07\x0e\x87\xe3\x98\xf3\x8bB\xa1\xc0\x82\x05\x0b\x84\xc5\x11\xcfG\xc7\xb8\x07\xa6S\xa0LdGPj'\x90\xcb\xe5D\xb5\x93B\xa1\x10\xc9\xfc4\xf0\xa5\x09\xeat|\x5c*\x17\xe3d\x89\xb2\x99BZ>/\x15qd\x15@\xd1=iO`\x8a\x1eJm]J\x09?\xfa\xbc\xcdf\xc3\x87>\xf4!,^\xbc\x18\x0b\x16,(0|\xa6\x1dd\xb9o.2R\xb5\xd9l\xf0z\xbd\xa8\xa9\xa9\xc1\xe2\xc5\x8b\xf1\xf2\xcb/\x8b\x1cQJ\x98\xd5\xeb\xf5\x22e@\xab\xd5\x22\x99LB\xa5R\x89\xf4\x00\xaa:;p\xe0\x00\x9a\x9b\x9b\xd1\xdb\xdb+\xfa\x15Wb{8\xba\x06\xc9d\x12\x91H\x04CCCH$\x12X\xb3fM\x81\xb0\x92\xc9d\xd8\xbf\x7f?\xda\xdb\xdb\x11\x08\x04D\x82xq\xbf_\x00\xb0Z\xadX\xb5j\x15\x9cNgEF\xff\x98\xd9!\x00\xd7\xacY#\x16W\xdaLN5o\xf3\xb87\x07y\x14\x1c\xed\xcae\xef\x07\x19\xb2\xef\x15\x1fJ\xbf6U\xc3a\x12\x80/\xbf\xfc\xb28\xed8\xed\xb4\xd3\x0a\x1c(J\xfd\x1dF\xa3\x11\x8f\xff\xf6ql\xdf\xb6\x1d+V\xac@ss3\x1a\x1a\x1aD\xb4\xab\xb8\x15&\xbdw\x03\x03\x03\xe25R$k\xaa\x11@i\xe4Q\xfa\x9e\xfa|>\x04\x02\x01q2F\x22\xd3h4\x0a[\x1b\x83\xc1P\xb6\xb1Q\xaa;\x13\x00\xb4\xb4\xb4 \x99L\x8a<\xbbR\x1e\x80S\x9d7hS?U\xfd\xc1\xe2\xef$\x09\xc0\xa9F\xd3\xc8\xe8\x90\x84\x0a\x09\x1c\x8aZ\x91\xe8!\xf1\x22\xcdq:\x9e\xc5l\xb6-\x5c\xd2\x1b^\xba\xd0\x17w\x06\xa1\xef\xa1(\x90T\xec\x15G\xfa\x8a\xf3Q\xa8\xfd\x8fV\xab\xc5\xda\xb5kq\xce9\xe7\xa0\xa1\xa1\x01\xb5\xb5\xb5\xf0\xf9|\xb0\xd9l\xa2sD\xa9\xe3\x8frE\x01U*\x15t:\x9d\xc8\x05\x5c\xb0`\x01\xf6\xef\xdf\x8f\xa1\xa1!\xf1\x1a\xa9j\x9c*\xces\xb9\x9c8\xfaU\xab\xd5\x05\xbd(\xfb\xfa\xfap\xf0\xe0A\xd4\xd4\xd4\xc0\xe7\xf3\x15\xb4\xac\xab$\x11H\x1b\x1a\xca\xe1\xff\x22\xf6\xb4\xee\xc1\xeb\xaf\xbf\x8e\x05\x0b\x16\xe0G?\xfa\xd1\x98(`\xf1<\x1b\x08\x04\xc4\xef^\xb0`AAq\xcdT#\x80\xc5\xe2\x91\x0a\xf2\xe8$\x05x/wo\xcd\x9a5\xd8\xb9s'\xacV+\x1a\x1b\x1b\xcb\xb2\x0e\x01\xff\xec\xe0\x05@x3\x02\xef\xe5\xfd\x9ds\xce9\x00PP\x10R<&\xa6{\xdc\xb2\xf8\xab \x01x\xac\x9b\x92\xbaM\xd0\xc7\x94\x04O\xc7\x95\xc59j\xf3I\xdd\x97\xaa\xb4,\xf5\xb5R\x1f\x97\x12\xe1\xa5\xaa\xd6H|\xacX\xb1\x02\x1b6l@SS\x13jkk\xe1\xf5zE\xc9\xbaN\xa7\x13\xc7\x87\xd3\xf9\xdeS. u\x07\xa9\xa9\xa9Acc\xa3HrN&\x93B\x80\xd2$-\xf5\xb2\xa3\x9e\xa5\x1a\x8dF\x08\xc3\xdd\xbbw\xa3\xb9\xb9\x19\xfd\xfd\xfd\x22\x8aYi\xb9\x80T\xedL&\xcdj\xb5\x1a+W\xae\x1c\xb3h\xbe\xf3\xce;x\xfb\xed\xb7E\xcf_\xa9\xf1\xb3\xf4\xda\xba\x5c.\xb4\xb4\xb4\x08\xf1\xae\xd7\xebO\xb8Z{*\x0b\x043w\xe6\x1f\xb3\xd9\x8c\xfa\xfaz\x1c9r\x042\x99\x0c\x7f\xf8\xc3\x1f\xb0y\xf3\xe6I\xfd,\xdd\x9fO<\xf1\x04>\xfc\xe1\x0f\x9f\xf0\xdc=\x9e\x00:\x9e\x0d\x88F\xa3\xc1\x8b/\xbe(\xd6\x1e\xb7\xdb-\x92\xe6\xc7\x13\x11r\xb9\x1c\xfd\xfd\xfd\xe8\xe9\xed\x11Q\xc3\xb3\xce:K8ZH+\xe4\xa5~\xb8\xc3\xc3\xc3\x05\x82h\xd1\xa2E\xa2\xe5\x1a\xcd{\xe4\x91;\xd9\x08`1\xc5\x95\xa5\xc0{\xa7\x00\xeb\xd6\xad+{\xb1\xa4\xd4\xdaG\xa5R\x8d\xe9\xb7~\xac*j\xde,\xceq\x01(\xbd\xd0\xe3%G\x92\xc5\x04E\xfeb\xb1\x98\xc8c\xa3\xe8\x9f4\xf27\xdf\x0c\xa1K\xd9s\x90`\x9b(\x97b\xa2\xf7\xa9\xf8\x98D\xadV\xa3\xa1\xa1\x01\xe7\x9cs\x0e\x1a\x1b\x1b\xe1t:\xe1\xf3\xf9\xe0r\xb9`\xb7\xdb\xa1\xd7\xebE\x83\xed\x99h\xdbE\xbbh\x83\xc1\x00\xb7\xdb]\x10\x05\xa4\x8a6\x8a\x02R\xc4O.\x97\x17t\xa6\xa0\xb6p\xf4\xbd###\xd8\xbf\x7f\xbf\xa8\x086\x9b\xcd\x22\x87\xb1\x12D \x8dq\x8a\xfeuwwc\xed\xda\xb5b\xf1\xa0\xeb\x18\x8b\xc5\xb0s\xe7ND\x22\x911\x95\xbf\xc5cb\xf1\xe2\xc5\xa8\xad\xadEUU\x15,\x16\xcb\xb4\x19a3s\x1b\xda|\x5cv\xd9e\xb8\xef\xbe\xfb\xa0T*\xf1\xc6\x1bo\x1cS\xf0\xd3\xa6\xf2\xe7?\xff9\xbe\xf4\xa5/!\x9f\xcfc\xfd\xfa\xf5\xa2\xe2\xf4D^\xcfD\xfd\xc6\xa7\xb2.\xf4\xf4\xf4`\xfb\xf6\xed\xe2\xff\xeb\xd7\xaf/\xa8\xf8,57(\x95J\xec\xda\xb5K\xa4]\x00\xc0\xd2\xa5K\x0b\xc4Y\xa9\xea\xe0\x9e\x9e\x9e\xf7\x0c\xb4\xdf\x7f}\xabV\xad\x12\x96id\xca\xadR\xa9\x84?\xdbd\x04\xf0T\xafa9Q\xa9T0\x99L\x18\x1e\x1e\x86V\xab-Y=<\xde\x5cW\xaa\xb9\x03o\x1e\xe7\xa8\x00\x9c\xe8\xe6\xa4.!T\xb0@\xc9\xec\xa9TJ,\xf2\xc5\x1d\x0d\xe6\xd3\xce\xa1\xd8\x5c\xb3\xd4\xc7\xe3\x09;i\x84Oz\xf3\xd1\xee\xcd\xe5r\xa1\xb1\xb1\x11+V\xac@CC\x03\xecv;\x9cN'\xdcn\xb78\xee\xd5\xe9t\xc2\x98r&#<\xd4#\xd8b\xb1\xa0\xba\xba\x1a\x0d\x0d\x0dhhh\xc0\xbb\xef\xbe+\xf2\xfb\xd2\xe94t:\x9d8\xf2\x966\xe8V\xa9T\xe2\x98\x98\xc4\xe1\xfe\xfd\xfb\xb1h\xd1\x22\xf8\xfd~8\x1c\x0e\x98\xcd\xe6\x8a\xc9\x05\xa4<\xcdp8\xfc^{\x1e\xa5\x12---cr}\xde}\xf7]\x1c:t\x08}}}\xa2\xf2\x97\x22\xe4\xd2\xcd\x82\xd1h\xc4\x9a5k\xe0\xf5zQUUU\xf6\xdc?\xde\xcd\xcf\x1fh\xc3y\xc9%\x97\xe0\xbe\xfb\xeeC6\x9b\xc5\xf0\xf00^y\xe5\x15\x9c}\xf6\xd9\x13.\xf4\x1d\x1d\x1d\xb8\xe5\x96[\xc4\xbdy\xd7]w\xe1\x81\x07\x1e(\xcb\x9c(\x9d\xcf\x8a=\xe6\x8e5O\xd1\x91\xf43\xcf<#\x04\x97Z\xad\xc6y\xe7\x9dW \xe4J=\x8fL&\xc3[o\xbd%:c\xd8\xedv\xd1\xe3\x976\x94\xc5\xf7\x99\x5c.Goo\xaf\x10\x80J\xa5\x12\xd5\xd5\xd50\x9b\xcd\xe8\xe8\xe8\x80B\xa1\x10\xc5\x19\xc1`\xf0\x98bn\xaa\x86\xdf\xd3\x91\xab\xadT*\xd1\xdc\xdc\x8c\xe1\xe1aaf<\x99y\x82\xe7\x8ay*\x00\x8b/<\xe5\xb2\xa5\xd3i\x11\x05\x0c\x87\xc3\xa2B\xb5T\xb1\xc2|c\xb2;\x5c\xe9Q\x83t\xd2\x96\xe6\x0b\xaa\xd5j\xd4\xd7\xd7\xa3\xa9\xa9I\xf4\x0f\xa4\xbe\xcbv\xbb\x1dV\xabU\x1c\x15R\x0b\x9b\x89&\xc2\xe9^t\xc8\xcb\xca\xe3\xf1\xc0\xef\xf7c\xc9\x92%8p\xe0\x80\xb0\x06J\xa7\xd3\x88\xc5b\xd0j\xb5\x05\x93\x0fM\xe6\xd9lV\xfcK\x15\xc1\xed\xed\xed\x22\xa7\x91\xda\xc3\x9d\xec\x5c\xc0\xe2\xe8\xdf\xe0\xe0 \x9a\x9a\x9a\xc6x]\xa5R)\xbc\xf3\xce;\xa2Y{<\x1eG\x22\x91(\xc8\xfb\xa3\xeb\xb4|\xf9r\xd4\xd5\xd5\xc1\xe9t\xc2b\xb1@\xab\xd5\x96=\xfa'mIW\xca\xfb\x8c\x99;\x9b\xd0|>\x8f\xe5\xcb\x97\xc3\xeb\xf5\xa2\xaf\xaf\x0f\xb1X\x0cO?\xfd\xf4\x84\x02P.\x97\xa3\xa9\xa9\x09_\xf8\xc2\x17p\xef\xbd\xf7\x02\x00\x1e|\xf0A|\xeaS\x9f\xc2\xca\x95+\xcb2\x1eK\x1d3N6\xbf\xec\xd0\xa1C\xf8\xe5/\x7f)\x22j\xa7\x9f~:\x96/_\x8e\xc1\xc1A1\xef\x15\xcf\x0b\xf9|\x1e\x81@\x00;w\xee\x14\x9f[\xbat)<\x1e\x8f(H,NI!\xa1\xd6\xd3\xd3#^oMM\x0d\xdcn\xb7\xe88\x22\x97\xcb\xe1v\xbb\x91\xcdf'\xf4Y,\xd5\xa5\xe7d\xe2\xf1x\xe0\xf1x\xf8&a\x018y!C\xc2$\x91H\x88\x9eyT\xf9H\xbet\xd2\x82\x06fr\x93\xb4T\xa8)\x14\x0a\xd1\x9e\xa6\xaa\xaa\x0a---\xc2\x10\x93|\xa3l6\x9b\xf8\xbf\xc9d\x12\xa2\x8f\xec\x0fNv\x22-\x19;\x9b\xcdfTWW\xc3\xe7\xf3a\xe1\xc2\x85x\xe7\x9dw\x0a\x8e\x81\xa5cJj\x05Cm\x8f\xe8H&\x16\x8b\xa1\xbd\xbd\x1dMMM\xf0\xfb\xfd\x222v\xb2\xa3\x80\xb4\x09\xa2c\xddH$\x823\xce8\xa3\xe0~\x91\xc9dhmmE__\x1fz{{\x11\x0e\x87E\xf4\x5c\xbaQ\xa2\x8a\xf8\x0d\x1b6\xc0\xe1p\x88#\xfcJ\xf6>df\xc7\xfcRSS\x83\xb3\xcf>\x1b\xbf\xfb\xdd\xef\x00\x00/\xbc\xf0\x02\xee\xb8\xe3\x0e\x98\xcd\xe6\x09{\x80\xdfv\xdbmx\xec\xb1\xc7022\x82l6\x8b\x0f\x7f\xf8\xc38p\xe0@A\xa1\xd6\x89\xdc;\xe3E\xc2&\xaa\x08V(\x14\xb8\xe3\x8e;DG\xa1l6\x8b/\x7f\xf9\xcb\x18\x1d\x1d-\x88\xb2\x95\xca1\x1c\x18\x18\xc0\xe1\xc3\x87\xc5\xd7\xd7\xae]+r\x01\xa5U\xc0\xd2S\x9aT*\x85\xce\xceN\xf1<>\x9f\x0fn\xb7[XY\xa9\xd5j\x98\xcdf\xe1i:\x91\x88\xe5\xfb\x98\xa9x\x01(5\xb4\xa5\x85J\x0ay\xfeQ'\x0aJd\xa7\x9c\xbfb\x8cF#\xf4z\xbdh\xc6,\xbd\xc1\xe8f\x9f1_\xaacL\x94\xd3\xf5\x9c\xc5\xff\xd2b\xafV\xaba\xb1XD\x95'\xb5\xca!\xaf>\x12vz\xbd\x1eF\xa3\x11\x16\x8bE\x1c\xef\xd2\xd7(\xff\xa4\x12\x84\x9ft\xa2#\xbfG\xb7\xdb-\x8c\xa1)\x0aH\xa6\xd7\xe4AE\x16\x15\xf4\xbeP$\x90\x0a$\xb4Z-:;;\xd1\xd5\xd5\x85\xbe\xbe>\xd4\xd6\xd6\xc2j\xb5\x8a\xca\xd8\x93\xf17\xd315\xf9\xfeuuua\xf1\xe2\xc5\xa2I;]\x8bl6\x8b\xed\xdb\xb7\xa3\xb3\xb3S$\x93K;\xb7H\xef\x81s\xcf=\x17UUU\xa2\xf2w:\xa2\x7f\xcc\xfc\xe4\x8a+\xae\xc0\x13O<\x01\x00\xd8\xbd{7\xb6n\xdd\x8a\x8b.\xbah\x5c\x11\x96\xcf\xe7Q__\x8fo}\xeb[\xb8\xe9\xa6\x9b\xa0\xd5jq\xe4\xc8\x11|\xf0\x83\x1f\xc4\xb3\xcf>{B\x9bC\x12n\xa5N\x96(\xa7\x8e\x9c$\xa4si2\x99\xc47\xbf\xf9MaO\x92\xcb\xe5\xf0\x95\xaf|\x05Z\xad\x16\x89DB<\x7f\xb1\xd8\xcaf\xb3\xd0\xe9tB\x00+\x14\x0a\xe8t:\x9cw\xdeyH&\x93\xef}\xbfB>&\x07\x90\xd6\xc1\xbd{\xf7\x8a\xcf577\x0b\xf3\xfaK/\xbd\x14\x89d\x02&\xa3\x09\xd1h\x14\x0a\x85\x02\xd9lv\xdc\xf9h.\x09\xc0\xf9\x96\xd3?g\x05`\xb1MI>\x9f\x177\x98\xd4z\x82nX\xda\x9d\xd1\xf1/U\xfe\x16[\x95\xb8\x5c.\xac\x5c\xb9\x12\xd5\xd5\xd5\x22\x99]\x9a\xa0+\xad\x08\x9e\xe8\xa6\x99\xe9\x08\xdct\xdc$RGu\x12\xc1\xd4\x06\x8d\x1eT\x8dE\xff\xeat:!\x02\xe9c\xfa:y\xe6I\x05S\xa5\xa1P(\xa0\xd7\xeba\xb3\xd9\xe0\xf3\xf9\xe0\xf3\xf9\xd0\xd2\xd2\x82m\xdb\xb6\x89\xd7Lm|T*\x95h)(\xad\x10V\xa9T\x05\x1b\x8b={\xf6\x08c\xe8\xaa\xaa*\xe8t:\xd1\xc7r\xa6\xa1\x08\x1e\xf9\xfe\x85B!\x9cw\xdeyc*\x7f[[[\xd1\xd9\xd9\x89\xbe\xbe>D\xa3Q\x912Q\x5c\x1ce4\x1a\xb1b\xc5\x0ax\xbd^\xd1\xaaO\xa3\xd1L\xeb}q\xacj?fv,\xc4\x93\x19#\x1f\xfd\xe8G\xf1\xa5/}I\xb4g\xdc\xb2e\x0b:::D\xd5}\xa9y1\x93\xc9\xe03\x9f\xf9\x0c\xb6n\xdd\x8a\x87\x1f~\x18j\xb5\x1a\xcf=\xf7\x1cn\xb8\xe1\x06\xfc\xeaW\xbf\x9a\xb25\x8cT`\x8d'\x1ah\xdd\xc9\xe7\xf3H&\x93\x05\x7f[$\x12AGG\x87\xc8\x0d>\xf7\xdcs\xb1a\xc3\x06qZ \x15\x80\xd2M\xb7L&CGG\x07\xb6o\xdf.\xe6\x94k\xae\xb9\x06N\xa7S\xf4\x11V\xc8\x15\xc2\xa7P\x1a\x08\x19\x1a\x1aBww\xb7\xd8\x88m\xda\xb4I\xbc\x1e\x9a\x8b)7\xf0X\xf3\xf7\xa2\xfd=\x00\x00 \x00IDAT\xf0\x5c\x12J\x5c\xf81\xc7\x22\x80\xd2\x1085D\xa6\xddQ\xf1\xe0%\xdb\x8b\xe2\xfe\xa5\xf4=K\x96,\xc1Yg\x9d\x85\xfa\xfazQ\x98@\xc7\x93\xf31\x0c^\x9coE\xb9&\xb4\xd3%\x11#\x8d\xe6\xd1C*\x14\xe9{\xe89*\xf9&$qj4\x1a\xe1r\xb9\xd0\xd0\xd0\x80\xce\xceN\x1c|\x18g\x9ey\xe6\x98\xdc\xa3d2\x89\xdd\xbbw\xe3\xc0\x81\x03\x88F\xa3B\x00\x16wq\x01\x80SN9\x05>\x9f\x0f\x1e\x8f\x07V\xabU\xb4\xeac\x98\x13]\x88I$~\xe3\x1b\xdf\xc0\x96-[\xa0\xd1h\xd0\xd9\xd9\x89\xff\xfe\xef\xff\xc6\x95W^9\xae\x88\xa4{\xf3\xfe\xfb\xef\xc7\xae]\xbb\xf0\xf6\xdbo\x03\x00\x1e~\xf8a\xc8\xe5r\xfc\xe2\x17\xbf\x98\xb2\x08\x9c(\x02H\x7f\x8f^\xafG<\x1e\x1f\x93\x9f\xeap8\xf0\xf3\x9f\xff\x1c\xb7\xddv\x1b\xb4Z->\xfb\xd9\xcf\x8a\xcaT\xe9\xf3K\xdf\x97\x5c.\x07\x9b\xcd\x86;\xef\xbc\xb3`\xa3\xff\xe3\x1f\xff\x18\x7f\xfb\xdb\xdf\x0aOgd\x85\x01\x11\xb5Z\x8d\xd7^{M<\xaf\x5c.\xc7\xe5\x97_>\xee|\xa7\xd1hD$r\xbc\xf5u\xael:\xb8(d\x8e\x08\xc0R\x17\xd1n\xb7\x0b\x13g\xe9\xf7Q\x7fZi\xd4\xaf8\xfaWWW\x87\xf3\xcf?\x1f\x8b\x16-B}}=<\x1e\x0f\xf4z\xbd\x88fHo\x84\xc9V~\xcd\x85\x1bF*\x02\xa5\x0f\x12\x83R\xab\x96b\xaf<\xcaw\x99m\x93\x085.\xb7Z\xad\xf0x<\xa8\xaf\xaf\x87\xdf\xefG0\x18\x14\xd5\xb3\x14\xfd\x94&]K{\x03\x03\x10f\xc9\x0a\x85\x02\xbbv\xed\x12\xc50\xe4\x96?\xd3Q@\xca\xfd\x0b\x87\xc3\x18\x1a\x1aB.\x97\xc3\xb2e\xcb\x0a\x22\xe42\x99\x0c\x87\x0f\x1f\xc6\x8e\x1d;\x10\x0c\x06122\x22<3\xa9\xdf&a4\x1a\xb1t\xe9R\x11\xfd#\xdf?6Ee&\x8a\xf8\xb5\xb7\xb7\xe3\xd0\xa1C8\xf7\xdcs'\xec\xeeC\xc7\xa97\xddt\x13\xbe\xfa\xd5\xafbtt\x14j\xb5\x1a[\xb6l\xc1\x95W^9\xe1\x18\xa3\xcd\xe7\xd3O?\x8d\x0d\x1b6\xe0\xd0\xa1CP\xab\xd5x\xe8\xa1\x87p\xf0\xe0A<\xfe\xf8\xe3\xf0z\xbd\x93\x9a\xcb\xa5\x1b\xa4\x89\xc4\x03m\xfe\x8a\x85i.\x97\x83\xc1`\xc0\xb6m\xdb\xd0\xd6\xd6\x86D\x22!\xee%:U\xa2y\xc0j\xb3bxx\x18.\x97\x0b\xaf\xbe\xfa*v\xef\xde-:\x12\xdd~\xfb\xed\xc2\xfa\xa4\xb8\x08O\xda\xf7V\xa3\xd1\xe0/\x7f\xf9\x8b0\xd5^\xb7n\x9d(N+e\x17c2\x99D.\xa2\xc1`\x80\x5c.\x17=\x84I 2LEF\x00\xa5\x13\x0cE(\x8aof:\x9e\x8cD\x22\x05v/4\xc0\x95J%\xce>\xfbl466\x0a\xdb\x0e\xab\xd5Z\x90\xcb4\xdfv\x0d\xc5\x13\xccx\xbbA\xe9\xce\xb5\x94Y\xeal\x14\x024&\xccf\xb3\xe8\x0e\xb2h\xd1\x22\x1c\x9f\x18\x1fmmmH\xa7\xd3\xa2\x18\x86\xf2\xff\xc8\x08\x9a\xc6\x1e\x1d}\xd3\x04\x1a\x8f\xc7\xd1\xde\xde\x8e\xc6\xc6Faz\xad\xd7\xeb\xcbR\x998\x19\xa4\xbe\x7f###\x88\xc5b8\xe5\x94S\xc6\x5c\xdf\x9e\x9e\x1e\xbc\xf9\xe6\x9b\x08\x85B\x08\x85B\xc2$]\x9a\xfbJ\x1ekg\x9f}6\x1c\x0e\x87\xb8_\xa6\xbb\xeb\x874RY\x09\x05X\xcc\xd4\xb0X,x\xec\xb1\xc7\x84\xe0{\xe0\x81\x07p\xf7\xddwOx\x1cK\xd7\xfb\xd2K/\xc5\x05\x17\x5c\x80\xff\xfb\xbf\xff\x03\x00\xdc{\xef\xbd\xb8\xfe\xfa\xeb\xb1l\xd9\xb2\x09E[>\x9f\x87\xcf\xe7\xc3\xce\x9d;q\xd9e\x97\xe1\xe5\x97_\x86\x5c.\xc7\xd0\xd0\x10>\xfd\xe9O\xe3\x87?\xfc!\x1e~\xf8a\xac]\xbb\xb6 \x80P<\x8e\xa5\x11\xb6\xf1\x8e\x80K\xad\x17t2@\x1e\xa0\xa5\x02\x14\x14\x01\xa4\x8a\x5c\xbb\xdd\x8e\xfb\xef\xbf\x1f;v\xec\x10\xdf\xf3\xaf\xff\xfa\xafp\xbb\xddc\xde'\xe9\x06<\x9f\xcf\xc3b\xb1\xe0\xbf\xfe\xeb\xbf\x0a:|\x5c{\xed\xb5\x22-\xa5\xd4f\xd7b\xb1\x88cs\xa7\xd3YpoI\xd3w\xe6\x0a\xbcy<\xc9\x01\x96\x99\xbe\xd8T\xf9H\x85\x1b\xc5a\xffE\x8b\x16\xc1l6\xc3j\xb5\x8aH\x86\xb4R\x93\x1f\xf3\xefHO\x1a\x05\xa4N%\xcb\x96-\x13\x13<\xd9\x0aIm\x84h\xb2\x97\x16\xc5\x90(R\xab\xd5\xa2\x22\xb8\xb7\xb7\x17\xc3\xc3\xc3\xa2\x0b\xcdtOH\x94\xea\x90H$D\xdb\xb7\xea\xeajX\xad\xd61\x11\xb5m\xdb\xb6\xa1\xb7\xb7W\x1c\xfd\xc6b1Q8\x22\x15\x93k\xd6\xac\x11\xb9\x7f\x16\x8b\x05:\x9dnZ+\x9b+\xa5\xea\x9e9\xfe\x0d\x88\xdf\xef\xc7\xc6\x8d\x1b\x91N\xa7\xa1R\xa9\xf0\xbd\xef}\xef\x98Q5\xba\x17\xd5j5\xfe\xe3?\xfeC\x88,*\x88\xa0\xfb\xefX\x9bV\xadV\x8b\x97^z\x09\x9f\xfb\xdc\xe7\x0a*n\x0f\x1c8\x80\xd3N;\x0d\xcb\x97/\xc7\x1f\xff\xf8G\xf4\xf4\xf4\x8c\xc9\x87S*\x95\xa2\x8bOq\x0el\xf1\xeb,\xfe\xdd\xe4*`\xb3\xd9D\xbe\x1ed\xe3\xdf\xa72\x99\x0c;w\xee\xc4-\xb7\xdc\x22\xee\xa7\xea\xeaj\x5c~\xf9\xe5\x13\xfe\x9d\xb4\xd9\xe3LD\xffJEP\x98\xd9\xb1\xa1\xca\xe7\xf3\xf8\xc1\x0f~P\x10Q{\xf0\xc1\x07E\xca\xc4\xb1\xae\xff\xd9g\x9f\x8dO\x7f\xfa\xd3\xc2\x7fs\xef\xde\xbd\xf8\xc4'>!*\xf1\x8fu/g\xb3Y\xfc\xf4\xa7?\xc53\xcf<\x83u\xeb\xd6\x89\xa8\xb8N\xa7\xc3\xee\xdd\xbb\xb1y\xf3f477\xe3\xd2K/\xc5-\xb7\xdc\x82{\xef\xbd\x17\xbf\xfd\xedo\xf1\xdcs\xcf\xe1\xa5\x97^\xc2\xc8\xc8\xc81\xffF\xe9\xd8\xac\xad\xad\xc5\xc6\x8d\x1bq\xd6Yg\xc1\xe1p\xfcs3\x8d\xd2\xfd\x84\x95J%\xc2\xe1\xb0\xc8\x8d\xa4\xf7\xe8\x96[n\x81\xc9d\x9a\x94H\xfe\xdb\xdf\xfe&6\x96\x00p\xc3\x0d7\x1c\xb3\xe0E\xa1P`\xd1\xa2EX\xb6l\x19\x00\x14\xa4\xa6\xd0I\xc7l\xe5D{73\x15.\x00'\xb3\x18H\x8f\x8dJ-\xb6dUB\xbbK\x1e$\x0cM\xaa\xd4\x7f\xd2\xe5r\xa1\xb6\xb6\x16\x0b\x16,\x80\xc9d\x12\xd5\xe4\xe4+Y||D\x132E\x12\xc9^f\xef\xde\xbd\xe8\xed\xedEww\xb7\xc8\xb1\x9b\xce(`q\xee\xdf\xe1\xc3\x87q\xca)\xa7\x88\x02\x1d\xe9&\xe9\xed\xb7\xdfF[[\x1b\x82\xc1`A\xee\x9f\xf4\xbe\xc9\xe7\xf3\x22\xfa\xe7\xf5za\xb5ZO\xda\xa6\x89\xf3\x01g\xd7B\x9cN\xa7\xb1z\xf5j,\x5c\xb8\x10\x89D\x02J\xa5\x12\xdf\xf9\xcew&5\x8f\xd3\xe9\xcd\xfd\xf7\xdf\x8fe\xcb\x96!\x95JA\xa9T\xe2\xf1\xc7\x1f\xc7]w\xdd5)\x11I\x82\xea\xe2\x8b/\xc6_\xff\xfaW<\xf5\xd4Sp\xbb\xdd\x88\xc7\xe3P*\x95\xc2\xd4\xfd\xa5\x97^\xc2O\x7f\xfaS|\xf1\x8b_\xc4\xb5\xd7^\x8b\xcd\x9b7c\xcb\x96-hkk\x9b\xd0\xec\xb9\x18\xaa\x8a\xa7\x1c;\x00%\xc5\x1f\x09\xacd2\x89K.\xb9\x04###\xc2\xcc\xf9\xa6\x9bn\xc2\xca\x95+\x85\xdf\xe8D\x1c=z\x14/\xbf\xfc\xb2\xf8\xffE\x17]\x84\x85\x0b\x17Njc&\xcd\xdd\xa5>\xec\xf4\xda\xf4z\xfd\xac\x1dw<7\xccq\x01H\x15\xbd\xc7\xf2\xe5\xa3\x5c\xacR-\xa4\xa4\xed\xccJ5\xd6f\xe6\xb7\x08\x94\xe6\x02\xd6\xd5\xd5\xa1\xae\xaeN\x1c=I\x8bB\xa4\x91*z\xd01\xb0\xd4S\xf1\x9dw\xdeAOO\x0f\x06\x06\x06D\x7f\xdd\xe9\x8a\x02f\xb3Yd2\x19D\x22\x11\x11\xd9;\xed\xb4\xd3\x0a\xda\xaa\x01@WW\x17\xb6n\xdd*,b\x8a}\xff\x08\x9dNWP\xf9KQ\xf3\x99\xcc\x95e\xe17;!\x91v\xd7]w\x89\xb1\xd9\xd9\xd9\x89\xfb\xee\xbboR?O\xc6\xeb/\xbd\xf4R\x81\x0d\xd3\x9dw\xde\x89G\x1f}tR\xe2LZ\xa8u\xf9\xe5\x97\xa3\xbf\xbf\x1f\xcf?\xff\xf8\xa0\x98\x7f\xa4\xc7\xcb\xd2\x00\x85\xf4\xf7=\xf8\xe0\x83\xc2\xc7\xb4\xa1\xa1\x01\x9f\xf9\xccg\x8e\xeb:iuZ,jY\x84E\x8b\xdf\xb3D\x9b\x8dE \xc5\xc6\xd8\xcc\x1c\x15\x80\x93\xbd\xc0\xc5\xe2\xb08\x12\xc8\xdee\xccD\x22P\xab\xd5\xc2\xe1p\xa0\xa6\xa6\x06MMM\xa8\xae\xae\x16\xbez$\x04\xa9ZV\xba\x18\xa8\xd5j\xf1\xf3tL\x9aN\xa7\xd1\xda\xda\x8a\xae\xae.\x0c\x0e\x0e\x22\x1c\x0e\x1f3\x99\xfdx\x05 \xf5\xbc\x0e\x04\x02\x88F\xa3X\xbe|\xb9xm\xe4C\xb6\x7f\xff~\x1c8p\x00\x83\x83\x83\x08\x04\x02\xc2\xa7Lj\x94N\x0b\xcb\x19g\x9c\x01\xaf\xd7+r\xfff:\xfa\xc7\x85 \xb3_\x04\x9aL&l\xd9\xb2E\x88\xb0\xd6\xd6V<\xf5\xd4SSZ\xac/\xbc\xf0B<\xf2\xc8#B@\x01\xc0\xd5W_\x8d\x1f\xfd\xe8G\xc2~i2\xaf\x85\xbaw\xe4r98\x1c\x0e\x5c{\xed\xb5\xb8\xe7\x9e{\xf0\xe7?\xff\x19\x7f\xfd\xeb_\xf1\xdak\xaf\xe1\xb5\xd7^\xc33\xcf<\x83\xe6\xe6f\xa4\xd3\xe9I\x09\xc0\xe2\xc0\xc4x\xe2D&\x93\xe1\x8e;\xee\xc0}\xf7\xdd'N\xa9\xf4z=^\x7f\xfdux\xbd\xde1\xebSq\x852}|\xcf=\xf7\x08G\x81t:\x8d[o\xbd\xf5\xb87L2\xc8\xe0v\xbb\xd1\xd2\xd2\x02\x8b\xc52'\xc6\xdcd\x8d\xc7\x99Y&\x00'\xbb\xf8\x14W\xfe\x16G\x0a\xa7#\x02\xc3\xcc\x0d(\x11\x9ar\x01\xfd~?\x96/_.\xc4H,\x16\x13\xc7D\xa5\xa2\x80d\x8cM\x1fS\xc1\xc5\xe1\xc3\x87\xd1\xd5\xd5\x85\x91\x91\x91\x92\xfd\xab\xcb\x11\xfdK\xa5R\x18\x1d\x1d\xc5\xe0\xe0\xa08\xc6.\xe6\x9dw\xde\xc1\xf0\xf00\x06\x06\x06\x90L&\x0b\xbc2\xa5G_\x8d\x8d\x8dhnn\x86\xd3\xe9\x14\x95\xbf3\x19\xfdc\xe6\x0e\x9f\xfd\xecg\xe1\xf7\xfbE\xf5\xea\xf5\xd7_?\xa5{ \x97\xcb\xe1c\x1f\xfb\x18\xee\xb9\xe7\x1e\xc4\xe3qqzs\xdbm\xb7\xe1\x9b\xdf\xfc\xe6\x94\xac\x95J\xd9\xbe\x00\x80\xc9d\x82\xc7\xe3A]]\x1djkkE\x04p\xa2\xe78\x96\x07\xaa\xd4P:\x91H\xe0\xdf\xfe\xed\xdf\xb0u\xebV\xf17%\x12\x09\xec\xdf\xbf\x1fUUU\xc8d2c\x8c\xf8\xa5\xcfA\xddG\x8e\x1c9\x82{\xef\xbdW\xe40644\xe0\x92K.\x19\xf75\xcc\xb7\x0d\x07\xb5\x80e\xe6\xa0\x00\x9cj\xa8\xb7\x94\xad\x09G\xff\x98cA\x15\xc1v\xbb\x1d\xd5\xd5\xd5\x22\x12H\x96\x11\xa9T\x0a\xf1x\xbc\xa0\x070\x8d+*\x02!k!\xa5R\x89\xd1\xd1Q!\x00)\x0a(=J>Q\xa4\x95\xbf\xa1P\x08}}}\xd8\xb0a\xc3\x98{\xa1\xbd\xbd\x1d\x07\x0f\x1eDWW\x17\x82\xc1\xa0\xa8 \x94F\xff\xe8\xde\xa0j\xc6\xaa\xaa*q$\xc6>\x99\xcc\xf1\x8cM\x00\xf8\xc9O~\x22<5\xe5r9.\xb9\xe4\x12\xe1\x957\x99\x8d\x7f*\x95\xc2\xad\xb7\xde\x8a\x9f\xfe\xf4\xa7b\xbc*\x14\x0a|\xfd\xeb_\xc7\xd5W_\x8d\xde\xde\xde\xb2Dv\xa4y\xe1\x13\xdd\x9fr\xb9\x1cN\xa7SD\x09\xc7\xab@\xd5h4hmm\xc5\x96-[\xb0o\xdf>\xf1\xdcUUUhkk\x83\xd7\xeb\x1d\xb3NI\xc5\xa5\xf4y\xe5r9n\xb8\xe1\x06\x91\x8f\x0c\x00\x9f\xff\xfc\xe7\x11\x8f\xc7y\xa0\x15m\x18\x8aE!3\x07\x04`\xa9\x0b9\xd5s\x7f\xce\x13`&\xb3\xe0h4\x1aX,\x16x\xbd^\xd4\xd6\xd6b\xd1\xa2E\xc2\x0c\x9a,%\xa4\xc7\xc0R\xe3b\xaa2\xa7#aZ\x04\xba\xba\xba\xd0\xd9\xd9\x89@ \x80X,V\x96#N:\x9a\xa6\xe8_ww7\xbc^/\xbc^\xef\x98\xe7omm\x15\xbe\x84\xd1hTD\xff\xa4\x0b\x1d\xb5\x8c\xab\xaf\xaf\x87\xd7\xeb\x15\xb9\x7f\x95d\x974\xd5\xfb\x9d9\xb9Q\x99t:\x8d\x0f~\xf0\x83\xf8\xe8G?\x8aT*\x05\x85B\x81W_}\x15?\xfb\xd9\xcf&\xbd\xa9\xa0\x88\xdc\xe7>\xf79<\xf5\xd4Sb\xa3\xa6R\xa9\xf0\x87?\xfc\x01\xeb\xd7\xaf\xc7\xd0\xd0\xd0\x09\x8fQi\xca\xc4D\xbd\x80\x95J%\xea\xeb\xeb\xb1r\xe5J,]\xba\x146\x9bM|\x9d\x0c\xe35\x1a\x0d\x1ex\xe0\x01\xfc\xbf\xff\xf7\xff\x10\x0a\x85D\x8b\xb6\xfa\xfaz\xfc\xe3\x1f\xff\xc0\xc2\x85\x0b\x0b|\x0a\xa5\x05\x8a\xd2\x8fi\x1c?\xf4\xd0Cx\xe9\xa5\x97\xc4\xef\xd9\xb4i\x13\x1a\x1b\x1bgu\xe5n\xb9\x84^\xa9{\x9d\xd7\xfa9&\x00y\x82gfj\xd1\xa2\x8a\xe0\xaa\xaa*\xf8|>\xf8\xfd~444 \x95J\x15\xe4\x06\xa5R)1aK;\x04\xd0q\x17=\x12\x89\x04\xf6\xed\xdb'*\x82\xc3\xe1pY*\x82\xa9\xb0#\x16\x8baxx\x18\xbd\xbd\xbd8\xff\xfc\xf3\x0bz;\x03@OO\x0fv\xef\xde\x8d\xee\xeen\xf1\xbb\xa5\xa6\xcfR\xdf\xbfSN9\xa5 \xf7O\xab\xd5VD\xf4O\xfa^M\xf6\xbe\xe7h\xff\xc9\x87\xf2\xf4\xee\xbd\xf7^\xb8\xddnqDw\xe7\x9dw\xe2\xc8\x91#S\xda\x98\x01\xc0\xbf\xfc\xcb\xbf`\xcf\x9e=\xa2u\x1c\xf0\x9e\x0dJUUUY\xd6\x83\xc9\xf4\x02\xa6\xfb\xba\xb9\xb9\x19MMM\xa2\xea\x99ZF\xbe\xf2\xca+X\xb0`\x01\x9e|\xf2I1\x0e\xa9\x08\x85\x22\x7f\xc5\xbfS\x1a\x01\x94\x1e\xfd\xa6\xd3iD\xa3Q\xfc\xfb\xbf\xff\xbb\xf8\xba\xd3\xe9\xc4u\xd7]\x87\x5c.7'r\xf7\xca\xad\x07\x8a\xfdL\x999,\x00y\x92g\xca\x0d\xb5E2\x1a\x8dp\xbb\xdd\xa8\xaf\xafGCC\x03\xf4z=\xb2\xd9,\x92\xc9$\x92\xc9$r\xb9\x5c\x81Y4\x89@\x95JU\x109S\xa9ThkkCWW\x17\xfa\xfa\xfaDE\xf0\x89\x1c\x03\x93\xf8K&\x93\x08\x85B\xe8\xea\xeaBSS\x13\xecv\xfb\x98\x1c\xc5]\xbbv\xa1\xbd\xbd\x1d\xa3\xa3\xa3\x08\x87\xc3\x22\xff\xaf\xb8\xd2\xb1\xb1\xb1QTC\xda\xedv\xe1\x13V\xe9\x13~\xf1\xff)\xcf\xb7\x9c\xb9\x96\xcc\xf1\xa3T*QUU%z\xfa\xcad2\x0c\x0c\x0c\xe0\xfa\xeb\xaf/\xb0V\x9a\xec5onnF \x10\xc0\x86\x0d\x1b\xb0j\xd5*\xfc\xecg?C2\x99,\xcbZ0\x99\xcdN\xa9~\xc1\xf9|\x1e;w\xee\xc4\xe5\x97_\x8es\xce9\x07\xa3\xa3\xa3\x05\x91\xbc\x8f}\xeccx\xe4\x91GJ\xaeY\xb4i,6l\xa7t\x92\xdbo\xbf]\xf4\x1c\xcf\xe5r\xf8\xcew\xbe\x03\xaf\xd7\x0b\xbd^?\xa6\xc77\xc3\x81\xa2y#\x00\x19f: \xd1\xa6\xd7\xebEEpcc#\x5c.\x970L\x96V\xf3f\xb3\xd91\x91\x03\xa91\xb4L&C2\x99\xc4\x9e={\xd0\xd9\xd9\x89\xc1\xc1AD\x22\x91\x82<\xc2\xe3\x15\x80\x14\xfd\x0b\x87\xc38\xe5\x94S\xc6$\xba\x0f\x0c\x0c\xe0\xad\xb7\xde\xc2\xe0\xe0\xa0(B\xa1\xdf+\xcd\xfdS*\x95X\xb5j\x15jjj\xe0t:a0\x18\x0a|\x0dgj\x92&\x9fO\xfa\xdcd+1\xa5\xcf#=Zc*\x87\x8f}\xecc\xb8\xf4\xd2K\x85\x98y\xe5\x95Wp\xc3\x0d7L\xaa\xc3\x87\xf4\xba\xd3&\xeb\x85\x17^\xc0\xd6\xad[\x91\xcb\xe5\xa0\xd1h\xca\x22\x0a\xc6\x1b\xebTAL) 4\xbe\xe2\xf18\x1ey\xe4\x11\x5cq\xc5\x158\xf5\xd4S\xf1\xa7?\xfd\x09j\xb5Z\xe4\xd7.[\xb6\x0cw\xddu\x17\xae\xbb\xee:\xc4b1Q\x8d\x5c,\x00\x8b#\x80\xd4g\xfc\xdb\xdf\xfe6\xfa\xfa\xfa\xa0P(\x90N\xa7q\xc3\x0d7\xe0\x93\x9f\xfc$\x1a\x164\xe0\xb4\xd3N\x83V\xab\xe5\x81\xc5T\xe6\xc6\x8f\xdf\x02f\xd6\xee^\xde\xf7\xf5\xb3X,p\xbb\xdd\xa8\xa9\xa9\xc1\xa2E\x8b\xd0\xdb\xdb+\xaa\xf6\x92\xc9$\xf4z\xbd\x10-R?@Z\xa8(\x7f)\x93\xc9\xe0\xc8\x91#8|\xf80jkk\xe1r\xb9`2\x99\xc4\xf7L\x05\x8a8R\xf4\x8f*\x7fkjj\x0a\x16,\x99L\x86\xdd\xbbw\xa3\xa3\xa3\x03###\xe2\xf8\x97\x8e\xb1\xa5\xa2\xcb\xeb\xf5b\xc5\x8a\x15p\xb9\x5c\xb0Z\xad0\x18\x0c\xd3\xde\xf6\xedD\x16\xec\xf1\x16\xeab\x01\xc9T\x16\x7f\xfa\xd3\x9f\xe0\xf3\xf9088\x08\x85B\x81G\x1f}\x14\x8d\x8d\x8d\xf8\xc67\xbe1\xe5\x13\x9d\x135.\xa6\xfb\xa08\xe7N\xea\xfbIb/\x97\xcbA\xa9T\xc2`0\xc0b\xb1\xe0\x8d7\xde\xc0\xfd\xf7\xdf\x8f\xe7\x9f\x7f\x1e\x81@@\x8c5\x95J%\x8a4\xbe\xfc\xe5/\xe3\x8c3\xce\x80N\xa7\x13]H\xc6\x9bkJmZ\x1e{\xec1l\xdb\xb6M\xbc\xd63\xce8\x03\x0f<\xf0\x002\x99\x0cjkjy0\x1dC\xcc3'y\x0d\x9d\xc9\x8b\xcf\x83\x80)'\xd2(\xa0\xddn\x87\xdf\xefGcc#\xdcn\xb7\x88\x04\xe4r9Q\xd5+\xed\x17L\x22\x90\x1e\x9434::\x8a\xf6\xf6vtuuahh\xe8\xb8*\x82i\x9cSnP(\x14\xc2\xd0\xd0\x10V\xaf^=\xe6\xf5\xf7\xf7\xf7\xe3\xad\xb7\xdeB__\x1f\x82\xc1 \xb2\xd9\xac(^\xa1\xc8#5\xbe_\xb7n\x1d\x5c.\x17\x9cNg\xc5W\xfeR5i\xb1\x10\xa4\xbcF\x8e\xfcU.\xc9d\x12;v\xec\x10\xd7K&\x93\xe1[\xdf\xfa\x16\x9ex\xe2\x89\x19\xa9\xda\xa4\xe7\xdf\xb6m\x1b\xda\xda\xda\x10\x0a\x85\x10\x89D\x90H$\x84):U\xda\xc6\xe3qD\x22\x11\x8c\x8c\x8c\xe0\x9dw\xde\xc1SO=\x85\xaf}\xedk\xb0\xd9lX\xbf~=~\xf3\x9b\xdf`tt\xb4\xe0\xf9\xb5Z-n\xbe\xf9f\xf4\xf7\xf7\xe3\xa2\x8b.\x82B\xa1\x10=\xeaK\x8dY\xfa\xbf\xf4s*\x95\x0a\x7f\xfe\xf3\x9f\xf1\xa3\x1f\xfdH|\xde\xedv\xe3\xf1\xc7\x1f\x17Ef\x0cS\xe9\xcc\xd8(\xa5\x89\x84\xcdc\x99\xb2\xee`\xde\xaf\x08\xb6Z\xad\xf0z\xbd\xf0\xfb\xfdX\xb2d\x09zzz\xc4\x91\x0c\x1d\xf7\x90\x8b>E\x14\xb2\xd9\xac\x88\x06\x90\xe5E.\x97C{{;\x16-Z\x84\x9a\x9a\x1a\xb8\xddn\x98\xcd\xe61\x1d\x00hL\xd3sI\x05\x0dE\xee\x92\xc9$\xc2\xe10\x06\x06\x06\xa0\xd5j\xd1\xd4\xd44&\x82\xb2o\xdf>\xb4\xb5\xb5!\x12\x89 \x16\x8b\x89~\xc4\xd2$\xe9|>\x0f\xbf\xdf\x8f\xa5K\x97\x8a\xe8\x9f^\xaf\xaf\x88\xca\xdf\xf1\xaa0\xd5j5\x22\x91\xc8\x18\xa1G\x02\x90\xc49o\x0a+\x0f\x8dF\x03\x97\xcb\x85g\x9f}\x16\x17]t\x91\xe8\xdet\xf5\xd5W\xe3\xc9'\x9f\xc4\x87>\xf4!\xa4\xd3\xe9i\xebJ\x91\xcdf\xa1T*q\xcd5\xd7\xa0\xb3\xb3\x13\x00\xe0\xf1xPUU%r^GGGE\x8e\xef\xe8\xe8(\x8e\x1e=:\xe6o {\x1a\x8a\x10\xaeX\xb1\x02\x9b6m\xc2\xed\xb7\xdf\x0e\x87\xc3\x81\xe1\xe1a\xb1.QZ\xc2x\x1b\x13\x12\x80\xb9\x5c\x0ef\xb3\x19\x8f=\xf6\x18>\xfe\xf1\x8f\x8b{0\x95J\xe1\xe1\x87\x1fF}}=\x0f\xa0In\xde\x999.\x00yrgfb\x22!_@\x87\xc3\x01\x9f\xcf\x87\x86\x86\x06\xd4\xd6\xd6\xa2\xa3\xa3\x03\x0a\x85B\x08\xbcd2Y\xd0-\x83\x8eu(\xe7\x87\x04a(\x14B[[\x1b\xfc~?jjj`\xb5Z\xa1\xd5jE\x94\x90\xc6\xb64*X,\x00\xd3\xe94\xe2\xf18\x82\xc1 \x8e\x1c9\x82\xab\xaf\xbez\xcc\xc47<<\x8c\xad[\xb7bdd\x04\xc3\xc3\xc3\xa2\x0d]q\xee_>\x9f\xc7\xc6\x8d\x1b\xe1p8\xe0r\xb9`6\x9b\xc5\xeb\xa94\xb2\xd9,\xd4j5\xc2\xe1\xb0\xe8\x11K\x11L\x8a.\xa5\xd3\xe9\x13\xca\xaddf\x86\x0b/\xbc\x10w\xdf}7\xbe\xfa\xd5\xafB\xa3\xd1 \x99L\xe2\xaa\xab\xae\xc2\xa3\x8f>\x8a\x8f|\xe4#\xc8f\xb3\xd32\x06i\xdd\x18\x18\x18\x80\xd9lF4\x1aE\x7f\x7f?\xfa\xfb\xfb\xc7_\xc8\xde\xcf\xe5\xa5\xfb2\x99L\x8a\xcfo\xd9\xb2\x05\x9f\xf8\xc4'\xb0p\xe1B\xd8\xedv\xf1;hSW,\xf2&\x12,\x1a\x8d\x06\xbf\xf9\xcdo\xf0\xdd\xef~W|.\x95J\xe1\xf6\xdbo\xc7\x07>\xf0\x01.z<\x0em\xc0\xef\xd9\x1c\x13\x80\x5c\xe2\xcd\xcc$r\xb9\x1cj\xb5Z\xf8\x02RAHOO\x8f8*\xa2c^\xda\xf1\x17\xb7s\xa2\xa4u\x1a\xbf\xfb\xf6\xedCSS\x13:;;\x85\xd92\xe5\xdbI\xab\x00\x8b'/:\xbaM$\x12\x08\x06\x83\xe8\xee\xee\x86\xdb\xed\x86\xcf\xe7C.\x97+Xp:::p\xe8\xd0!\x04\x02\x01\xc4\xe3\xf11]?\xe8\xf9\x1a\x1a\x1aPWW\x87\xea\xea\xea\x93\xd2\xf3w\xbc\xfb\xbb\x18\x85B\x01\x93\xc9\x84\x91\x91\x11h4\x1ad2\x99\x82\xe35\xe0\xbdN-\xd4\xadE\x1a\x05\xe4\x05\xa0\xf2\xc8f\xb3\xf8\xcaW\xbe\x82X,\x86\xff\xfc\xcf\xff\x14\xd7\xe8\xba\xeb\xae\x83J\xa5\xc2\xe6\xcd\x9b\xa7\xe5\xf7\xaaT*Q\x80\xa5T*\x8f\xb9\x8e\xd0}H\xe3\xd2\xeb\xf5\xe2\x9ak\xae\xc1\x8d7\xde\x88%K\x96\x881{\xac\xce R\x93\xe9b\xb1\x22\x93\xc9`6\x9b\xf1\xf4\xd3O\xe3\xee\xbb\xef.\xd8\xe8}\xf1\x8b_\xc4E\x17]\xc4\x91\xad2\xcc!\xcc\x1c\x10\x80\x0c3\xd3\x02\x90\xfav\x92/`CC\x03\xf6\xed\xdb\x87\xbe\xbe>(\x95J\x11\x05\x94\xb6\x80#\xd1\xa2\xd1h\x84A4M\xe2\xa9T\x0a{\xf6\xec\x81\xcf\xe7\x83\xd3\xe9\x84\xd1h\x84Z\xad.\x10_$\x06\xa5\x93>\xf5!\x8eD\x22\x18\x1a\x1aB__\x1f\xae\xb8\xe2\x8a\x82D\xf6|>\x8fh4\x8a\xbf\xfc\xe5/\xa2\xf2\x97\x16<:V\x96.z\xcb\x97/GMM\x0d\xaa\xaa\xaaD\xf4o\xa6s\xe8\xc6\xeb\xa4 \xfd\x1aE[\x13\x89\x84\xe8\xb2\x22\xed\x05+\x93\xc9\xd0\xdd\xdd\x8dP($:\x9dX,\x16\x16\x80\x15\x0a\xf9c~\xe3\x1b\xdf\x80\x5c.\xc7\x9dw\xde)6P\x1f\xfe\xf0\x87\xf1\xc3\x1f\xfe\x10\xb7\xddv\xdb\x98\xd6\x9e\xe5\xc0h4\x22\x91H\xa0\xad\xad\x0dmmm8t\xe8\x10\xc2\xe10\xa2\xd1(\x86\x87\x87\xd1\xd9\xd9\x09\x9b\xcd\x86\xc5\x8b\x17\xc3\xe5ra\xc1\x82\x05\xa8\xaf\xaf\xc7\xc8\xc8\x08\x14\x0a\x05\x96.]\x0a\xa3\xd1\x88d2)\xa2\x83\xa5\xe6\x8d\x89\xc6x\xf1\xe7\x9f\x7f\xfey\xdc}\xf7\xdd\x22\xbf/\x93\xc9\xe0\x9e{\xee\xc1\xcd7\xdf\xcc\xe3\xf78`#\xe8y&\x00\xf9b3\xd36\x90\xdf\xaf\xd4\xb5X,\xa8\xae\xaeFCC\x03\x1a\x1b\x1bq\xf4\xe8Q\xd1\x98=\x93\xc9@\xaf\xd7\x8b\x89\x87\xf2\x00)\x82(\xb5\x83\x91\xc9d\xe8\xe9\xe9Akk+l6\x1b\x8cF#T*\x15\x9cN\xa78>\x92&\xc4S\x04\x22\x99L\x8a~\xbf]]]\xf0\xfb\xfd\xa8\xab\xab\x1b\x13M\xd8\xb9s'\xda\xda\xda\x84=\x0cY\xd6\x14\xe7\xc5\xb9\xddn\xac\x5c\xb9rL\x0e\xd4\xc9\x16\x80\xd2\x1d\x0c\xa3\xd1\x88\xcd\x9b7\xc3\xeb\xf5\x8eY$v\xef\xde\x8dW^y\x05\x07\x0e\x1c\xc0\xc8\xc8H\x81\xaf\x99\xb4\xf27\x97\xcb\xe1\x82\x0b.\x80\xddn\x87\xdb\xed\x86\xc5b\xa9\xa8\xe8_q\x15\x9f\xf4c\xb5Z-\x8cv\xe5r9\x0c\x06C\x81\xa7\xa1B\xa1\xc0\xe0\xe0 ^~\xf9e\xa4R)\xc4b1\xd4\xd5\xd5\x89\xc8\xeaLu6\x99/\xd0F&\x9f\xcfC\xa7\xd3\xc1h4\xc2h4\x8a\xb6\x89\x93\x8d\x04R\x84|\xe3\xc6\x8d\xd8\xb5k\x176m\xda\x84\xfd\xfb\xf7C\xab\xd5\x22\x9dN\xe3\xdak\xaf\xc5#\x8f<\x82\x9f\xfc\xe4'hjj\x12\xc2\xbf\xdc\x22\x89\xee\xdf1'J8~1A\xa6\xd2V\xab\x15o\xbf\xfd6n\xbd\xf5V\xbc\xf6\xdak\x05\x91\xeds\xce9GX\xbd\xb0\xf8cX\x00\x1ec\x91\xe0I\x9c\x99i\xe4r9\xb4Z\xad\xc8\x05\xf4\xfb\xfdX\xb4h\x11^}\xf5U$\x93I1iS^ y\x04\xd2\xcfR\x84M\xa3\xd1\xc0h4\xc2\xe7\xf3\xc1\xe3\xf1\xe0\x82\x0b.\xc0\x1bo\xbc\x81w\xdf}\x17\x89D\x02\xdd\xdd\xdd0\x18\x0cP(\x14H&\x93\x88F\xa3\xd0h48\xf3\xcc3\xb1|\xf9rq\x9c)]$R\xa9\x14v\xef\xde\x8d\xa3G\x8fb``@T\xfe\x16\x1f\xff\xe6\xf3y\xb8\xddn477\xa3\xba\xba\x1av\xbb\x1d:\x9dND\x1cO6\xd2\xfe\xc3\xc5]=\xe8\x08\x8e\x84\x9e4:H\xef\xb7\xc3\xe1@SS\x13\x94J%\xb6m\xdb\x86\xde\xde^,^\xbc\x18\x1e\x8fG\x1c\xad\xf3\xdcQ\x1e\xa1Dc0\x91H \x9b\xcd\xc2b\xb1\xc0\xe3\xf1\xa0\xba\xba\x1a2\x99\x0cz\xbd~JG\xee\xf4\xbd\x0b\x16,\xc0\xbb\xef\xbe\x8b\x9bn\xba\x09\xbf\xfe\xf5\xaf\xc5xx\xfe\xf9\xe7\xb1l\xd92\xdc|\xf3\xcd\xf8\xc1\x0f~ \x04h9\x8f\xf5e\xf2\x7f\x0a\xc0rT\x93\xe6r9\xe1\x12p\xe3\x8d7\xe2\xb7\xbf\xfd-\xe2\xf1\xb8x\x0fS\xa9\x14\xbe\xf0\x85/\xe0\xee\xbb\xef\x86V\xab-(\xe8b\x8e/0T\xfc13\xc7\x04`qd\x80afJ\x00R;\xa8\xaa\xaa*\xd4\xd4\xd4\xa0\xbe\xbe\x1emmm\x18\x1c\x1cD&\x93A\x22\x91\x00\xf0^Q\x02\xe5\x0dIs\x7f\x8cF\xa3\x10\x91\x16\x8b\x05MMMX\xb6l\x19\xd6\xacY\x03\x00\x18\x1c\x1cD2\x99DWW\x17\xd2\xe94\xccf3|>\x1f\x5c.\xd7\xb8\x13[6\x9b\xc5\x1f\xfe\xf0\x07\xbc\xfa\xea\xab8|\xf8\xb0\xb0\xa7!3\xdb\xe2\xe2\x8f\xb5k\xd7\xc2\xeb\xf5\xc2\xe5r\xc1b\xb1\x9c\x94\xca\xdf\xf1\x88\xc7\xe3\xa2x\xa6\xb8C\x8aF\xa3\x11\x06\xd0\x14\x0d\xccf\xb3\xd0\xe9t\xd0\xeb\xf50\x99L\xf0z\xbdhhh\xc0\xb9\xe7\x9e\x8b\xef}\xef{\xd8\xb3g\x0f\x8cF#\x02\x81\x00\x82\xc1 R\xa9\xd4\x94:\xaf0\x13\xcf\xbf===\xd8\xbbw/\x12\x89\x04t:\x1dZZZD\xd5<\x09\x9f\xe3\x19[r\xb9\x1c\x0f?\xfc06o\xde\x8cO}\xeaS\x18\x1c\x1c\x14\xe3\xff\x87?\xfc!~\xfc\xe3\x1f\xe3\x17\xbf\xf8\x05\xae\xbc\xf2JX,\x96\xf2\x09@\xc8D\xb1\xd0\x89\xe4\x95+\x95J(\x95J\xc4\xe3q\xbc\xf0\xc2\x0b\xf8\xf9\xcf\x7f.>O\xb8\x5c.\xfc\xfa\xd7\xbf\xc6\x05\x17\x5c \xc6$oN\xca?FY+\xb0\x00d\x98\xb2 \xcd\x05\xf4z\xbd\xa8\xaf\xafG}}\xbd\xa8\x08\xa6\x02\x8fT*U\x90\x0cO\x22\x8c\xaay\xa5\xf9m\xa9TJ\x1cu\x91\xd0\x93\xf6\xf5\xa5\x9f\x97V\x12RD,\x99L\xe2\xcd7\xdf\xc4\xae]\xbb088\x88H$\x82`0X\xb2\xf8\x03\x00\x9cN'\x16/^\x0c\xaf\xd7[q\xd1\xbf\xe2\x05Pz\x0cN\xef\x81\xb4\xad\x16u\x8c A\xa8\xd3\xe9\xa0\xd5jQ]]\x8d\xc6\xc6F\xa4\xd3iQ\xe5)\xad\x96f\xca\x0f\xb5s\xeb\xee\xee\x86\xcf\xe7\x83\xcdf\x83\xc1`8\xee\xe7#\x8b\x9fG\xa8\x91U\x00\x00\x1daIDAT\xcb/\xbf\x1c\xbbv\xed\xc2\xd7\xbe\xf65\xfc\xeaW\xbf\x12\xddwr\xb9\x1cn\xbc\xf1F<\xf7\xdcs\xf8\xfd\xef\x7f/\xa2\xee\xe5\x18\x7f\xc5\xde\x7f\x93]o\xa4'S\xad\xad\xadx\xec\xb1\xc7\xf0?\xff\xf3?\x18\x1a\x1a\x12v1d\x22}\xf3\xcd7\xe3\xfb\xdf\xff\xbe\xb8\xef\xa7\xab\xf3\xc9|\x15}\xac\x15* `2\x9dj\xbed\xa2.\xc3L\xf7\xa0~?\xbf\xcfl6\xc3\xe5r\x89c`\xb3\xd9,\xc4])\xcf=\x8aj%\x12\x09\xa4\xd3i\x84\xc3a\x0c\x0e\x0e\xe2\xc8\x91#hoo?\xe6\x22 5\x89\x96V\xc8\xbe\xf2\xca+x\xf6\xd9g\xd1\xde\xde\x8e\xfe\xfe~Q\xf8!\xb5O\xa1\x85I.\x97c\xc9\x92%\xa8\xab\xab\x83\xc7\xe3)\x88\xfeUZ\xe4\x81Z\xbeI\x0bSH\xe4Q!\x00\xe5VJ;\x81\x00\xefuA\x19\x1a\x1a\x82B\xa1\x18\x131\xa5k\xc2\x8f\xe3\x7f\xd0\xfbH\xe9\x0d\xdf\xf9\xcew\xf0\xc4\x13O\xa0\xab\xab\x0b\xb1XL\xe4\x9fR\xf4\xf9\xb8#\x08\xef\x1b5\xbb\x5c.<\xf4\xd0C\xd8\xbe};N?\xfdtQu\x0f\x00\x9f\xfd\xecg\x91\xcb\xe5\xcav\x0c,\x15\x80S][d2\x19^x\xe1\x05l\xdc\xb8\x11\x17^x!~\xf9\xcb_\x8aqH\xe3\xf3\xfc\xf3\xcf\xc7\x8e\x1d;p\xdf}\xf7\x89#_\x16\x7f\xe5\xd3\x08\x13\x8d7\xd6\x0a\xb38\x02X\xaep.\x87\xd8\x99rD\x01\xa5\xb9\x80\xb5\xb5\xb5hll\xc4;\xef\xbc\x03\x00\xa2\xe8\x22\x99L\x8a\xcaS\xa9\xb1s2\x99D*\x95B8\x1cF0\x18\xc4\xdbo\xbf\x0d\x97\xcb\x05\xb7\xdb=\xa9\xc5@.\x97c\xdf\xbe}\xd8\xb9s'^}\xf5U\xf4\xf7\xf7#\x18\x0c\x22\x12\x89\x08\xb3d\xa9\x08\xa5\xdfKy\x84.\x97\x0b6\x9b\xad\xa2*\x7fK\xdd\xef\x91HDD\x82\xa4\x22Pj\xa7Cy\x82$\x88\xbb\xba\xba`\xb5Z\xf1\xd2K/\xe1\xe2\x8b/\x86\xdb\xed\xe6\xfb\x7f\x9a\x04z<\x1e\xc7\xa3\x8f>\x8a'\x9f|\x12\x9d\x9d\x9d\x88\xc5bH$\x12H$\x12\x22\x1a^\x8e\xdfC\xd7}\xf5\xea\xd5x\xf3\xcd7\xf1\xfc\xf3\xcf\xe3\xe3\x1f\xff8\xd6\xacY\x833\xcf<\xb3\xac\xd7\x956Jt\x0fOE<<\xf1\xc4\x13\xb8\xea\xaa\xabJ\xf6\xa8\xf6\xfb\xfdx\xec\xb1\xc7\xb0a\xc3\x06\xf1\xdc\x5c\x90\x84\xb2\x8d\x0f\x16ys\x5c\x00N$\x0a\xa7\xb2\xcb\xe4\x1b\x8e)\xc7\xa4\xa3P(`6\x9bEw\x90%K\x96`\xdf\xbe}\x22\xc7,\x99L\x8a\x9d?\x1dO\xa5R)\xc8d2\xe8t:D\x22\x11a\x1f\x93L&\xf1\xe8\xa3\x8f\xe2\xd4SO\xc5\x8a\x15+\xa0\xd5ja6\x9b\x0b\xc6j6\x9bE(\x14B__\x1f\xda\xda\xda\xb0o\xdf>\xb4\xb6\xb6\x22\x12\x89`xx\x18\xa1P\x08\xb1XlL\xcf_\x22\x97\xcba\xfd\xfa\xf5p\xbb\xddp:\x9d\xa2\xebG\xa5\x0a@z\x9fI\xc8J\xefy\x8a\xa6\xd2\x91\x1fE^3\x99\x0cl6\x1bzzz\x10\x89D\x10\x8dF\xb1z\xf5j\xb4\xb4\xb4\xc0h4\x0a!\xce\x9c\xf8\xbc{\xe8\xd0!\xfc\xf1\x8f\x7f\xc43\xcf<\x83\xfe\xfe~\x91r maX\xceD|\xba\xe7R\xa9\x14>\xf0\x81\x0f`pp\x10\xdd\xdd\xdd\xd0j\xb5e\x1fs\xd2H\xfbT\xb8\xf8\xe2\x8b\xb1d\xc9\x12\xb4\xb6\xb6\xbe\xb7\x00*\x95X\xb2d\x096n\xdc\x88\x8f|\xe4#X\xb3f\x0d\x1f\xf7\xce\x80\x1e`\xe6\xa8\x00\x94\xde\x98\x94\x0fT\x5c\xe1\xc8032\xb8\x95Jh4\x1a8\x1c\x0e\xf8|>\xf8|>,_\xbe\x1c\xaf\xbf\xfe\xbaXD\xa8\xe2T\xaf\xd7\x8b\xf1I\xc6\xc5\xe9tZ\x88\xbfp8\x0c\x97\xcb\x85@ \x807\xdf|\x13~\xbf\x1f\x1e\x8fGT\x11SqIGG\x07\xfa\xfb\xfb\x0b\x04_8\x1c\x16\xc7\xceT\xf8!\xb5\xe6\xa0{\xc2d2a\xf5\xea\xd5p\xbb\xddp8\x1c\xc2\x1c\xb9\xd2\xc4\xdfxG\x8e\xd2\xaf\x93\xc8\xc8d2B\xd4\xc5b1h4\x1a\x04\x02\x01\xa4\xd3i\x04\x02\x01$\x93Itww\xc3\xe1p\xa0\xaa\xaa\x0a\xb5\xb5\xb5\xd0\xe9t\xc2\xf2\xc6h4B\xa7\xd3\x9d\x94\x85\xa7\x9c\xe2\x88\x9e\xebx\xaf\xe5d_\x7f2\x99D(\x14\xc2\xc1\x83\x07\xf1\xc6\x1bo`\xc7\x8e\x1dhooG(\x14\x12Q-i\xc7\x95\xe9\x18[j\xb5Z<\xb7\xdf\xef\x9f\x96MG\xa9\x8e1\x93y\x8fL&\x13\xd6\xad[\x87\xbe\xbe>|\xees\x9f\x13\xf3\x82Z\xadF$\x12A&\x93)\xbb`e\xfey}H\x0b\xb0G\xf0\x1c\x15\x80\xd2\x5c&\xbaY\xa5\xf9K\x1c\xddcf2:\xa5P(`4\x1a\x0b*\x82\xf7\xee\xdd\x8b\xd1\xd1QQ\xdc\xa1R\xa9D\xa7\x0a\xeap\x00\xbc\x17\xd1\xa3j]\x83\xc1\x80\x9e\x9e\x1etww\xc3\xe9t\xa2\xbd\xbd]\xfc\x0e\xb2\x8c\x19\x1c\x1cD.\x97C<\x1eG\x22\x91@4\x1aE4\x1a\x15Gm\xf1x\x5c\x88?\xa9\xef\x1fAG\xbf\x1e\x8f\x07V\xabU\x1cMWb\xee\x9f\xf4\xbe\x9e\xa8\xa7*E\x03\xb3\xd9,4\x1a\x8d\xb0\xcbI\xa7\xd3\xd0\xe9t\x18\x1a\x1a\x12\x1dN\xc8\x9f.\x16\x8b\x8d1\xd0\x9e\xca\x22_\x1cY\xad\xa4H\xc7tZ_P46\x12\x89 \x10\x08```\x00CCC\x22\xed\x80~7\xe5eR\x9e\xe6t\xbd\x96\xe9B*`\x8f\xe7\xfd\xbc\xf7\xde{\xf1\xb3\x9f\xfd\x0c\xb1X\x0c[\xb7n\x15\xc7\xe2F\xa3\x91\xd7\xa7i\x9e7\x8aSm\x989\x22\x00\xa5\x8b\xc2\xf0\xf00Z[[\x91\xc9d\x90N\xa7\xd1\xd1\xd1\x81\xd1\xd1Q\x84\xc3a$\x12\x09\x11\x0d\x91\xee\x06fb\xe2`\xe6\xe1\x00\x7f?\x0ah\xb7\xdb\xe1\xf5zQSS\x83\xc6\xc6F\xbc\xf5\xd6[\xa2Wm\x22\x91\x80\xc1`\x10BE\xea\x0bFQl\x12\x88z\xbd\x1e\xa1PHT\x19\x93P\xa4\xde\xb7\x99LF\x88<\xe9d\x97H$D\xe5\xb14\xfaG\xd8l6\xb4\xb4\xb4\xc0\xe7\xf3\x89\xca_\x8dFS1\xc7\xa1R\x93\xeaP(\x84}\xfb\xf6A\xa1P \x18\x0c\x8a\xc2\x96p8,\x22\xa7T|@\xa2'\x97\xcb!\x1a\x8d\x0a[\x18\x00\xc2k-\x14\x0a\x89B\x97t:\x0d\x83\xc1\x80\xae\xae\xaec\xce\x09\xb3\xcdG\xecD\xc5\xd6\xb1~>\x9b\xcd\x22\x95J!\x93\xc9 \x12\x89 \x16\x8batt\xb4\xc0V\x87\x22\x80\xd2\x0d\xfal\x9bs\xc7+\x02\x99lT\x89\xa2\xf6\xf4^p$jz\xaf\x15\xcdu}}}hmm\xc5\xc8\xc8\x08\x06\x06\x060<<\x8c\x91\x91\x91\x82\xd3\x96\xe2\x8d1\xfd\xcb}\xc2+\x5c\x00J/\x18%\xcf\xd3b\x11\x0e\x87\x0b\x92\xe0\xc73\xc0\x95F\x10\x19\xa6\x1c(\x14\x0a\x11\x05\xf4x<\xa8\xaf\xaf\xc7\xc2\x85\x0bq\xe0\xc0\x011Fe2\x99\xc8\x01\xa4\x1e\xb6T!IQ\x12\x95J\x05\x99L\x86H$\x02\x8dF\x83d2\x89`0\x88|>\x0f\xbd^/\x8e8\xa59\xaft\xdcKQ\xb0x<.\x04e\xf1D\xb6h\xd1\x22,X\xb0\x00.\x97\x0bV\xabU\x1c\x81V\xd2\xbd@\x133\xe5:\xaaT*\x84\xc3a\x0c\x0d\x0d\xa1\xbf\xbf_\xe4\xf4\xd1\x91x\xf1u\x00 r,\xa5\x0b\x84B\xa1@(\x14\x12\xdf\x17\x08\x04Dk\xbc\xa9F\x99\xa6k\x81\x98-\x0b\x0fy3R\x11\x13\xe5]J\xe7U\xe9b:[\xf3-O$\x02Hs\x02\xa5'\xb0\xb0\x98\xfey\x83\xdc\x15\x82\xc1 b\xb1\x18B\xa1\x10zzz\x10\x8dF1::\x8aX,&r\x89KE\xf0\xf9\xfa\xcc\x82\x08 M2\xc3\xc3\xc3\xd8\xb7o\x1f\x86\x87\x87E\x97\x04Z\x1c\xa4\x11\x02i\x04\x90&'\xee\x02\xc0\x94\x1b\x95J\x05\x8dF\x03\x9b\xcd\x06\xbf\xdf\x8f\xfa\xfaz\xd4\xd5\xd5a\xf7\xee\xddb\x13B\xd1\xbbX,V\x10!\xa1\xc8\x1e\x99\xe5*\x95J$\x93I\xa8T*\xf1 \xe3c\xe9B\x22\xb5\x96\xa1^\xc1\xd9lv\x8c\xfd\x0c\x15\x9c\xac]\xbb\x16n\xb7\x1bUUU\xd0\xeb\xf5\xa2%Z\xa5-\xba\x00\x10\x0e\x87\xd1\xd6\xd6&&n\x9a\xc4\xa3\xd1\xa8\x10\xbc\xc5\x02\xf0X\xd1\xbcb\x11CsA%L\xfc\xb3i\xf1\xa1\xb1%\x1dk\xd2\xb6\x9c\xd2\x08\xdal\xed\xc4p\x22>\x80\xe3\x8d5f\xfa\x85z \x10\xc0\x9e={\xd0\xdd\xdd-\xf4\xc0\xf0\xf0\xb0\x08\x08\xd1\xc6\xb1x\xbc\xd2ub\x11X\xc1\x02\x90\x8eyd2\x19FGG\x11\x0c\x06E\xd4\x83\x16\x06\xca\x85\x92\x1e\xb5\x95\xf2\x0ed\x01\xc8\x94;*\xa2R\xa9`4\x1a\xe1p8P[[\x8b\x95+Wb\xef\xde\xbdb\xa1$\x1b\x93\xe2\xb1'\x97\xcbE\xb50\xf5\x1a&\x93[\xaa\x10\xd6\xe9t\xc8d2\x05\x9b\x17\xb2\x92\xa1EY:\xfe\xa5c=\x9f\xcfc\xc9\x92%\xa8\xaf\xaf\x87\xcb\xe5\x82\xd9l\x86N\xa7\xab\xc8\x8d\x10\xbdn\xca1\xa3\xa3\xf1H$\x22\x8e\xb8\x8b\x0b[&\xbb8\x17\xe7\x0d\xce\xf5]\xfft\xe7\xdeI7\xd7\xf4\xb1TXOV\x8cW\xe2\xfb&\xed\x05|\x22\x22\xb6\xf8~\xe7ugz6$t\x8d\xfa\xfa\xfa\xd0\xdf\xdf\x8fd2)R\xc2h\xfe\x95\xce\x1d\xc5\x91\x7fi\xee S\xa1\x02\x90\x16K\x85B\x81H$\x82P($\xaa\x1e\xa59O\xc5\xea\x9e&)\xba\xa19A\x94\x99\x96\x81\xaeTB\xa7\xd3\x89\x5c@\x9f\xcf\x87U\xabV\xe1\xf5\xd7_\x1f\xd3\xcd\xa2xA\x90V\x0c\xd3\x18\xd6j\xb5H\xa5R\xa2\xa0A\xa5R\x15$\xd9\xd3\xc4GB\x90\xa2\x80\xd2\x85\x99\xee\x97\xf5\xeb\xd7\xc3\xe9t\x8a\xca_i\xff\xdcJ\x8b\xba(\x95JD\xa3Q\xd1U\x85v\xee\xe3\x1d\xdd0'O(\x8d'\xa4\xa9\x05\xdal\x8e*\x8d\x97\x03x\xbc\x82\x99\x05\xe0\xf4\x5c'\x9a\xc7\xe4r9\x22\x91HA\x0a\x98t\xde(\xbe~\xc5u\x01\xe5\xec%\xcdL\x83\x00\xa4\xe31\xbd^\x8fL&\x83\x91\x91\x91\x82\x04\xd0\xc9F\x05t:\x9d\xc8}\xe2\x1b\x92)\xe7\xf8\xa4\xee \x1e\x8f\x07uuu\xc2\x0f,\x18\x0cNj\xc1\x91\xf6\xec\xcd\xe7\xf30\x18\x0cB\x10R\xa7\x03\xfa\x7f\x22\x91\x80Z\xad\x169qT\x85Y\xdc\x0c\xfd\xcc3\xcfDuu5\xdcn\xb7\xe8\xfaA\xf9\x86\x95\x04\xfd}\x1a\x8d\x06\xf9|^\xe4?J\xff~\xa6\xb2\x04\xe0xc\xf9D\xfa\xffV\x8a\xb0(\xb7@\xa19\x82)\xffuR\xa9T\xd0\xeb\xf5H$\x12\x18\x1e\x1e\x16\xf3E\xa9`\xd0\xb1\xc6,U\xb03\x15$\x00I\xfc\xe9t:\x98\xcdf\x11\x15)\x95\x13u,\xccf\xb3h/\xc5\x17\x9a)'\x0a\x85\x02:\x9d\x0eV\xab\x15~\xbf\x1f\xb5\xb5\xb58\xe3\x8c3\xf0\xdcs\xcf\x8d\x9b\x08^*\xd2@\xc9\xe3\xd4\xf3T:V\xa9\xe2W\xa7\xd3\x09\xc3gz\x14\x1f\x8f\xeat:,_\xbe\x1c\x1e\x8f\x07UUU0\x1a\x8d\x15\x99\xfbG\x93\xb8F\xa3\x81\xd5j\x85V\xab\x15\xc7\xe2'\x9a\x83\xc5\xcc\xdcbLcY\xab\xd5Vt\x87\x99\xa9\xfc-'*\x109\x028\xbd\xd7I\xadVC\xa7\xd3\xc1b\xb1@\xa5R\x89\x22\xb0RQ\xbf\xf1\xae\x8b\x5c./\xf0\x06\xadDk\xacy+\x00)<\xabV\xaba2\x99\xe0\xf3\xf9D\x94`*\x0b\x02\x0d\x88\xaa\xaa*\x98L&\xa1\xf8yQa\xca\x05mT,\x16\x0b<\x1e\x0f\x9a\x9a\x9a\x10\x8b\xc5\xf0\x8f\x7f\xfc\x03}}}\xc7\x8c\xa4H\xad4\xc80Zz\xc4!\x15v\xe4\xf5G\xd1\xbfb\xdb\x17\x00X\xbat)\xea\xeb\xeb\x85\xef\x9f4\xfa]i\xc2\x99L\x99\xa9HE\xfa~0\x95\x8f4\xb7\xcal6\x8b\xcd\xc6l=Z\x1b/8 \xb5\x0d\x99\xaa\x00daQ\xfe\xeb#\x97\xcb\x85\x03\x834Mf2\xe3\x95H\xa5R\xb0Z\xad\x228\xc4\xd7\xa9\x82\x04 \xf5d\xd4\xe9t\x22\xc1~\xfd\xfa\xf5\x22G\xaa\xb8\x92\x87vo\xd2\xfc*\x95J\x05\xadV\x0b\x93\xc9\x04\xaf\xd7\x0b\x87\xc3!\x12\xe1\xf9\xec\x9f)\xe7\x8e\x94z\x04\xdb\xedv,X\xb0\x00\x00p\xd5UWa\xc7\x8e\x1d\xc2\x8bJ\x9a\xabJ\x95\x94\xa9TJT\xaee\xb3Ya\x19\xa3\xd5j\xa1\xd1h\xc4\xf8\xa6/\x04`:\x9d\xc6e\x97]\x86\xd3O?\x1d\xa3\xa3\xa3\xa20N\x1a\x18\xa2\xebI\xa2P\xa3\xd1\xc0`0\xc0n\xb7\x8f\x19\xb3\x9c\x1eVA\x02\x90&\x18\xab\xd5*\x12\xc5\xddn7\x12\x89\x84\x88\x10\x94*\xb9\x97VB\xaa\xd5j\x18\x0c\x06\x98L&q<\xc1\xa1^f:&&:\x8a\xa0vkf\xb3\x19\xe1p\x18\xb1XlL\xa1\x07\xf0\x9e\x17\xe0\xc8\xc8\x88hS\x16\x08\x04\xa0\xd7\xebQ[[+\xbe/\x9dNC\xaf\xd7\x17\xb4{\x93\x8e\x7fi\xe5/\x80\x82\xca_\x8a\xfeU\xea\xa4F\xef\x99\xd1h\x04\x00h\xb5Z8\x9dN\xd1\xd5G:\x91K==\xf9\xde\x9dY\x8a#`\xd2\xe3PZP\xb5Zm\xc1\x11\xf0l\xcd\x03,\xfe\x9b\x8b\xff\xfe\xc9\xfeM\x1c\x01\x9c>\xc8D\x9f\x8e\x80u:\x1d\x5c.\x17\x12\x89\x84h\xb7Y\xea:HuA\xf1\x98\xa5\x9ck\xa6\x82\x04 -\x10\x00`\xb1X\xa0\xd7\xeb\x0b\x8e\x87\x8e\xe58O\x17\x9bz\xb1\xd2\x83U>3]\x82\x86\x04\x17%\x18\xdbl\xb6\x92-\xda\xf2\xf9<\x22\x91\x08\x06\x07\x07\x11\x89Dp\xf0\xe0A\xa8\xd5j,^\xbcX\x08\xc8@ \x04$\xfd,\x89\xc0\xe2\xa3\xdf\x5c.\x87e\xcb\x96\xa1\xa9\xa9\x09n\xb7[\xecj+}!\xa6|\x5c\x12\x0e\x16\x8b\xa5\xe0\xfe\xe6hJ\xe5\x08\xa2R\xd11\x8a\xe2\xd2f[\xadV\xcf\xfaH\x0a\xb9L\x9c\xe8s\x14\x7f\xccc\xb7\xbc\x22\x10\x80\xf07\x95\xea\x82\xf1\xdeo\xe9\xc6E:fU*\xd5\xac\xdf\xb0\xccI\x01(\x15\x81$\xe4t:\xdd\xa4\x0bA\xa4G\x15\xec\x03\xc8\xcc\x04\x14\xb5V(\x14\xc8f\xb3\xd0\xeb\xf5\x05bFZ\xf0A\xad\xca\xe8hw\xf9\xf2\xe5\xb8\xfe\xfa\xeb\x11\x0e\x87q\xe4\xc8\x118\x1c\x0e\x1cW\xc5\x95\xbfk\xd7\xae\x85\xd7\xeb\x85\xd3\xe9\x84\xc1`\x98\x15\xb9\x7f\x0cS\x89\xd0=u\x22-\x08K9S0\xcc\xbc\xba\x8f\xf8-`\xe6;\x94\xaf\x07@\x18\xe3\xe6\xf3y$\x12\x09D\x22\x11\x0c\x0f\x0fC\xa3\xd1\xa0\xa5\xa5\xa5`\xb1\x88\xc7\xe3hkkC$\x12A,\x16C4\x1a\x15\xfe\x7f\xd2\xe8_>\x9fGuu5V\xacX\x01\xa7\xd39k*\x7f\x19\xa6R!\xcf\xccr\x1e\x01\xf3\xbd\xc8\xb0\x00d\x98y\xb8\x98\x90\xcd\x00\x1d\x07e\xb3Y\xe1\xff\xd7\xd5\xd5\x85\xb3\xce:k\xcc\xcfuvvb``\x00\xc1`\x10\x91H\xa4\xe0\xf8Wj\x7f\x94\xcf\xe7q\xe1\x85\x17\xc2n\xb7\xc3\xe9t\xce\x9a\xca_\x86\xa9\xd8\x85\xeb}\xe1&\xad\xd6\x97\x1e\xe5N\x16\x8e\x002,\x00\x19f\x1eS\xdc\xc1\x82L\x9cC\xa1\x10zzz`4\x1a\xd1\xd4\xd44\xe6\xb8\xa9\xad\xad\x0d}}}\x08\x87\xc3\xa2\x8b\x08\x89?\x22\x97\xcba\xe1\xc2\x85\xa8\xa9\xa9\x81\xd7\xeb\x85\xcdf\x83N\xa7\xab\xd8\xb6o\x0c3[6mr\xb9\x1c\xf1x\x5c\x08\xc0P($\x22\xf9\x93\x15\x91l\x04\xcd\xccg8\x07\x90a$bM*\xfe\x06\x07\x07\xd1\xd5\xd5\x85\xeb\xae\xbb\x0e\xb9\x5c\xae`\xb1\xe8\xed\xedEkk+\xba\xba\xbaD\xf4Oj\xfd\x22=\x8eZ\xb3f\x8d\xc8\xfd3\x99L\xa2\xcb\x0d\xc30'\xb6i\x8bF\xa3\xd8\xb3g\x0f\xd2\xe94\x06\x06\x06\xa6l\x0c\xcdG\xc0\x0c\x0b@\x86\x99\xc7\x90`\x93\x8a\xbf\xfe\xfe~\x1c\xe8gs\xb9\x1c\xd2\xe94\x12\x89\x04FGGq\xf4\xe8Q\x1c\x9f\xc7\xf0\xf00\x06\x07\x07100\x80\x03\x07\x0e\xe0\xc0\x81\x03\xe8\xec\xec\x14\x85&\xc1`P\x08?\x12\x7f\xd2\xc2\x0f\x85B\x81L&\x83\x8d\x1b7b\xc9\x92%\xa8\xa9\xa9\x81\xc3\xe1\x80\xc1`8\xa6Pe\x18\xe6\xf8\xe6\x84\xf16qS\xb9\xdf\x5c.\x17\x16/^\x0c\xadV\x0b\x8f\xd7\xc3o,\xc3\x02\x90a*\x09\x8a\xe6e\xb3YD\xa3Q\x0c\x0d\x0d!\x9dN\x17\xe4\x00\x92\xa9\xeb\xd0\xd0\x10\xe2\xf18\x86\x87\x87\x11\x0e\x87E\xa4 \x9f\xcfC\xa9T\xa2\xbf\xbf\x1f\x81@\x00n\xb7\x1b\xfd\xfd\xfd\x88\xc7\xe3\x08\x85B\x88\xc7\xe3\xe8\xed\xedE$\x12A\x7f\x7f?\xa2\xd1(b\xb1\x18\xc2\xe1\xb0\xb0\x9e\x91\xb6{\xa3\xe7\x94\xc9d\xc8f\xb3X\xbat)\xce>\xfbl\xf8\xfd~x\xbd^\x98L&\xf6\xfdc\x98\x93(\x10'\xb5\x00*\x95X\xb8p!\xbfa\x0c\x0b@\x86\xa9\xe4\xc9\x9c\xaam\xa9\x02W&\x93\xc1h4\xe2\xe8\xd1\xa3\xe8\xee\xee\xc6\x8b/\xbe\x88\xa3G\x8fb``\x00\x89DB\xe4\x0a\xd2sd\xb3Y\xb8\x5c.(\x95J\x8c\x8c\x8c \x91H@\xa1P\x08\x91\x17\x89D\x90\xcdf\x91J\xa5\x84\xc7 \x09?i\xab7\x12\x7fd\xf8\x5cUU\x85\x8b.\xba\x08\x0b\x17.Dmm\xad\xc8\xfd\xd3h4,\xfe\x18\xa6\x82\x05\xa0t#\xc70,\x00\x19\xa6\xc2\x90\x8a\xae\x5c.\x87@ \x80\xbf\xfd\xedop:\x9d\xc8\xe5r\xe8\xec\xecDww7\xf6\xee\xdd\x8b`0\x88t:\x8dT*U\x10\xa9\xcbd2\x00\x80\xde\xde^$\x12\x09qTL\xad\xe0\xf2\xf9\xe8\xf5zTUUA.\x97\xc3l6#\x1c\x0e\x0b\xc3\xe7l6[\xb0\x00\x94\x8a\x00\xd01\xb1\xb4\xb8C\xa1P\x88|\x22\x8dF#\xa2{Z\xadV|\xacR\xa9\xa0\xd1h\x0a*|9\xc2\xc00'\x17\xa3\xd1\x08\x83\xc1\x80\xba\xba:ttt\x08\xafO\x99\x9c\xab\x80\x19\x86\x05 3\xa7v\xfcj\xb5\x1af\xb3\x19\x1a\x8d\x06V\xabUt\xfc\x18\xaf\x95S\xa9*`\xea\xe0AB\x8e\xa2\x80$\x04\xe9\xb8Y\xadV\x8bJC\x85B\xc1G\xbd\x0cS\x81P\xe4_Z\x11,\xcb\xf3}\xca0,\x00\x999\x83L&\x83Z\xad\x16\x22M\xa7\xd3\x89N\x1d\xc5\x22o2\xcf%E*\xf0H\x0c\x96:6f\x18\xa6\xf2\xe6\x851~\x9f\xb2\xf7\x1f\x0c\xc3\xb0\x00d\xe6\xcedO\x11\xba\xe2N\x1f\xe5x\xeeR\xe2\x90a\x98\xcaG\x9a\x8f+\x93\xc9 \x97q~.\xc3\xb0\x00d\xe6\xfc\x84\xcf0\x0c\xc30\xcc\x14\xd7Q~\x0b\x18\x86a\x98\xd9\x0cG\xee\x19\x86\x05 \xc30\x0c3\x8f\x05 \xe7\xee2\x0c\x0b@\x86a\x18f\x1e\x8bA\x86aX\x002\x0c\xc30\xf3D\xf4\xb1\x08d\x18\x16\x80\x0c\xc30\xcc<\x13\x80\x0c\xc3\xb0\x00d\x18\x86a\xe6\xb8\xf8\xe3\x1c@\x86a\x01\xc80\x0c\xc30\x0c\xc3\xb0\x00d\x18\x86a\xe62\xd2N \x1c\x01d\x18\x16\x80\x0c\xc30\xcc<\x11\x80\x0c\xc3\xb0\x00d\x18\x86a\xe6\xb1\x18dA\xc80,\x00\x19\x86a\x98\xb9\xbe\x90qkH\x86a\x01\xc80\x0c\xc30\x0c\xc3\xb0\x00d\x18\x86a\xe60\xc560\x1c\x11d\x18\x16\x80\x0c\xc30\xcc<\x15\x83\x0c\xc3\xb0\x00d\x18\x86aX\xf41\x0c\xc3\x02\x90a\x18\x86a\x01\xc80,\x00\x19\x86a\x18\x86a\x18\x16\x80\x0c\xc30\x0cS\x99P\xe4\x8f#\x80\x0c\xc3\x02\x90a\x18\x86a\x18\x86a\x01\xc80\x0c\xc3\xcce8\x02\xc80,\x00\x19\x86a\x98\xf9\xb6\x90\xc9\xe5\xc8\xe7\xf3\xfcF0\x0c\x0b@\x86a\x18\x86a\x18\x86\x05 \xc30\x0c3'\xe1#`\x86a\x01\xc80\x0c\xc30\x0c\xc3\xb0\x00d\x18\x86a\xe6*2\x99\x8c#\x80\x0c\xc3\x02\x90a\x18\x86\x99\x8f\x22\x90a\x18\x16\x80\x0c\xc30\xcc<\x16\x83,\x08\x19\x86\x05 \xc30\x0c3\x0fD\x1f\xa1P(\xf8\x0da\x18\x16\x80\x0c\xc30\xcc\xbcZ\xd4\xe4\xbc\xac1\x0c\x0b@\x86a\x18f\xce#\x8d\x00\xf2\xf1/\xc3\xb0\x00d\x18\x86a\xe6\x99\x00\xe4\x08 \xc3\xb0\x00d\x18\x86aX\x002\x0c\xc3\x02\x90a\x18\x86\x99{\x0a\xb0\xb4\x18d\x18\x86\x05 \xc30\x0c3g\xf5\x1f\xe7\x002\x0c\x0b@\x86a\x18f~\x09@.\x02a\x18\x16\x80\x0c\xc30\xcc\xfc\x15\x80\x9c\x03\xc80,\x00\x19\x86a\x98y,\x06\x19\x86a\x01\xc80\x0c\xc3\xcc\x87EM\xc6\xcb\x1a\xc3\xb0\x00d\x18\x86a\xe6<\x05Q?^\xd5\x18\x86\x05 \xc30\x0c3\xbf\x04 G\x00\x19\x86\x05 \xc30\x0c3\x1f\x162\xb9\x1c\xf9|\x9e\xdf\x08\x86a\x01\xc80\x0c\xc3\xccGdr.\x02a\x18\x16\x80\x0c\xc30\xcc\xdc\x16|2Y\x81\xf5\x8bB\xae\xe07\x85aX\x002\x0c\xc30\xf3A\x042\x0c\xc3\x02\x90a\x18\x86\x99\xaf\x8b\x1a\x1bA3\x0c\x0b@\x86a\x18f~\xc1\xd1@\x86a\x01\xc80\x0c\xc3\xcc#\xf2\xf9<\x0b@\x86a\x01\xc80\x0c\xc3\xcc7X\x002\x0c\x0b@\x86a\x18\x86\x05 \xc30%\xf8\xff\x82\xe5f\x8a\x9e\x05\x9f\x09\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\x1a\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x03\xcfIDATx\xdat\x8c\xcb\x0d\xc00\x08C\xcd\xe7\xca\x0e\xd9\x7f)n\x9cX!\xa1\x82H=\xb5\x96\xccC2\x86\xaa\x0a_b\xfcH{df\x99\x19\x88\x08\xfd!\x22n\x83\x99!\x22p\xf7\xa1\xaa\xde\xa0\xaf\xce9/\xdb\x13\xf4\xb2\xf7\xc6Zk\xd8~\x04\x10#y\xae\xfa\xfe\xfd\xfb\x7f\x90\xa5 \x002\x86\x93\x93\x93\x91\x05\xe6*\x90K`\x0e\x81\xeb\x80\xb9\x06C\x02\xa4\x1d&\x00S\x00\x10@8]\x85\xd7Q p\xef\xde\xbd\xff|||`w\x800\x0c\xc0\x02BDD\x84\x11\xc5\x86\xb7o\xdf\xfe\xe7\xe6\xe6\x86k\x80\x853L\xc3\xcb\x97/\x19\xe4\xe5\xe5\x19\xe1F\x81\x14\x800L\xc3\x9d;wP4\xc3leA\xb7\x1a\xc6VTTd\xf8\xf3\xe7\x0f\xdc\x16\x10\x1b\xc3\xd3 \x7f \xdb\x06\x92\x83\xc9+))\x81\xfd\x00\x10\x80t2X\x01\x18\x06a\xe8\xd8\xc9\xff\xffMO^\xbcvU\xc8\xc8\xec\x0a\x93\x15\xa4\x17\x1fM4mO\xe9<\x9a\xa7\x0d\xdc\xa6Uu\xf0T8\x02f\xb6zp\xf7\x81f\x06\xa2D${\x1f/\xa01b\xc8@\xe4\xe9U\x12\x00^R\xc8\x01\xfc\xdf4\xf4\xd6\xac\xd7=-@\xfe\xc4bz\x0b@\xf3' \xe2\xcb\xe6+0\xf7\x90\xf7%\x00set\x03 \x08\x03Q?\xfcb\x10V \xec\xff\xc5\x0a\xac\xc1\x02Rb\xc9y\xb41\x9a\x98hB0jyr\xbd\x96\xc7^\xfa\xdcJ\xbf\x03\xec\xfc@<\x1eB\xb8\x18K\xb5\xe6K\xe5U\xed%\xae\xd7\xf8\xac\x03\x13\xa0N\xb5\x00\x08\xc1\x84\xe2=6@\x13\xa0\x1f @F)e\xcc\xbd(\xc7\x9f\xca\x82)\xa5\xc5f\xb7\x00\x96C\x019\xe71\xd7Z\xb7\x18\xe3\xb2\x03+\xd6\x05H\x00z\x9b\xdf\xf1\xb0\xf2\xe2\x02\xf8\xccB \x9e\x15^\x0e4\xde\x05pI\xb3\x8b\xa4B\xb5\xa7\xbd\x02\xb4\xd6f\x12Q*\xd6\x97wxZ|\xcd\xe7\xd7\xad\xe2\x10\x80\xfdj\xcb\x01\x18\x04a\xc7\xf0\x00^\xc2\xfb\x9fla\x09\x89\xab-\xe0\x92\xed\xcb%\xfe\xeca\xe9\x80R\x8f\x16\x1d\x80\x0f\xe4\xda,\x95;i\x5c\x99dX\x0f\xcdR\xbd\x00\xd8\xe6\xad5*\xd1\x0c@\x0d\xee\x90\x01n\xce\xa2\xf7\xe7L\xafB\x00\xb59\xb2P\x11\x97\x00\xa2\xa5\xa2\xdf\x02`\x808\xcd\xec\xb2i\x86\xf9H\x01\x14\xd8\x18\xe3\x1e\x85>\xcdP\x96\x15\xa3R\x1f\xb0\xdf\xc3\xee\xbfn4,\xc3jy\xa6\x00\xd9,\x8e\xde\x959\xa8|\x88\x0c\x18\xbb\x10@E\xc3\x18d\x0eC2P \xbd\xf7G\xf5l\x03\xcc-\x9f\xc9\x00Kv\x09@\x81D\xb9\x88X/Ud\xdd\xca\x22\x9c\xcd\x163^nyRW\xe1Gl\xd6XY\xbep\x16\xfcb[.\x01\xda\xb1\x82\x14\x88A\x18x\xf1\x03\xad\xf4\xff?\xf4\xd0\x17,9\x08\xd9\xe0$c\xdc]\xba\xa0P(\x22m&\xea\xcc$_\xff\xc1\xdfk\xfe\x06\xb0\x01<|\x14f\x91\xa6&\xe4(-]E]\x07\xafQ`;\x8bi\x16\xea\x81\x8b\xcf\xbe\xae\xeb\xa7\x99\x95Z\xb5\x13\xbf\x07$\xdc\x01\x09^|bW \x94\xe5\x91\xdfd<\x10\xda\x1dI\x98|\xe3\xbe\xef\xfc\x11B\xb6\xdd\x03\xe0\xd9\xc8\xc8\xf6\xa3\x9e\xd5\xd2\x1d\x88\x82G\x9e\x981\xdc\xba\x9b\xde\xdf\xf5z\xc6W\x97l\xf0\xa3\xb9\xd6\x1a\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04YIDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91\x98\x22r2\xa8j\xc7\xb0\x17\xd8\xad\xb5\x8e\xd9e\x03\x06-\x86^`q\x0c\x19 \xc0'\xb5\xd6\x96mW\xe8/&\x85\x88B\xe8\xc9`\xad@-\xf7'Y\xee\x0f\x06\x00\x8e&n\xd0\xa1\xad\xbe\xdb\xbf\x04\x16_\x0f\xbf\x0b\x10\xc8W\x00\xe6\xaa \x05`\x10\x86\xed\xb0\x93\x1f\x12\xff\xff\x1f/^<\xce\x0a\x91\x18\xea\xc6\x06\xc2\x84A\xd9Z3\xd34\xbe\xd6\xd2v)\xfd\x0e\xe0\xd4\x17\xa6\xf1\x10\xc2$,p\xad\x0b\xf4\x82{\xab3\x9f\xc4\x1c\xb8\x00P\xaa\x07\xc0 \xdcP\x8e\xd9\x00]\x00$0\x80=\xe6k9\xe7\x91\x17cte\xf6\x08\xa0t\xf0\x9f\xa7\x94z\xdc.\x80\xa3\xd6\x8a)\x9f@\x94J\x17\xc0\x0aX\xdbJK\xbb\x9cF\xac'X\x0e\x0e\xbb\xf7jc|\xbb\xeb\x01\xe7\xb8\x00:\xd28\x89M\xa6z\xd9'\x80RJ\x97\x1b\x8a\xb4\x1f\x1e\x15\x88\x9b\xc4\xd7v\xb7k]\x02\xb0c\xc5*\x14\x830\xd0\xa1\x9d\x0b.~\x89\xe0\xd4\xb1\x93_\xec\xa7\xf85\xaf\x11\x84r\xbd\x18;\xf4M\x1d\x84BC.Qs\x97\xf8q\xd1\x07\xf0\x02]KK\xd5;i\x5cXd\xb8\xa4\x86\xaeT}\x03\x10\xe7!\x04J\xd1\x0c@\x13\xeea\x06\xe8\x9cE\xdf\xffc53\xbbe\xc69f\xa1E<\x050ZZ\xf4\x8f\x004\xc0R\x8a\xdb\xb6\xad\xf5q\xc7q\xb4i\x09\xcf\x83\x01\xa8\xd7\x14\x8d\xc5\xb9(Z\xce\xb9\x81Y\x91\x0f\x15\x8de\xc1\xa4tt\x1e\xd3\x00\xdd\x81\x8cl\xe7\xb4\xe9j\xadn\xdfw\xf3z\x9a\x00x\xed\xd6uu\xde{\x97RR\x05\xc7\x04\x18\x098F\xccD\x9f\x01-\xac\xfc\xad\xed\xc2*\xd6:\x0c5\x03f\x18c\xbc\x09\xfac\x80\xeb\xde[4\xc0\xb8h\x0a@\x03\x99\xd9\xae)=8\x07B\x1aa\x7f\xbd\xc0\xef\xab\xad\xd0\xb5\xd9U\xf4\x11\x9b\x11\xa0u^\xa8\x05\x7fi[~\x02\xb4gm'\x10\xc2@\xd0\x0f\x0b\x10D\xd0*\xec\xff_\xf0\xc72\xac\xc0\x0a\x8e\x09\xac\x84e_F\xcf\xf3\xc0\x80(^N\xf7\x11wg&_\x7f\xc1\xdf\xf7\xfc\xd7\x81\xd7\x81\x87\x8f:2)/M\x1a\xa2\xe4\xe5\xcaS\x1d,\x92\xca\x95\xc5b\x07\xc8p\xe0\xec\xae\xebn\x8d,\xb8*\xbd\xdfr\xc4\xcd\x00\x8c\x07a\xa6\x0e\xa4EY\xc2\x9bZ\x06,\x92Mg\x04\x0c\xcf\xd8\xb6\xad<\x03\x1al\xb7\x1c\xb0\xf0i\x14fr\xcd\xea\xd47\xe0\x19/Q\x06\x8b\x00I\xbf\xe5\xe0%\x9f\xaf\x91\xaaC\x19\xf0\x8c\xe6g\xe8_Xn\xd8\x0b@\xfa\xdb\xb6MK\x9041\x0bVK\xf7N;\x10]6t\xbd\xaekR\x0cA\x81\x86a\xd8\x11\xdc4M\x09\xf7\x13\xe6\xd7\xa0\xe3\xd1\x0c\x14\xf5\x01-B\x1aq\xc4\xbe\x10\x98\x10H#\x8e\xe8\xff\x1f\xd5\xc8\x96e\xa9\xb0#\x86%u{#\xf3\xa4Z^]\xb054\xcf\xf3>\xbf\xef\xfbj\x1c\xc7\x9d\xaekL\xae\x04\x19\xd7\x9e\xa1y}\xce+\x85E}\x10i\xa8\xe5\x96<\xaf\x91\x0a\x8b\xca\x169 \x19\xcf\x1f\x9a\xdf\xe3\xd0@\x9a\xaf\x19\xe99tJ\x9a\x90\x9c\xe0u\x1c\x03\xea\x02\xa9\xfd\x9a\x01\x96\xa1\x97g\xc0\x8a\xa44\x9a\xa6I\x87ex\x04J\xd05\xd7\x12\x8a?b\xa9NG\xe6G\xe7x\xce\x5cV\x85\x8e:r\xc4\xc9\x92\xe7\xb9\xa4>\x87\xd4\x11!2\xd2\x8c\x22\x86\x93\x86\xe2q\x82W\x95\xf8\xf5\xf8\x00\xa0\xbc\xef)_\xde\x1f\xb5\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00Y>\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x01X\x00\x00\x01O\x08\x06\x00\x00\x00\x08\xbf\xd6c\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00\x1e\xc2\x00\x00\x1e\xc2\x01n\xd0u>\x00\x00\x00\x07tIME\x07\xdf\x03\x09\x16+\x08u\x14\x9b\x99\x00\x00 \x00IDATx\xda\xec\x9dw\xb8\x5cE\xf9\xc7?\xef\xde\x92\xdc$\x84\x90\x84\xde\x12@\x04\x04\xe9M\x8a\x14\x15\xa9\x82 *\x22\x0a\xa8`\xe1'\x0a\x22X\xe9 X\xc0\x02\x82\x88\xa0\x14\x05\x95^\xa4\x83\xa8\x80 5\x02\xd2;\x81\xf4r\xfb\xdd\xf7\xf7\xc7\xcc\xb9w\xb3\xd9\xdd\xbb3{\xce\xee9\xbb\xf3}\x9e\xf3,\xe4\x9e:g\xe6{\xbe\xf3\xce[DU\x09\x08\xa8\x07D\xa4\x0bX\xb1\xcc\xb6\x92\xfd\x9d\x02\xe4\x00\xf5\xdcnP\xd5S\x1d\xef+\x07\xb4\xd9kO\x01&\xdb\xcd\x05\x8b\xed\x86\xaa\xfe+\xbc\xed\x00\x80\xf6\xd0\x04\x011\x91\xe7d`\x13\xe0=\x15\x08t\x99:\xdc\xca\x8c\xf06\x02\x02\xc1\x06d\x99L\xa7[2-\xdc\xd6H\xc9\xed\xe5\x1b\xd5,V\x05\xc7\xdd\xd696\xa7\x8dGh+\x18\xaf\xfdV)\xf7\x87\xde\x18\x086 \xbbD\xda\x09lP\x82L\x97M\xf1m\x07\x9bW@ \xd8\x80\xd4\x91\xa9\x00[\x01\xdb\x14\x10\xe9\xfb\x80\x8e\x8c=J\xa3\x086g\xb7\xf8\x94\xab9\xdf\x14\x9ed\x0a\x9d\xe4\xe8G\xec\x9f\xbb\xed>\xaf\x05%\x1b\x086 \xbd\xa4:\x09\xf8\x08\xb0'\xf0Q`\x85&x\xacZM\x04\x92\x12\x13A\xce~\xdc\x96\xa7\x9f\xb5\x8b\xfe6\xdf\xfe\xce,4\x19\x04\x04\x82\x0dh<\xa9nd\x09u\x0f`\xdb&\xec\x03\xc1D\x10\x10\x086\xa0n\x84:\x1e\xd8\xd5\x92\xea\xee\xc0\xeaM\xfe\xc8\x8d4\x11\xc49\x9e\xa6`\x19zz \xd8\x80\xfa\x91\xea{\xacB\xdd\x13\xd8\x11\x18\xd3B\x8f\x9f\x0f= \x10l@\xdc\xa4\xba\x1ep8\xb0/\xb0N\x0b7E#M\x04\x12\xe3\xb9\x96\xc5\xb8\xbeI\x19\xb5L\x8b}8\x03\xc1\x06\xd4\x9dT;\x81\x8f\x03G\x00;\x85\x16\x09\x0a6 \x10l@\xed\xc4\xba6\xf0%\xe0P`\xf9&z\xb4A\xe0]`\x81Ui2\xcao\xa9\x7f\xeb\xaeAyJ\x02J4q\xc5l]\xba\x00TC\xec{ \xd8\x00/Rm\x07>\x06\x1c\x89Y\xb4\xca\x0a\x09\xf4c\xdc\x89Jm\xef\x14\xfd\xff\xec\x06\x12D\xd6\xc8\xb5\xd0T\x90+P\xef\x81`\x03\xc1\x068\x10\xeb4\xe0\x8b\xc0a\x98U\xe5\xb4N\xcb\xff\x07<\x0a\xad\xf5\xceE\x10\xd0\xaa\x04[@\xaeG\xd4x\xaa9\xc0GU\xf5\x9c\xd0M\x02\xc1zb\x0d\xe0x\xe0I\x11yLD\x8e\x15\x91UC\x13\x07\x82\xcd2\xb9\xfe:\x06r\x9d\x01l\xa5\xaaw\x84.\xd2R\x18\x07\xac\x9f\xd0\xb97\xc6dU{UD\xee\x14\x91\x83l\x82\xa1b\xb4\xd9\xadZ7\xadz\xa8K\x97H\xae\xe1\xb1\x18U\xd7\x0d\x04\xdb\x5c\xe4\xfa\xa5\x1aOu\x1d\xb0m(\xdf\xd2\xb2\xea\xb5\xbd\x0ecn\x17\xe0r\xe09\x119RD\xc6\x84\xa6\x0f\x04\x9bvr\xbd\xa0FrU\xe0\x14\x8c\xa7@\xa8\x88\xd9Z\x88\x94\xd66u\xbe\xeet\x8c9\xeb%k>\x98@:m\x9d>J\xd9%\xafm \xd8\x94\x93\xeb\x85\xc0\x17k8\xcdb\xe0@U\xfd\x81\xc7\xaao@\xf3\x90\xec6\x0d\xba\xf6\xca\x91\xf9\x00\xf8\x0a\xb0\x9c\xa7\x89 I\x9e\xc89^'\x10l\xd6\x1b\xa0\x80\x5c\xbfP\xc3i^\x05>\xa0\xaa\x7f\x0e\x1c\xd3\xf2x\x12x\xb9\x81\xd7_\xce\x0a\x85\x9b,\xd1N\xf6P\x98A\xc1\xa6\x89\xa3\xb2*\xd8,\xb9\xfe\x068\xbc\x86\xd3\xbc\x0c\xec\xa4\xaa\xaf\x04ni\xe1A \x12\x959\x99\x0a,\x0f\xaci\xd5\xec\x86v[\xbdA\xb76\x08\xdc\x85\xa9\x0f\xf7v\x89\xbf\xf7\xda\xdfg\xec\xef@\xf2\x09\xa6\xdb0\xd9\xbdv\x04~[p\x1f\x85SxH\xd7\xc2\xd8X\xfb\xdb]0n\xa3-\x87YTV\xabr\x9b6\xd5g\xe6L\x04\x22R\xeb\x82\xd6\x8b\xd6,\xf0Z\xe0\x8d\x802&\x82\x89v\xab\x06\x9d\xc0\x96\x96\xfc\xea\xe1\xe6\x05&\xef\xc1\x85\xb6/\x03<[`\x22\x98\x97\x12\x13\xc1\xdf-y\xceie\x82m\xcf\xd8@8\xb6Fr}\xc1*\xd7@\xae\x01q\xa1\xdf\x9a\x12\xee\xb5JvG\x84-\xd1\xe1D0I`\x13\xe0\xe7\xc0_\x80+R\xda.]v\xccv\x15|\x88\xc6\x14l\x83\xc0\x80\xddg\x96%\xda\xbeQ\xc6\xff\x11\xc0\x0bY\x0a\x00\xca\xcc*\x9f\x88|\x14\xf8Q\x0d\xa7x>(\xd7\x80:\x98\x12nD\xd9\x15c\xab\xfd\x01\x90TB\xf6v\xe0\x93\x98\xb5\x88\x8f\x92\xbe\x1c\x01Qm\xae.\xbb-kg\x08\xab\x00\xd3\x80\xd50\xeei+\x17\x90n\xd9\x19\x86\x88\x9c\x8f\x09$\xfa\x93\x88\xac\x1d\x086^r}/\xa6\xac\xb6\xef\xfdF\xe4\xfaz\xe0\x80\x80:\xe1%U=EU\xdf\x8bIk\xf8GL\xf2\xa0\xb8\xb1\x02&X\xe1w\xc0Z\xcd\xd6\x88\x22\xb2<&qN\x94\xb8i2p\x9d\x0d\xcaH\xff\xfd\xa7\xdd\x06+\x22\x930\xb9X\xd7\xf5<\xc5s\xd6,\xf0F\x86:\xd5\xa4$li\x01\xe5\x15\x92\x9d\xc2F6\xd8e\xa8\xde\x06\x1ba\xd0nX\x93\x01\xaa\xba\xa8\xe8:k\x03\xdf\x06>g\xaf\x177\xfa\x80S\x81\xd3\xe3\xac\xb6\xe1i\x83\x1d\xb0\xbfs\xec\xefxL\x1e\xddr\x02\x08\xac\xbbYd\x93\x15\x91M0\xa1\xebk\x948\xe6:L\xd4e\xaa\x09,\x97\xf2\x8e\xdf\x06\x5cY\x03\xb9\xfe\xcf*\xd7722\xd0\xdbE\xe4t\xe0\xd9\x90u\xa9\xf9\xa0\xaa/\xa8\xea\x970!\xb2?\xc1TF\x88\x13c0\xe1\xdew\x88\xc8J\x19\xff\xe8\x1dh?Tk\x94\xd9\xe5c\xc0\x89\xc1DP\x1b~\x84\xb1/\xf9\xe0YK\xaeof\xa4C\xad\x06\xdc\x0d\x9c`\xa7}W\x96\xc9\xb6\x14\x90}\xa2}SU\x8f\xc5\x044\x9cX\xa0\xf2\xe2\xc2\xce\xc0\xe3\x22\xf2\xe1\x06sK\xce*\xd7\xf1\x15\x15{'9:\x87Cq\xdbD\xe44\x8c\xcf\xefh\xeeo\xdf\x17\x91\xfd\x03\xc1\xfa\x11\xce!\xc015\x90\xeb\xceY)\xef\x22\x22{`\x5co\xb6/\xf8\xe7\x1d\x80\x93\x03\x1deKx\xe1\x10N\xaa\xaasT\xf5$\xab\xd2\x8e\xc1\xd4|\x8b\x0b+\x00\xb7\x8a\xc8\xa9\x0d\xfaPGi\x17'\xd8\xad<\xc1*9\x94\x1c0\x09\xb8\x06\xf8\x8eC{_*\x22\x1b\x05\x82u#\x9c\xad1~~>\x88\xfc\x5cSO\xae\xd6$p\x16p#0\xa5\xc4.'4X\x85\x04\xd4F\xb4\xd5*\xda\xc5\xaa\xfaSk:\xb8\x00\xe8\x89q|\x7f\x17\xb8+\x03&'\x01n\x06\xf6p\xc7\x01\x9f\xf1<\xfc*U=-\xe5\xe4:\xd6N\xff\xbe\xe9\xfb\x01\x11\x91]\x02\x07\xb6\x1c\x860&\xb3\xaf2\x92\xdd\xabV|\x00\xf8\xa7\x88\xac\x9f\xd2g\xfe<\xf0\xb8\xe7\xb1\xe7\x89\xc8\xb6\xa9\x19\xf7i\xf0\xd3\xb5\xab\xe87x\x12\xfe\xa3\xc0\xf6i\xce;i\x83%n`I/\x01\x1f\xbc\x0dl\xa2\xaa3\x03\xef\xc4\xfa~\xa2@\x83\xe5\x19\x094X\xc6\xf14\xc3\xc9K\x80Y\xf6w\xb6\xfd\x8d\x02\x10\xfa\xed\xd6\x8bY\xc8\x1a*\x0e\x08(X\xd4\x8c\x12\xd0\x14ccLi\xa48\x16\xad\xe6\x00{\xa9\xea\xbf\xca)X\xfb\x9f\x91\x1f\xfaz\x09\xbd\x82(\xab\xdd\xd3\x00\xaa\xda+\x22\xd3\x80\x7f[\xd3\x86\xcf8\xd9\x22\x0d\xfe\xef\xb9\x14t\xee\x95\x81\xcb<\xef\xe5\x1d`\xdf\x94\x93\xeb\xaa\x98\xccB\xdb\xc7p\xba\x97\xc8h\x0e\xdf\x161/D\xbe\x9fS\xed\xb6f\xd1\xb6\x8a%\xf0\x09\x96<}\xdc\xa7\x1e\x07\xben?\xd8\xb5b2&(a\xcf*L&\x92p\xdb-q\x1dU}\x19\xf8D\xc1\xc7\xc9\x05+\x01\xd7\x8aHg\xa3;E\x1aL\x04\xbf\xc6\x94\xcapE?\xb0\x7f\x9as\xba\x8a\xc8z\x98\xdc\x9d\x1b\xd6x*\xc5,z\xec\x98\xa5\x90\xdf\x80\xc40\x88\xc9\x0b{*\xc6s\xa6\x16\x8c\xb3d\xf4\xb9\xb4=\xa4\xaa\xdec?&>\xd8\x02\xe3\xa2\xd6\xba\x04+\x22\x9f\x01\xf6\xf1<\xfc\xab\xaaz\x7f\x8a\xc9uk\xe0~\xca\x87\xfaU\x8bY\xc0\x9e\xaa\xfamU\x1d$\xa0.\x0a*\x86q\x95c$\xa3T\x94_ve`\x9d\x025;\xd1.z\xf9\x8e\xc3\x7f\x03\xdf\xc0\xdf^\x19\xa1\x1d\xb8\xc4\xae\x83Tj\x9f\xa4\x10-\x0e.U\xc7KU\xcf\xc3d\x0c\xf3\xc1\x09\x22\xf2\xfe\x98\xc6s\xcen]v\x1b[\xb0\x80Yv\xe12\xd7@\x02Z\x11\x93\xd3\xd2\x07\xbfT\xd5\x8bRL\xae\xbb\x03wR:x\xc0\x05\xf7al\xae\xb7\x04\x0e\x0c(\x83w\x81C\x80\x8b\xf1\xf3!-\xc4\x8fD\xe4\xa7\x0e^\x0e\xf5\xc2\xd7\xf0[\xe0\xeb\x00~\xd7H\xb7\xb4F*\xd8\xf3qsU\x8ap\x97\xfdj\xa7\x95\x5c?\x8bq\xc3\x1a_\xc3i\xf2\xc0i\xc0.\xc1$\xd0\x94h\xc3,\xaa\xb5G\xea\xad\xc0%\xca\xb7\xbf\xfc\x0c\xe3\x853\xab\xc6{\xfb\x06\xf0\x07\xbb\xf0W\xef\x19D9SA?\xb0?~>\xc1\x9b\x01\xc7\xd5\xaa\x5c-WMf\xc4\x9e>\xcdn+ar\xdd\x8eM\x0d\xc1\x8a\xc8\xa705\x86\x5c\xf1\x22p`Z\xa7\xca\xb6\xe2\xc2\xa5\xd4\xb6\x105\x13\xd8MU\xbf\xd7\xcc\xa54\x02\xc1\xd2\xc9\xc8BW\xc9\xe9\xb1\x87i\xe3NL\xb5\x83\xbf\xd7x\x7f\x9f\xc1,\xa2M\xa0A\x8b\x5c%Hv&\xb0/~a\xc4?\xa8\xc1%-z?\x11\xc1F\xc4\x1a\x91\xec\x8a\x96`\xc7\x94\xfaH\xe6\x1a@B+\x00\xbf\xf08t\x11\xf01U\x9d\x9dRr=\x198\xbb\xc6\x8ex\x8f5\x09\xdc\x118(\xc0\x07v\xc6\xb33\xc6a\xbf\x16\xec\x86\x89\xaa\xecL\xd1\xb3\xfd\x07\xbfJ\xd2c\xac\xa9\xa0\xee|\xd7\x08\x05\xfb+\xfc|\xdb\x8eR\xd5\xa7RJ\xaeGa\xc2\x1ak\xc1\x9f\xacr};\xd0D\xd3\xa3\x13cB*,\xa9\x12\xf9\xbd\xd6\xac\x16UuHU\xbf\x83\xa9\x02PK\xe2\xed\x9d\x81\xdf\xd3I\x1b\x9d\x89*\xd8\x5c\xd1V\xe9\xd9\xae\x04\xce\xf5\xb8\xc6\xd68\x98\x16\x0b\xd4h\xe4\x13\xbd\xbe\xdd\xa2\xf26c\xed6\xc1*\xd8\xf1\x94(}\x93\xab3\x11}\x028\xc0\xe3\xd0[T\xf5\x92\x94\x92\xeb'\x81sj<\xcd/\x81\x83\xac\xad) .\xc5w\x01\xc6\x97\xb4\xaf\x86\xd3\x1c@?'\xa4\xec\xd1N`\xa4\x0a\x82\x0bN)\xa8\xce\xd0\x5c\x0aVD\xa6Z\xf5\xea\x8a\xf9\x98\xc8\x954\x92\xeb\xae\xc0\xefkl\xc7\xef\xa9\xeaQq\x96\xf8\x08\xa8\xed\xb5\x92|\x01\xc1\x8e\x22\xf5\xda\x85\xb1\xdb\xb7\x13\xb3\xcdSU\xffj\xa7\xfb\xf3k8\xcd\xa7\xe9\x1f\xae\x89\x95\x9c\x82\x9dd\xb7\xd1\x9f\xa9\x078\x0cw\xaf\x89.\xe0\xb7\xa3-&\x16\xe5_\x18\xad\x84{\x8e\x91\xc5\xca\xa5\x14x=\x15\xec/1Q,\xae8&\x8d\xc5\x0aEd3L\x8c\xb8\xaf\x8dj\x08\xf8b\xdas(\x044\x85\x92\xbd\x17\x93\xb2\xb0\x96\x1c\xc9_\x03>\x92\xa2g\xfa\xbb\xa7`\xdb\x01\x93\xd7\xa1y\x14\xac\x88|\x1cSb\xd8\x15\xb7\xa9\xeaoSH\xae\xeb\x00\xb7\xe0\x1e\xaf\x1e\xa1\x178 \xcd\xbe\xbc-\xac^\xeb5\xee\xda\x0b\x94\x8f\xaf\x17\x81\x0b!=\x01l\x87)\x02\xea\x8b#1\xb6\xccdf\x0d\xf3\x00\xb7R\x9f\xc7c\xc2\xc7]q\xa6\xcduP\xcdL\xa6bI\xf1\xd1\xfaN\xe2\x04k3\x8d\x9f\xe7q\xe8B\xe0\x8b)$\xd7\x95\x80\xbfaR\xd2\xf9`>f1\xeb\xda\xc0g-I\xae\xd1\xb8\xeb( \xd9\xf6\xa4\x09\xd6\x92\xecK\x96d\x1f\xae\xe1\xbe\x8f\xc1\xad\xbal\xf5\x04\xebQ\x09\xc2\x93#\xc6cR\xa3Vs?\xd1bV5}g\xa9\xfb\xaf\x87\x82\xfd9\xc6W\xcc\x15\xc7\xa6-\xcf\x80\x88L\xb4\xca\xd5\xb7\xfe\xfc[\x98|\x02\xf7\x05>\x0bh\xd0\xd4\xfa]\x8cw\xc0\xed\x9e\xa7\xe8\xc4\xc4\xf8\xaf\x99\x92\xe7\xb9\x13\xbf\xf2R\xbb\x8aH\xe2\x02.\xd1t\x85\xb6D\x85O\x98\xe7\x1d\xaa\x9a\xaaZT\x222\xc6>\xcb\xce\x9e\xa7x\x11\xd8\xd5f\x09\x0aX\xb2m\xc71\x92*pj\xd1\xef\x98\x83I\xde\xfdn\x0c\xed\x10\x19\x06\x9e\xb2\xa49\xdb\xe1\xdd\xb5a\x16\xa1f\xe0^\x09e\x01\xf0\xbe\xe25\x1e\xeb/\x1be:\x8b\x0a\x00\x94[?Zd\xb7w\xa2\xb6P\xd5\xe1r\xec\xed\x09vZ\xc1\xd4\xd8q\xc5\x22\xe0\x0b)\xe4\x81\x0bk \xd7\x99\xc0GZ\x9d\x5cmL\xf8\xba\xc0\xfb\xed\xb6\x11&\xbf\xe9\xea\x15\x0e{\x16\x93I, ^\xe57`#*o\xc3,\xfc\xb8b2p\x12p,\xd0\xe8t\xa1\x0b0\x95h\x7f\xe7x\xdcD\xcbQ\x9fI\xea\xc6\x92L\x82\xf0i;\x88\x5cq\x9c\xaa\xbe\x922b8\x14\x93P\xc3\x07\x0b\x81\xddU\xf5\x85\x16$\xd4\x951.B;Y\x22\x8d\x1c\xb5]\xb0B\xb37S\xd1o=I\xb6WD\xf6\xc1$\x15\xf2)}\xbd\x0afE\xfe\xec\x06\xb6A\xa44\x9f\xc0\x84\xf7\xee\xedx\xfc\xa7D\xe4\x8c\xa2 \xa6B[jG\x95\xf7_\xd2~\xdc\x9e\xd0\xc0\xea\xc0\xafx\xd9\xdd\x98\xfc\xb0i\x22\x89\x0d0.f>\xe8\xc3$\x04\x7f\xb4E\x08\xb5\x03\xb3\x88\xf2Q\xbbm\x1c\xc3i\x97\x13\x91\x0eU\x1d\xa83\xe1\xd5\xebZ\xd1\xf5\x1a\x92\x17DU\xe7YS\xde?\xf1\xb3\xabng\xa7\xf6\xb7\xc4\xd0\xe6>m\x1fq\xd8r\x98\x1c\xb9\x1f\xc0-\x8b]\x0e\x93Ww\xdf2\xf7\xd4Y\xe5;,I\xb0I\xbd\xd4\xc3\x81\xb5\x1d\x8fY\x0c\x1c\xaei\xa8a3B\x18\xe30v\xaaq\x1e\x87\xe7\x81\x83U\xf5\xae&'\xd5\x89\x22r\x98\x88\x5c\x8b)\x91r7\xc66\xb7q\x8c\x97Y\x9e\x80$I\xf6M;\xd3\xf0\xcd\xc4u\x18\xfe\x0b\xbfqb\x91\xa7\x18\xfa\x98\x88l\x95\xc4\x0d\xe5\x12\x18p]\xf8U@=\xde\xba\x91\xa4\x09\xbf\xc4\xdf%\xe5(U\xfds\xb3\x0eJ\x11\xd9VD.\x06\xde\xb4\xca\xe1c\xf8\xfb\x05\xb7\xaa\x99\xa0\xb0\xccL\x22\x91\x5c\x0e$\xfb,\xb0\xa7\x15:\xae\xe8\xc0\xd8b\xbbj\xe0\xa1\xaar\x11\x14\xce\x96\xec\x8ci\x9a\xdd\xa2\x85\xc9G\x81{=\xee\xa1\x5c\xc0\xcf\xb8Q\x04V\xdd#\xb9\xfe\x0f\xf7\xd5\xbc\xfb\xf1\x8b\xcaH\x92@>\x0b\x1c\xeay\xf8\xc96\x13{\xb3\x91\xea\x14\x119ZD\x9e\xb2S\xcaC\xa9-\xefm\xb5X\x91\x80z\x90\xecC\x98\xbc\xab>\xe6\x98U\x80\xaf\xa4\xe4Q.\xc25d\x01>$\x22;\xc7}#\xb9\x98\x07\xe0$;=t\xc5q)3\x0d\xac\x87I\x08\xee\x83\x0bT\xf5\x87MF\xac[\x8b\xc8\x95\xc0\x1b\x98\xc4\xce\xef\xab\xf3-$\xad`\x1b\xb6\xd0T\xa0V\x13\x0f4\xa8\x92d\xfff?\x9c>\xe3q\x07\xfc\xc2i}\x14\xfb\xcav\x8b\xf2\xb4\x16b!&G\x88+N/\xba\x9fj\xee\xa9\x1d\x13\x88\xd0e\x7f\xc7\x16\xe6\x85\x8d\xfb\x85~\x0b\xf7\x02\x86\xd7\x97+\x1b\xdc 2\xe9\x02\xae\xf2Tf7\xa4\xe8+\x1eG[l!\x227\x01\x0f\x00\x9f\xc2\xdd\x03 K&\x82F\x92kj\x08\xd6\x92\xec\xe5v\xca\xef\x83/\xd8)\xbbo;T\xfb\x1eV\xb1\xdb\x14J/j\xdd\x03\xb8\xe60\xd9FD\xf6v$\xfd\x88`#\x92\xed*|\x97\xb9\x18\x07\xe3\x8a\xb8W\x80\xcc\x93\x82\xca\x8fE\xf89~.+/\x03\x874CV,\x11\xd9TD\xae\xc7\x14\xd5\xdb#\x05\xb7To\x1bl#<\x09RC\xb0\x96d\x7f\x0a\x5c\xe1qh\xa7\x15Zc=\xda\xa0\x22\xa9\x89H\xa7\x88,#\x22\xcbX\x014~\x14n\xb9\xd2\xe3\xfeO\xb5\xa4\x19\xe5\xe7\xad\xf6\xfe'`LY+\x02\xab\x02\xab\x8a\xc8\xd48_\xe8\xf7=T\xdf\x15iJ\xa2-\x22\x07\xe1\x17\xe40\x00|RU\xe7\x91a\x88\xc8\xfbE\xe4\xaf\xc0#\xb8\xfb\x136\x0b\xc16\x82\x5c\x0b\x09\xb6\xee\x0b\x5c\x15\xf0uL\xa0\x87+V\x05\xbe\x9c\x80\x82\x1d\xc3H\x02\xecj\xa2\xed\xfe\x89{2\x98\xf7\x03\x07:\x10\xac\x94 \xd8\xd5\xec\x16\x0f\xc1\x8a\xc8t\xdcs\xb6\x0e\xe0\xe7m\x90\x14\xb9\xac\x86\xbf\x0f\xeeqv\x81 \xab\xc4\xba\x82\x88\x5c\x06<\x86\xa9\x95\xd6\xe8\x01\xde\x8f\xb1\xf7>\x86\x89\x99\x9fA@#\xb0\x188\x18\xbf\x84\xdd\x1f$\x99\xcc[NB\x1c\xb8\xdcS,\xc6\x12#\x10W\xa0\xc1I\x0er:\xc2\x85)s\xcb\xfa)~nF\xd7\xaa\xea9Y\x1dA\x22r\x88}\xf6)u\xbet\xaf%\xce\xc7\x80\xc71\x918o\x02\xef\xa8\xea|Z\x03R`\x16\xe8H\xe9\xfd\xfd\x17\xf81~\xa6\xbc/\xdaw\xdb[\xe5\xaca\xb4\x0f\xfb2\x18\xbb\xab\x0b\x1e\xb6*\xfc\xbd\x0e\xc7\xac\x03|\x1eS\xc6\xc9\x07\x91\xdf\xf6\xf2\xed1\x0c\xd0\x0dq\x8f\xe5\xed\xb6\xb6\x8e\xb4\x90\xcc\x870\xa55\x5c\xf12\xfe\xae\x5c\x8d~\xe6i\xc0\x05\xd4/\x89\xf23\x984\x8f\x0f\xd9A\xf7lZ\xab\x03\x07,\x85k0IhvsD\xc8\x9c\xdd\xd5\xd6\x86\xff\x975\x09$A\xae\xb31\x05 w\x04\xa6\xa8\xea\xc7U\xf5\xc2\x8c\x91k\xbd\x89\xad\x5c$WZ\xda$\xdaz0I]\x5c\x83\x10\xda\x80#\x1c\xae#U\x10\xac\x0f\xc9\xfa\xd8b\xbf\x8c\x9b7D\xbc\x04\x8b\xdbJa\x84\xb3\xd2\xa2\xfaDd\x0d\xfc\xcamg\xce\xee*\x22\x07\xda\xafq\xdc1\xd7\x8a\xc9?p\x10\xb0\xaa\xaa~CU\xff\x1e\xa6\xffM\x89\x970a\xd1\xae\xd8\x08\xbf\x94\x88q\xe2\x19\xe0\xef\x8e\xc7L\xf60\x8b\xc4C\xb0\xd6\x17\xed`\xc7\xc3\xde\xc2\xf8\x99\xa6\x05?\xc3=\x91\xcb\x1bd\xc8\xee*\x22m\x22r6\xc6`\x1f\xa7j\x9d\x09\x9c\x09\xac\xab\xaa\xbb\xa8\xea\x95\xaa\xdaG\x80\xeb\xf8\x8b\x94k\x94\xec\xbb\xb0FW\x9a\xd4}\xa4,o\x05\x1e\xf48\xc7a\x15\xc6Z\xb5\xb9\x08\xda\xa8\xbeFV)\x9c\x8f{\x84Zc\x08\xd6\x92\xabk\xd6\xf7ST\xb5;%\xc4\xb3\x1b\xf0q\x8fC\xbf\x91\x15\xbb\xab-\x95~\x1b\xfeQ9\xe5\x88\xf5\x9b\xc0tU=AU\x9f'\xa0\xd5p\x11\xa3{\x06\x14c9L\x8e\xe8F\xe2i\x8c=\xd8\x05\xab\x00\x1b6\x82`]\xcd\x03/R\xb9\xd0X=\x89g\x0c\xf0\x0b\x8fCoW\xd5\xab3B\xae\x9bc\x02\x06v\x89\xe9\x94\xefZ\xa2^KU\x7ffk\xd3\x07\xd4\xae`\xebRU6f\xbc\x8b\x09'w\xc5\x9e\x94\x0e\xa3\xad\xd6\x06[\xd8^\xbeJ\xfc|L\x94W]Tl\xces\xf0n\x87{8\xe9O\xeb\x984y4|\x0bx\x8f\xe31}\xd4\xb1\x9ez\x8d\xe4\xfayL\x86\xb25b8\xdd,L\x02\x9f\xe9\xaa\xfa\x93\xb4\xcc@\x9a\xd0D\x90F\x82\xadD|\xd7\xe1\x1e\xeb\x9f\xc3\x94\xfe\x962&\x82\xd1\x08Vb \xd8\x97p\xf7o\xdd\x16S^\xa6n\x0a\xd6U\xbd.\x06\xfe\x90\x12\xf2\x99\x86\xa9\xdf\xe3\x8a\xb3T\xf5\xb9\xb4\x8fX\x11\x89j\x13\x8d\xad\xf1Tj\xbf\xf6k\xab\xeaY\xb6Dr@@\x84!\x8c\x1f\xb5+\xd6\xc3x\x994\x12\xa7\xe3f\x8bm\x07v\xad\x0b\xc1Z\xbb\xde\x01\x8e\x87]\xae\xaa\x0bR\xd21N\xc2=1\xf0K\x8c\xa42K3\xb9\x9eH\xf9\xc4\xc1.x\x16S^\xfc+)zo\xf5Vl\xf5@;#\x19\x98\xda\x0a\xb64\xbai\x95\xba\xa7'q_\x99\x07\x13\xd4\xd3\x88g\x8c\x94\xf2\x8b\xb8\xdbb?\xecs\xcf>\x0a\xf60\xdcW\xf1RQgKD\xd6\xc4\xb8\x13\xb9\xe2(U\xedM53\x88\x9c\x06\xd4\x9a\x87v\xc0\x12\xf4\xc6\xaaz\x7f\x10iuG\x07p\x02p\x14\xe9\x0b4(\x87\x8bq\xaf*\xbb\x1a\xa6vV#\xe1\xean\xb6\x0a\x1eY\xf6r\x8e\x83X\x18\xddi\xb8\x18\x0f\xa6\xa8\xe8\xdf\xb7p\x8f^\xbbVUoJ9\xb9\x9e\xedi\xf6(\xc4\xc3\xc0\x16\xaa\xfa\xbd\xe0nU7t2\x92vo\x9c\xfd@n\x81Y\x98<2\x85\xca\xbe\x14\xe6\xe2\x97\x16\xb0\x11*\xb6\xd0\xd6{3\xe0\x1aM\xba\x9b\xcf\x05]/\xe0Z\xdc\xec\xfcT\xf4\x10\x91\x15\xac\xfavA7\xee9n\xeb\xfd\x5c\xe7P\xbb\x1b\xd6\xd9\xc06\xaa\xfaD\xe0\xbc\x86`,&\xb3\xdc&Ec\xed\x0b\x19\xb9\xff\x9b0y9\x5c0\xcd~L\x1a\x85A\xab\xbe]\xb0\x0d\xb0l\x92\x04\xeb\xba\xb85\x07\xff\x8c4q\xe3h\xdcm\xaf'\xab\xea\xab)%V\x11\x91\xf3j\xfc\x00,\x06>\xa5\xaa\xc7\xa9\xeaP\x0b\x13\x5c#\xa7\xe3]V\xb9\x96\xf2\xb5\xdc\x0b\xf8\x5c\xbd\x9f\xa3\xa0\xe4\xc9\x98\xa2\xad\x1c\xf2\x183\xa0\xab\x13\xff\x81um\xe9\x8e\x0e\xa1\xa3\xa3\xb0\x8d.r\xbc\xe76\x1c\x17\xbbr\x0e\x8d\xbe:\xc6\x8f\xcd\x05\x97\xa4\xc1v)\x22\x13q/\xe5\xf2\x1a&f?\xad8\x15\xbfP\xe5\x08/\x00\xdb\xaa\xea\x9f\x08h\x14\xc9\x8e\xc7,\xba\xae_a\x9f\xfd0\xe5z\xaa\x9a\xc2G\xb5\xa0j!\xd7\x82\xf3\x8d-\xda*\xe1\x19\x8ck\xa0\x0b\xdeS\xa4\xda\xebe\xea\x10@l\xba\xd4\xdb\x1c\xcf\xf1\x11\x97\xbe\xe2\xa2`\xbf\x84\x9b\xff\x99\xe2\xe7\xc6\x91\x04\xbe\xe2*\xed\x81\x9f\xa4\xc8o\xb7x\x10\x1cLm6\xd7[\x81-U\xf5\xc9\xc0\xab\x0d\xc3\x04L\x0a\xbdu\xab\xd8\xf7S\x96h\xd3\x8e\xabS\xafb\x97\xc6\x85\x8e\xfb\xaf\x04l\x1c+\xc1\xda\xfa\xe3\xae\xf6\xa0\xbbT\xf5\x7f) \xa3\xb1\xd6<\xe0\x82Y\xc0oRJ\xae\xdbR[D\xdc\xb9\xc0\x9e\xaa:7p\x5c\xc30\xd1\xce@\xd6v8\xe6\x10`\xcb\x04\xfbU\xce\x8e\xf3\xa9\xf6\xbe\xd6\x066\xb7[5\x0a\x16\xe0U\xdc\xf3\x14l\xc0H\x95b)\xb0PH\x19e\xed\x8f\x81\x81\x1c\x03\x03\xc5\x01\x0d\xd7\xe3\xbe\xd8Uu\x0e\xe5j\x15\xec\xde\x96\xb9]p~J:\xf3a\x98:9N$\x94\xc6\x88%\x9b\xfd\xeb\x1a\xfc\x93]\x9c\xa6\xaaG7Ca\xc6\x0c\xa3\xdd\x92\xeb4\x8f\xe9\xed7p\xcf\xc7\xda\x08\x15\xeb\x8aO4\xeafm\xd67\xd7\xc5\xae\xad\x81Iq\x12\xec\xfe\x8e7\xf0\x16&\x94\xae\xd1\x84\xd4\x8eq\xcdr\xc1B\xfc\x12p'\xfd,\x130e\xc1W\xf4<\xc5\xf7T\xf5{\x81\xdfF%\xb1\xa41\x08\xdc\xe81\x95\x06\xe3\xca\xf5\x1d\xca/\xd6:\xdba\x0b\xd4\xe28Lz\xbe)\x8cd\xf6\xf2\xc1\x0b\x80\xab[\xe6&\xd6T2\xc9n\xe3\x80q6\x19>\x22\xd2\x86\xf1\x11\xae-\x94\xb8\x1d\xa1\xbd\xe4;\xfe\x0dn\xf9\x09\xda\x80\x0f\xc5B\xb0\xf6\xe1vw|\x94\xdf\xa4$\x1f\xe8\xa7<\x94\xc2yi\xcb\x96e\xab\x10\x5c\x8e\xa9x\xe9\x83cT\xf54\x02\xd2B\xb2\xb7\xe1o\x82Z\x0d\xf8\xbf\x0a\xf7\xe9s\xff\xb9\x02\x82\x9dZ#\xc1\xfa\xaa\xd8\xfd1\x19\xb7\x96c\xc47\xb8\xc3\x92\x7fD\xb0m1\xbc\xdb\xa5\x22\xd3T\xf5eLqM\x17|\xa0\xda\x86\x1d\x0d\xdb\xd9\x87\xae\x16C\xa4\xc7~\xe9\xea\x1f\xda\x8b\xc9\x11\x9b6\x9c\x84{\xe5\x08\xacJ\xfa\x8a\xadq\x1f\x90.\xdc\x8c_\xf2j0\xc9G\xf6O\xf1\xb3\xfd\x17\xf7J\xc0[R\xff\xc2\x9b\x85p]\x90_\xcb~\x90j&\xd8\xbd\x1c/|\xa3\xaa\xbe\xde\xe87,\x22\x1b\xe1\xb0\xdagq\xb1\xaa\xceL\x99z\xdd\x16\x13>\xe9\x83/\xab\xea\xf9\x04\xa4\x157\x00\x97z\x1e\xfb\x19`\xb3\x22e\xe6\x83(\xa3\xd7TL5\xd5\xc91=\xdb\xd5\x1e\xf7\xb1\x07\xa6r\xec\xb6v\xdb\x1c\x13\x9e\xba\x1ef\xd1mrM\xcf:H\x8e\xc1\xb2Y\xbbn\xc0\x986]\xb0y\x1c\x04\xbbw\x1d\xa6\x07I\xe03\xce\xcdo\x22\x9a\xd2D\xae\xe3\xec\x00\xf4\x99\x1a\xfdDU/ \xed\xb8\x06\xbfzQ\x82I|>9\xa5\xcf\xf5\x18\xe0\x9a\x8c}\xe7F\xdd\xac5i^RW\x82\x15\x91\xb5\xed\xd7\xc3\xc5\x08\xac\xee\xf2\x01\xb3d\x96&\xf5\xba\x0b\xf05\x8fC\x9f\x00\x0e\x0a\xaeX\xde\x83\xafQa\xb3\x97\x00Oy\x1c\xb75K\xaehK\x8a\x9e\xff!\xdc}L\x0bU\xecXF\x16\xbe\x96\xb3\x84+1\x7f\xf1\xc5\x5c\xed\xaf\x8f\xa4\xe4\xa5\xb9\x12l\xc3\x15\xac\x88l\x82[\xec\xf8\x10\x10\xeaj\x05\xfc\xc9\xf6\x05\x17U\xb5a\x8a\xee\x7f.\xf0\x9a\xc3\xfe\x13JM\xbd\xeb\x04\x05^r\xd8\xbf\x13S3\xad,\xc1\xba\xda_\x1bN\xb0\x22\xb2\x02nI\xc1_MIbmW\xf5z\xbd\xaa\xbeF@b]\x89\xf4\xda_\x0b\xf1\x16\xee\xeb\x1e\x1f\x0bf\x02`\xc4U\xad\x9c\x92\x95\x82\xbfGx\xb1\x163A\xad\x0a\xf6\xe1\x14\xbc,W\xfbk\xc3\xcd\x03\x222\x19\xf7\x8a\x0b\xbf$ \xc0\xe0\xcf\xb8\xd9bw\xa1|\xa9\xef,\x10l#\x03\x0e^r\xdc\x7f\xadJ\x04\xeb\xb2\xc0\xd5O:\x16\xb82g\x1e\xc0\xe4\xabu\xe9\xf0\xffU\xd5\xbb\x02\xaf\x04X\xbc\xed8\xf6\xba<\x84H\x92x\x0a\x13xP-\xd6\xa5>A\x12\xa5<\x0c\x5c\x15\xec\x12\x04\xdb^\xa0\xaa\xa6\xe3\x16M\xf4\xa4\xaa\xf6\xa7\xe0ee\xd1\x83\xc0u\xca\xf6\xab\xc0)u5\x15d\x01w8*\xbb\x0f\x03\x7fK\xc9\xbd/\x06^\xa0|\xbd9\xc5\xf8\xcc>a\xb7\x19\xb8\xd9\x9d}\xdf\xad\x94\x10\x9d\xafa\xf2\xc3V[\xf1azI\x82%\x9b\xf6\xd7\xc9\x98U\xc6j\xf1\x86\xaa\xbe\xd0\xe0{\x9e\x00\xec\xeap\xc8\x02\xe0\xf7\x81\xf7\x02\x8a\xf0\x80%\xaa\xf1U\xee\xbf\x0e\xc6q?-\x99\xd7\x9e(\x22\xd87\x0b\x08\xf5I`aJ\xees\x08x\x85\xea\xfd\xec'\x14\xb6s!\xc1f\xd1\x83`\x1bG\xc5qO\x0a\xeey7\xdcR*^\x1ajl\x05\x94@?\xf0w\xdc\xfc\x5c7\xc5$\xf3N\x03\x1e\x06\xa6\x14\x90\xea\xac\x94\xcc^\xdaJ\xa8\xd8\x97p\x0bdZ+\x22\xd8\x5c\x0d\x0a6\x0d\x0b\x5c\xeb\xb4\x80y\xe0\xb2\xc0%\x01ep\xbb\xe3\xfe\x9b\xa6\xe8\xde\x9f\x06\xce\x01\xeeJ\x09\xb9V\x82\xb7\x1d\xd6\x97`\xfb\xf1+1\xdch\x82}\xb0\x917kCc\xf7t8\xe4ML)\xe1\x80\x80Rx\x01c\xab\xac\x16\x1b\xd3\xd8\x8cZiG\xb90ZWO\x82\xe9K\x10\xac\x88\xacn\xed\x06\xd5\xe2\x89\x94,p\xb9\x10\xac\x02\xcf7\xf8~\xb7\xc7m!\xf1\xfa\x90\xd4%`\x14\xfc\xd3a\xdfq\xc0{C\x93U$\xd8R\xfe\xb1/\xe1\xe6\x16\xb7\x94\x82\xcdj\x04\xd7{\x1c\xf6}KU\xbb\x1b|\xbf\xfb:\xee\x7f]\xe8\xf3\x01\xa3\xe0Q\xc7\xfd7\x0bM\xe6\x8c^\xdc\xea\xa3M\xc5\x96\xed\x89\x08vm\xc7\x0b6\xdc\xfej\xa7\xdb\xd3\x1c\x0ey.\x05/j'\x87}\x17\x92\x82Le-85\xccJDW\x84\xe715\xf1\xaa\xc5\xa6\xe1u\x8f\xaa`K\xbd\x7f/;lD\xb0\xae9\x17\xd3\x10`\xb0&\xd5\xfb\xa6\xd1h\xf3\x80\x88t\xe1V)\xe2\xd6\x14\xd5\x0b\x0bH/\x14\xb7\x82\x82k\x91\xbeD\xdcY\x80+\xc1\xaeRH\xb0\xab8\x1e\xfcF\x0a\x1e\xd8u\x81\xab\xd1\xf6\xd7M\x1c?\x08\xd7\x87>\x1dP%\x5c\x16o\xc5c\xc6\xdaJ\x0a\xb6\x94\x9b\x96\x0f\xc1.K\xc1\x80w!\xd8\x87h;\x92/<&G.T|\x18\x81\xab{\xdf\xb2MD\xb0\xfd\x05\xfd\xaf\x8d\xda\xbc\x08\xa4\xccuz0\xc9\xb7\xc7\xba\x98\x08\x22\x05\xfb\x8e\xdd\x9e\xacp\xf11\x94/-#u\x1c\xc8\xae$?\xae\x22)/\x06:\x11:\x0b^S|p\xbd\xd7I\xf6\xe3Vk\x9b\xd6\xfb\xbdH\x99\xdf\xe1\x0e\xfa2s'\x83.\x13\x11\xe8\x08\x91\xb2\xc4\xff\xb7\x91\xcb\x9b_\xc9\xb7\x91\xcbw\x90\x1b\xea\xa0m\xa8\x93\xb6\xa1<9\x01\x18K[R\x09\x87\xb2\x9a\x8b\x00`\xae\xe3q\xcb4\xd1\xc7%2]\xb6\x01\x1d\xa3(X\xf1$X0v\xeej\xd7T&\xb9L]\xd5\xb2w\x1a\xd0\xe5\xb8\x7f#\xef\xdb5=\xdfP\x10b\x01\x9ep\xfd\xe8\x04\xf5\xef\x0e\x97yn{;\xeei\xff\xd2\x00W\xdf\xdd\x15FU&\xc9\x95p\x5c\xcd\xe3\xd9\x9a\xb2\xe3\xbf\xcd\xa2\xc9\x13\x19\xe3\xed\x7f\x99\xb3\x8a\x16`E&\xcc\x05X\x95\x89\xf3\x00:i\xab\xe5\xc3\x94U\xf7\xacbE\xe6Z1\xb6\x19\xf2\xe2\xe6\x8bDT\x0e\xb3\xd05\xaa\x82\x15\x11\x01((,ZI\xc1v{|\xc44\x97\xd1F\xedt\xdc\xbf\x91\xa5W\x82\x82\x0dHk_\x0b\x0a\xd6\xffcVU\xfb\xb6{L+\xd2\x80!\x8f\xfd\x1b\xe5^\xe6\xaa\x12\xfa\xeat\xafu\x1f\x5c\x1d\xe4\x06\xdb\xc8\x0d-}#\xbaD\xe7\xcd\x97\xf9\xf0+\xc8\x10\xf9\x1c\xc0B\xfa\xba\x00z\x18X\x1c\x83\x82m\x96A\xdf\x96\xf6>Ptm\xad\xf1\x03Q\xf8\x0c\x83\x05c}\xb4PY\xf1\x98\xc9\xf8\xb4\x95\xb6\xd7\xa8\x98\x1a\xf5\x82\x16:\xee?\xb6\x81\x1f\x12W\x82\xedo\x12\x82]\xea\xfc\xed\xe4\x86\xda\x0b\x08VQ)l \x1d\x1e`Z\xe6\x84*j;\xff\x22\xfa\xbb\xcc\xbcm\xa0\x13`\x19:\xfb\xcc\x08mI\xaf\x82,\x9a\x084\xe6>8T\xf0\x9b\xa7\xb6E\xaeX\x15l\x16\xe1\xeaP5\xae\x81\xf7\xfa\x16p&9r\xe4\x87_`\x94s\xb2\xd4\xb6\x88\x80\x80\xfa\x98\x08\xf2\xa1\xc9\x927\x11\xdc\x99\xc1\x87\x9c\x0e|\xc1a\xff\x17\x80\xbb\x1bx\xbf\xd7%\xdc\x953\xa1\xda\xf6c\x83\xa9\x1d\x05\x0a3_\xa6\xb3\xf61\xd0\x06\xb0\x80\xfe\xaeE\xf4\x8d]\xcc\xc0\xd8\xc5V\xb1\x8e\xc8\x95|\x1b@/\x83\x1d\xd6\xac \xd1YkT\xb1Y\x5c\xec\xca\x0dO\x12\xb2\xd3o\xfa\x19Y\x1b\x89\xd4\xe7\xe4\x1a\xfa\xfe@\x81\xa9\xa0Z\x13A\xb4\xd8\x15-tE\x0bd\xb9\x0a\xd7q\x22\xd8\xac.r\xb9FT\x84\xb8\xeb\x80V\xc0\x04\xc7\xfd\x83\x82\xad\x83\x82\xcd\xe2\xe2\x80\xabC\xf5D\xc2\xea|\xc3\xd1I[^\x0a\x94E\xaeHeD*t\x0c\x1dC\x00c\xc9\x0f\xe4Q\xe9'\xdf\x9eC\xf2\x0a\xa2\xc3JuX\xc9\xe6\x0a\xd5p\xae6U\x96u7\xad\x95\x1c\x8fkt\x14\x97\xc6\xa8\xa4\x0b\xcf\xa5\x9e\xefy\xf4E.A\x1c\xeeV\xdbU5s_1\x11q&\xd8,>g\xf3}\xfbO\xad\xd8\xf9\x8b\xc9\xb1\x8b\x8e\x01\x80n\xfa\xc7\x98(/E\x8b:\xff\x08\xc1\x0e/\x98I\xce?L2\xeb\xaaj\x05\x87c\x06=f\x82q\x93kR\x04;\xda9+\xbd\xeb\x5c\xc5\xbf\xbb\xddi\xbeUL\x04\x93\x02\xbb\x05\xb4\x00\x5c\x08v\x0e\xc1\x0f6\xf1YNV\xbd\x08\x02\xc1\xb6\x00\xda\xc9\xe5;i\x1b\xea\xa4}p,\xed\xfd\x03\x0c\xb5\xf73\x94[R\x86\x99\xc5\xae\x11\x05+R\xcf\x01\x94\x12\xe4<\x08vv\x0a\xee;\x22\xf8|\x8c\xe7\xaa\xc5D\x10\xe51\x88k\x91+\x1f\x16\xb9\x02\x02ZS\xc1\xce\x0a\xcd\x95\xf8\x078\x9b~\xb0\xaa\xda-\x22\x03\xf6k\x13\x14lS\xf7f\xd16$\xdfNn(R\xab\x85\xe8'\xdf\x0e#\xb6\xd8\xe2\xd4\x87-#\xf6\x0d\x5c2\xb7E\x04\xbb\x18\x13g\x1f\xb5\xdbx\xdc\x13\xda\xfb*\xce8l\xb0\x83^\xdd\xaa\xbc\x82\xed\x1cE!\xb7\x84\x9b\x96\xab\x8a]\x81\x80\x80\xe6'Y\x97\xc4B\xb3C\x93\x05\x05;Z\x07\xa9\xb6\x1a\xc3z\xa1_d\x139D#\x15+%\xd4\xe9H\xb8\xad\xc6\x91\xf76\xad\xf6W\x1d\xe5\xde\x04\x13|\xd3\xe1p\xce\xb71\xae\x8b\x0b\x81w\x19\x89.\x14F\xd2\x81\xe6\x12|\x9e\xc2\xb2.y\x0f\x95\x18\xed;T\xc3\xfb\x1e\xfe\x7f\x9bX\xab\x9a~\xe0\xd2&\xf9,+\xd8\xe7\x1d\xf6\x9d(\x22\xab\x10\x901rEM\x15\x83\x5c^\x10-\xe5~5\x84\xe6\x86\xe2\x99\x89\xa5\x99\x5cG\x9bF\xe7\x80u=\xc6O\xde\x12\xecLK\xb2s\xac\xb9`\xb4X\xfe\xb8\x9e\xa7x\xf3\xc1\x90\x07\xc9\xe6\x0a>&\xa3%\x8a/\x86K\x89\xad\xfe,\x13\xec\xd3\x8e\xfb\xaf\x1f(+\xa0\x89\xe1\x92\xd7y\xbe%\xd4\x007\x8c\xc7-\xd9\xff\x9c,\x9b\x08\xfe\xebA\xb0w\x86>\x92=\x08\x945\x11\xe4\x97\x0a4\xd08\xdc\xb4\xd2\xa4f#\x85\xd6Fe\xb7J\x17\x82}\xde\x9a\x08\xc0DEv\xdbswX\xf2\x8djN\xad\x98\xc0\xf3D\xea\xb8\x8f\x91$\xd6\xc5\xf9\x04\xaa\xcf\xf7\xdca\x17\xb9\x06\x9c\x14p\x94\xb0\xbfw\xf8,f[n\x14\xe5=\xc5\xf1Ygg\x99`\x83\x82\xcd\x1c\x06\xff\x09\x17;\xecJ\x22\x12\x02\x0e\x02\x9a\x11\xdb\xb8\xa8*\xdc\xb3\xd1\x05\x18Lu\xdc?\xf3\x04\x1b\xcc\x04\xad\xa1\x5c\xa3\xe9d\xa9)d\xb4@Q\xe8z\xe3\x8b4&\xdb\xcec\xa2\x95\xcaM\x9fW\x03Vu8\xdf3E\xd3\xf4%\x15\xdfX\x94\x0e\xf2t$\xea\xa6\x95\xa7\x83!r|\x916\xb6\xc3\xd8\x90\x07ps\xb9\xd2\x02\xf3\xc9P\x99\xbf\xc5}\xdf\xae\x0a\xf6\xdd\xf6\x8c\x0f@WO\x82\xf5\x80\x7f\x05\xde\x0ahQ\xf5\x0a\xf0`J>\x1b+\x93\xe7\xe8\x02\xb2\x7f\xda\x92\xff\xe3\xc0\x8b\xa4\xb3t\x92\x8b\x82\x1d\x04\xe6\xb7\x1a\xc1\xbe/\x8c\xc7L\xaaX*(\x12i\x81\xe7\xafd\x83u!\xd8!\xe0\x91\x0a\xed\xaa\xf4.\x11a\x95\xdc3\x0d\xb1I\xc1\xff\xe7\xec\xd8|\x1f\xb0\xbf\xbd\xa7W\x80\x19\x18\xaf\x93\x19\xc0\x82\x0a\x0a\xbf\xf8y\x0ag>q\xcdJ\xdc\x14\xac\xf0\x0ej\x13e\xb4\x90\x89`\xc7\xc0W\x996\x15h\x05\x82m\xd64\x85\x95\x9e}\x0a\xb0\x8e\xc3\xb9f0R0T+|\xc8\x92^\xe4\x82\xca\x0bs\x02L\xb3\xdb\x9e\xf6\xdf\xce\x06\xfeQ\x81`K\xb5\x97\xc6\xf8n\xd5I\xc1\xaa\xf1\x96\xc9\xb4\x0dVU\xdf\xc5-i\xc5fa\xa1+\xb3\xe4Z\x8f\x8a\x14i\xf5D(\xf7\xec\xdb;\x9e\xe7\xa1\xa2s\xe6\xcb\x10m>\xe1\xf6V`s\xc7c\x9e\xaf\xd06y\x96\xce\xce\x15\xf7\x87b<#\x01\x18\xd5\xe0-`A\xae\x09\x06\xe0?\x1c\xf6m\x03v\x0a\x9c\x95I\xf3@=\xfc3\xd3H\xae\xe5\x9e]\x80\x8f\xd6@\xb0\xe5\xce\x9bt[GSm\xd7\xcc_3G!\xd8r\x0a6\xae\xe7p\xf5 x\xb3Y\x08\xd65\xfcu\xd7\xc0[\x01M\x80M\x19\x09\xf9\xac\x06/\x03\xef\xa4\xe4\xde7v\xdc\xff\xa9\x14\xdc\xb3\xab\x07\xc1[\x90\xdd\x921\x81`[K\xc5\x96\x9b\xce\xb6\x02\xca\xb9\xa8\xedQ\x83z-4;h\x09\xf5\x97\xb49&N\x82\x1d*3\xdb\x89\xfaL\x5c3\x92\xe5\x1d\xf7\x7f\x0bx+\xf3\x0aVUg0\x92\xb8\xa2\x1a\xac\x9f\xc5\xd4\x85\x22\xd2I@\x80\xc1\x8a\xb8\xdb0\xefK\xd1\xfd\xbb\x12\xec\x8c\x14\xdc\xf3j\x8e\xfb\xbf\x05\x19_\xe4*\xc0]\x8e\xfb\xef\x92!b\xddSD\x1e\x01Nla\x05[M\xb8hM\xcd\x9c\x816(\xc4\xee\x8e\xf7\xfc\x04\xf0z\x09\x05[\xcd\x0a|\xdcX\xce\x91\xac\xe6P9\x7fE9;21?\x83k\xd2\xfe\xd7\xc9x>\xd8\xa66\x13\x88\xc8n\x22\xf2 p#\xc6\xa5\xe5(\x11Y\x9e\x16\x82\xaa\xd6s\x91KHg$W\xf1\xb3w\x02\x1fr<\xc7-U\x9c\xb7^m\xbdQ\xcc\xeaUK\x989H\xe0\xfe\xdf\xeb\xb0\xef\x00\xc6G?\x10l\x0a\x89u\x17\x11\xb9\x1f\xb8\x15\xd8\xaa\xe0O\x13\x80o\x87\xd9q\xcb\xe3\xc3\xb6/T\x8b\xd9\xa4%z\xcb\x8f`\xd3\xb0\xc05\x197/\x82\x19\xaa\xda\xd74&\x02U}\x05\x93!\xa8Z\xac.\x22\xeb\xa6\xe9\x19Dd\x07\x11\xb9\xc7~,\xb6+\xb3\xdbWDd\xa5\x16T\xb1\xd5\x98\x08\xd2\xaa@\xe3P\xb0C\xf6w\x1c\xf0I\xc7\xe3o\xa5\xf4\xa2U%?X\x9f2,\xd5\xc2\xd5v<\x9a\x82-\xbe\xd7\xc2\x05\xae8\xccJy`-\xc7c\xfec\xfbn>\xd7D\x1d\xd1U\xc5\xee\x93\x12b\xddFDn\xc7,B|p\x94\xdd\xbb\x80\xef\x04\x11\xd7\xb2\xf880\xd1a\xffA\xe0\xb6\x14\xdd\xff\xba\xb8\xb9\x96\xcdci\xdbq#\xb0\x8e\xe3\xfeQ82\xadL\xb0\x87\xa4\xe4\xbe\x0f\xc7\xcd\xa6\xf6%\x11Y\x8d\xd6\xc3hJ$N\x05+){\xee\xbc\x9d\xa2\xba\x8a\x82\xbfS\xbe\xbc})[k\xd2\x8b\x5c\x1ft\xdc\x7f\xc6(\xed2\xbcE(\xea+q=\x83+\xc1\xfe\xa7\x19\x09\xf6.\xc7\x06\xddHD6I\xc1}\x9f\x0a\xf4;\xec?\x068%\x90kY\x82\x1d\xae\xc1\xecA\xaai^\xe4:\x0c\x97ZU\x067Tq\xdej\x887\x0e\xe4p\x0f\xed}\xd2\xa1}(C\xaeq<\xc7\xda\x0e\xfb\x0ea2\x825\x17\xc1\xaa\xea,\x8c;\x8a\x0b>\x97\x82\xfb~\x05\xb8\xd8\xf1\xb0\xcf\x8b\xc8\xcea\xc6\xdc2X\x17\xd8\xcd\xf1\x98\x7f\x01\xcf\xa6\xe8\x196\x01\x96u\xd8?\x0f<\x90\x82\xfb\x9eB\xe5b\x88\xc5x\xba\xb0dQ\xae\xc9:\xe25\x8e\xfb\x1f$\x22i\x88f;\x0dSe\xd3\x05\x17\x88\xc8X\x02\x22D\xf5\xbbrV\xc4\xfa*\xd1\xb4)X\xc1\xd8\xdd]\xc6j\xbe\x8a\x8fv\xf1\x22Pqr\xf3\xb8\x17\xb9\x5c\xcd\x03\x8fcl\xb0\xd5(\xc6$\x17\xb9\xd6v\xdc\xff\x91b\xd9\xdeL\xf8\xbdc\x83\xae\xe0\xa1\x0c\x92P\xb1\xaf\x03\xbfq<\xec=\xc0\xf7\x03\xaf6=>\x8d\xc9;\xe0\x82\xbf\x01\xaf\xa6\xe8\x19\xc6\x00[;\x1esOJ\xee\xfd=\x8e\xfb\xff\xa7i\x09VU_\xc2\x18\xf6]\x90\x96\xc5\xae3\x18\xa9\xd3^-\xbe%\x22\x1b\x11P\xac`}Tb\x1a\xed\xaf\xd30\xb6W\x17\xf4Y\xa11\xda\xf3\x94Rx\xa5\x0a\x12\xc6\x81\xadpK\xf5\xd7G\xf5\xbe\xbb\x95\x22\xd2\x82\x82M\x00\x97:\xee\xbfO\x1ar\xc4\xaa\xea\x9b\xc0\x05\x8e\x87u\x00\xbf\x11\x91f|\x8f\xad\x8ev\xe0h\xfb\x8e]\xf0g\xdcr$\xd7\x03\xae\xe6\x81\x07=\xc4FRp\xf1 \xc8\x03\x8f5;\xc1^\x0dt;\xec?\x1680%\xf7~2\xe5\xf3^\x96\xc3\xd6\x84\x08\xaf\xb8Uh\xa5s\xd4+/\xed\xa7\xad\x82u\xc1\x02\xe0O\xd5~\xd3)\x1f\xc7\x1f\xa7\x82\x9d\xe8a\xe2\xb8\xd7E\x9b\x90\x5cN\xdb)\x80\x8b\xf8zVU\x1775\xc1\xaa\xeaB\xdc\x17\xbb\x0eI\xc9\xbd\xcf\x01\xfe\xcf\xe3\xd0SE\xe4#\x81`c\xa9H \x0e\x03;)\xac\x07\xec\xe7q\xdc\x95@O\x95\x1f\x9az\xb9im\x8f1\xddT\x8b\xb9\xc5*\xd0\xf19\xe2|/\xde\xfe\xaf\xcd\xac`}\xcc\x04\xdb\x89\xc8\xdai\xb8qU\xbd\x0a\x93\xe0\xc5\x059\xe0J\x11\x99\x1e\x84l\xe61\x01\xf8\x86\xc7\xd8|\xc1\xa3\xdf\xd4\xe3\xa3\xe7Zu\xe1v\x92\x0b\xd3u\xc5{\x1d\xf7\x7f\xa4U\x08\xf6N\xdcC\xec\xbe\x99\xa2\xfb\xff\x0a\xb0\xd0\xf1\x98\xc9\xc05\x222\xaeI\xdfi\xb1\xfbM1\x96p\xd3\xf2P\xb1\xd5\x9a\x18\x16\xdb\xad?\x01sA\x1bp<&\xdf\xab\x0b\xfa\x81sl\xbb\xe4\xaa|\xf6\xc8\xbd)o?\xec\xc5\xd5X\xe30\x11l\x05\xac\xe1x\xcc-N{/C\x9eeJ.r\xd5\xe2\xa65\x84\xc9\x88\xe5\x9a7\xa15\x14\xac\xaa\xe6\x81\xcb\x1c\x0f;LDVN\xc9\xfd\xbf\x06|\xd7\xe3\xd0\x8dqw\xf7\x0aH\x0f\x8e\x046\xf48\xeer\xe0\xb5\x14>\xcf\x01\x8e\xfb\xbfF:\xb2ga?rk:\xec\x9f\x07\x1em\x15\x05\xebc&\x18\x0b\x1c\x9b\xa2\xfb\xff\x15~\x91,\x07\x89\xc8\xd1M\xa8^GSWq,rUc\xc3\x1d\xb4[\xdc\x09\xc0?\x86IE\xe8\x8a\x19\xc0\xf5%\xda`\xb4\xb6\xa8d\x83\x8dC\xc1n\x8c\xbb\x0f\xe9-\x0emj\xee\x7f!y\x16\x96T\xb0\xd4\xf0~\x16\x01\xeb;\x1e\xf3\x80\xaa.h\x19\x82U\xd5gX\xba\x0e\xd1h8BD\xa6\xa6\xe4\xfe\xf3\xc0\x17\xedT\xc5\x15?\x16\x91O6\xdb+\xa5\xf2\x02L\xb5\xc4R-\xc9VC\xb0qa\x0b\xe0\xf3\x1e\xc7\xf5\x02?/j\x8fj\xdb \xe9\xaa\xb2\x07x\xbc\xdf\x9b=\xae\x9bD.\x82E\xb8\x07F\x94\xb4\x7f7\xbb\xff\xe4E\x8e\xfb\x8f\xc7,0\xa4\xe5#\xf1\x14\xc6\x17\xd2\x15m\xc0e\x22\xb2\x1f\x01i\xc7\x1a\xc01\x9e\x1f\x85\x8bqw\xeb\xab\x07\xde\x8b{b\xed\x1bRd\xe6\x18\x07l\xebq\xff-G\xb0\x97\x02o8\x1e\xf3\xb54\x04\x1e\x14\x90\xecy\xc0%\x1e\x87\xb6\x03\x7f\x14\x91=\x9bH\xbdVZ\xe4\xcaQ\xfd\x02\x8f\x0f\x06\xec\xd6g\xb7\xc1\x98\xce\xfb.\xa6\xbc\x88+\x1ea\xe9\x5c\xaf.&\x82\xa5\x16\x81\x8a\x16\xbajQ\xb0\x9f\xf08\xe6\x0c\xcfk\x95\xeb\x0f\xb5Drm\x8e[\x80\xc7\xcbV\x0c\xb5\x16\xc1\xaaj?\xf0c\xc7\xc3&\x02G\xa5\xecQ\xbeL\x09\x17\x90*\xd0\x09\xfcED>L@Z\xd1\x83I\xf6s\x83\xc3131^\x03i\xc44k\xf2p\xc1]\xc0\xbfS\xf4\x0c;\xc5a\x1eh\x05\x05\x0bp\xa1U\x09.8ZD&\xa4\xe5\x01T\xb5\x17\xd8\x1f\x98\xe5q\xf8\x18\xe0:\x11\xf9`\xc6\xdf\xe3h\x8b\x5c\x85\x0a6\x89\xbc\x02=v\xeb\xb7[\x9c\xbe\x9ay\xe0\xb7\xc0\xf9U\x9c\xb7\x1b\x93C\xb8\x94\x1b\x9f0\xe2\xaa6\x9a\x9a\xaf\xe4\xc6T\x8b\x82=\xc0\xe3\x98\xd3\xf1_\x5c+\x95\xf9\xab\x16\x15.\xc0\x0eq\x98\x07Z\x82`U\xb5\x1b\xf8\x99\xe3a\x93\xadjL\xd3s\xbc\x02|\xcas`w\x017\x8a\xc8n\x04\xa4\x19\x7f\x03N\xc2,\xb2\x94#\xc5\x1f\x93N\x97,\x80U(_O\xae\x1c\x1e\x04\xeeN\xd13\xbc\x17\xb7\xfc\xaf\x8b\xa8\x90\xf9\xabU\x92\x84\xfc\x8a\xearK\x16\xe2\x18\x11\xe9J\x19\xc9\xde\x89\x7fM\xae\x09\xc0M\x22\xf2\xd5\x8c+\xd8j\xbc\x08\x92@\xa1c~\xdc.Z\x85x\x02\x93[\xe2\xcd\x12\x7f\xfb\x1d%\x9c\xd9K\xb4A5\xb6\xe8$r*|\xdc\xa3\xfdO\xa7\xb6R5\xe59\xb3\x06bM\x02+\xe2\x16\xda\x9b\xc7\xf8\xee\xd6\x87`Ed{\x11Y3\x8d=@Ug\x03\xbfv\xa1i\xf6\xbbv{\xd1*\xd8\xcf\x02{`rkD[\x9c\x8a\xbc\xf8\xfe\xfb\xad\x10YD\x07o\xd0\xb1\x84\x9bc\xb4\xd0\xf76\xf0?L\x14\xd9c\x98\xa8-\xd7\xc8\xa7Wk\x98\x89\x95z\x0e\x97\xbe\x12\x97z}HU\xdfI\x94`Ed9\x11\xf9\xba\x88\xcc\xc0T\x13\xf8z\x8a\x07\xe9OpO\xe4\xbb\xaf\x88\xec\x91B\x92\xed\xc7\xd8\xbc\xee\xa8\xe14\x1b\x02\xff\x16\x91}\x08H3zS|o\x13\xf1K\xb1y\x9e\xaa\x0e\xa5\xecYb5\x0f\xd4D\xb0\x22\xb2\x9d\x88\xfc\x1ec\x8c?\x07\xd8\xc0\xfe\xe9si-\xc6\xa7\xaaoc\xdca\x5c\xf1\x8b4>\x93u\xdf\xfa\x18n\x09\x8a\x8b\xb1<\xc6\x8d\xeb\x12\x11Y6\xc5\x03y4\xdbZ\xd2\x8b\x5c\xf5\xb2\xc1F\x0av\xa1U\x93\x83\x8c,\xb0E\xc1\x0eq>O1F\xae\xd3\xceL\xda\x99\x09\xbcb\xb7W\xed\xf6\x16\xc6ep\x9e\x15T\x93\x1d\xaf=\x0b\xb8*\xe6\x99M9\x05[-\xa6\xe2\x17}\x16\x1f\xc1Z\xb5\xfa\x7fV\xad\xdeo\xa7/\xc5\xc43\x19?_\xb8z\xe1Gv\x9a\xe3\x82\xb5\x80\x13R\xfa\xd1\xe8\x06\xf6\x02\xfeY\xe3\xa9>\x07<\x95\xd2\xc4\xddiX\xe4*\xf6\xc3M\x8a`g\xd9m\xa15\x05E\x04[h\x9e\x88\xdb\xe4R\xd8\x9f\xf2\xaa:\xa0\xaa\x03\xf40\x93\x9e\x8a\x04;\x0d8\xd4\xe3\xda\x17\xda\x8fG\x12\x04[M_)\x85\x8f8\xf2\xe1+\xaa\xfad,\x04+\x22\x9b\x89\xc8\xa5V\xad\x9e[\xa0V\xcb\xe1K\xa9\x95A&\x15\xe0i\x1e\x87~[D\xd6I\xe93-\xc2\xd8T\x1f\xaa\xf1T\xab\x01\x7f\x13\x91_\xa7)\xd0\x22 \x95\x10\x8c\xfb\xa3\xeb,\xf8i\xdc\xa2\xd6\xea\x816\xdc3\x99U\x95\xdc\xbc\xda\xc6\xf9\x00f\xe5\xba\xdai\xf2\x0e\x22\xb2A\x8a;\xc7\xd9\x18#\xbd\x0b\xc6\x00\xbfL\xf1\x87c\x01\xb03\xa6\xe8]\xad8\x02\x98!\x22\x07\x8bHZ*\xad\x8e\xb6p\x91t.\x82\xa1\xa2-\x9f\xd0u\xa2\x5c\x07\xa5\xa2\x93\xe2\xac\x955\xaa\x12W\xd5~\xbb\xcd\xb5[\xb7\xdd\x060\xd5n\xb7\xf2\xb8\xe6\xb91\xa9\xff\xa8\x1f\xc4\x11\xc9\xb5\x0dn\xc1\x050\x92\x222\x16\x82\xbd\xcccZ\x9df\x15\xdb\x8f\xf13t\xc5n\x22\xb2\x7f\x8a\x9f\xab\x1b\xe3]pJ\x0c\xa7[\x03\xf8\x03\xf0\xb0\x88\xec\x1a\x04[@\xc1\x8cv2p\xa6\xc7\xa1\xb7\x02\xcf\xa7\xf0\x91\x5c\xcb\xda\xbcL\x95\x8b\xcb\xb9*\x07\xee<\xaa\xafV\x19\xe1\x90\xb4.v\xd9g\xba\x1d?C\xfb\xb9\x222%\xc5\xcf\xa5\xaa\xfa\x03\xe0 \xe2Y}\xde\x0c\xb8CDn\x16\x91\x0d\x1b\xf9hU*\x938\x0a\x1f\xd6r}_D\xa5h\xca+\xcbN\xbb\xd5I\xc1V\x1a\x03\x98\x8a\xab.\x98\x8f\xa9\xbc\x90T\xbf(\xfe7\xaa|\xb6\xd5p_\xdc\xba\xd0\xe6k\x8eM\xc1\x02\x5c\xe0x\x13\xcb\xe1\x97\xb6\xac\x9e\xf8&\xee\xc1\x07\xab\x02\x97\xa6h\xea\x5c\x8eh\xaf\xc4d\x05z;\xa6S\xee\x0e<.\x22\x97\x8a\xc8f\x0d \xd7Q\x85U\x82\xe4Z\x8a`\xe3&\xd9n\xbb\x95>\x7f\xfc\x16q/\x82\x15\x91\xcf\x03\x07{\x5c\xefR\xfb\x01IBP\xd4\x92p\xdbU\xbd\x0e`\xf2\xf0V\x85\x9c\xc3\x83<\x00<\xe9x3G\xa4\x9c\x84\xde\x00N\xf48tO\xd2U$\xb1\xdc\xf3=\x88\xb1\x93=\x16\xd3)s\x18[\xfc#\x22\xf2/\x11\xf9\x8c\x88t\xd6\xebqhl.\x82\xa4\x15\xec<\xbbE6\xc5%\xaf\xb1\x08E\xec\x16\xe3\xf3\xb8DR\x89\xc8\xfa\x98\x85-W<\xcd\x92\x09]\xb4\x0e}\xa5\x9a\xeb\x8c\xc1=\xbc\xf7\x1aU\x9d\xe92`\x5c\xe0\xaab\xb7\x13\x91\xf7\xa5\x9c\x87\xce\xc5\xaf\xd0\xda\x19\x22\xb2U\xca\x9f-\xf2\x9a\xd8\x0e\x93\x8b!\xce\x8e\xbd\x0d\xc66\xff\x9a\x88\x9c*\x22\xab\xd7\x99d+)\xd8,b\xbe\xdd\xca+\xcb>\x94\xbex\xde\xa1k\xfc\xbfM|t\x15&\xba\xd1U)_@\xedu\xb2\xe2\xec+\x11v\xf4x\x9e\xf3]\x15\x89\x0b.\xb3\xd3\x18\x17|)\xe5\x044\x08\xf8d\x98\xea\xc0T\x0cX6\xed#\xd7\xae\xfc\xfe\x1f\xf0!\x8c\x1fc\x9cX\x01S\x01\xf7%\x11\xb9^D>g\x17A\x02\x9a\x0b\xbf\xc0\xaf\xe2\xed\xcd\x98E\xa14\xc2\xd5<\xf0\x8c\xaa\xde\xe3r@\xbb\xe3@\x9d/\x22\x7f\xc2\xcd\xb9\xf8\x10\x119^U{\xd2\xdasT\xf5>\x1b\x95v\x88\xe3\xa1\xd31\x91a\x07da\x84\xa8\xea]\x22\xb2\x91U\xed\x9f\x8f\xf9\xf4m\xc0\xdev\x1b\x12\x91\xfb\x80k\x80k\xad\x8aNzz\x9e+\xf8M\xca\x06\xeb2\xfd\x84\x11g\xfa\xe8\xa3\xb6\x00\x13\xa1U\xca\xc5\xa8/R\x96%\xcf\xb4L\xc1\xd9\xfa\xea\xdboD\xe4 \xe0p\x8fC\xe7\x02W\x14\xb5W\x9e\xf8\x8bF\x96\xea+\xa3\xd9\x98\xdf\x03\xac\xedxn\xd7\x5c&^\xa1\xb2\xaef\x82I\x18\xd7\xa1\xb4\xe3[v\x8a\xe6\x8a\xfdE\xe4+Y\x91!\xaa\xba@U\x0f\x05\xf6\x05\xdeI\xe82m\x18\x9f\xdc\x9f\x03\xaf\x8a\xc8\xc3\x22\xf2c\x119PD\xa6\x071\x98\x1d\x88\xc8\xba\x1ec>\xc2%\x1e3\xdezaw\xc7\xfd{0\x0bu\xc9\x12\xac]8y\xc2\xf1\xb0#\xd2\xde\x91lV\x9c/z\x1e\xfeS\x11\xd98K\x03GU\xaf\xb3S\xbe?\xd7\xe1r\x9bc*\xa7\xfe\x09xQD\xde\x11\x91\x9bD\xe4\x87\x22\xb2\xa7\x88l\xe0`jit\xa8\xac\xcb\x22W\x94;`\x81\xdd\x16c\x5c\xe7z\xad\x0e\x8dr\x0d\x0cVi\x13Mj\x81\xad\x1c\xb9\x8e\xc5\xd8]}|\x18\xeeg\xe9\x1c\x19\x85\xea\xb2\x1e\xcfQ\xee\x1a\x13\x80\xed\x1d\xcf\xf5G\xeb\xae\xea\x84v\xcf\x1b\xbf\x00\xb7\xd5\xc4mEdsU}$\xe5\xa4s\xb5\x88\x9c\x07\xb8*\xd21\xc0U\xf6\x19\x17\x91\x11\xa8\xea\xbb\xc0'DdGLt[\xbd\x16\xed\x96\xc7\xa4\xe0\xdb\xa3h@/\xc2$\x9b~\xdd\xfe\xbea\x95CD\x9c]v\x1b\xb2\xffvo\x11\xc1B\xba\x16\xba\x06\x0aL\x03\x00\xbd\xaa\xea7\xc1_\x882\xa6\xee\xf7\xffSL*BW\xbc\x0d\x9cW\xc5\x87\xaaQ\xd8\x15w\x8f\xe2_\xfb\x5c\xc8\x97`/\xb3\x03\xd2e\x05\xee\x94\xe2\x01\x95R|\x13\x13\x1a\xbc\x89\xe3q\xeb\x02\xbf\x13\x91OV\xeb\x84\x9c\x22\xa2\xbd\x0f\xd8ZD\x0e\xc4\x94\xf0X\xbbA\xb72\x01S\x13\xe9\xbdU\x0e\xe2{\x09HJ\xbd\x1e\x89_]\xbaA\xcb\x0di5\x0d\xb4y\xf0\xd0\x7fT\xd5+\xcfG\xces@.\xc0=Y\xee\xee\x22\xb2]\xda;\x96U\x18\x07\xe2\x1e\x80\x00f\xb1\xeb\xe7Y\x1dT\xaaz\x15&q\xf2\xd7\xf1\xab`[O\xe4K\xf4\xe5$\xab\xca\xfa\xdec\xb4\xa8S\xeb\xc2\x8e\xd2G\x9e\xbe\x9a\x17\x87\xa2\x5c\x07\x95\xc8u\x7f\xfc\xfc]\xc1\xd4\x0d{\xa1\x82r\xadG\xc9\x9dJ\xe6\x9c\x0fc*\x17$\xae^\xbd\x09\xb6\xc0L\xe0\x8a\xd33B4\xcf\xe1o7\xfe\xaa\x88|?\xc3$;\xa0\xaa?\xb7*\xf6tJ\x97\x87N\x03\x86\x08HB\xb9\xee\x84\x09i\xf5\xe1\x86\x7f\x007\xa5\xf8\xf1:q_p_\xc0\x88'D\xfd\x08\xd6J\xe6\xc7\x1d\x0f\xdb1\xa5\xf9FK=\xdf\x15\xc0E\x9e\x87\x9f,\x22Gdy\xa0Yo\x83\xefbb\xb5\xff\x0fx&\xe5\x0av\x98#\x1a\xa0\x8a\xe2<\xa6\x9a\xf3\xd5\x82\xb29e\xedB\xedu\xe0e\xed\x9diM\x03\xa3\xdd?\xd4'\x92\xabT[\xed\x89{r\xf0?\xa8\xaaw\x88o\xad%c~\xe2q\xcci\x19\xe2\x99\xff\xc3/\xca\x0b\xe0<\x11\xf9x\xd6\x15\x8d%\xda_\xa8\xea\xfa\x98@\x85kS\xa2\x1e\x87J\x10k=\xcd\x02\xea8\xd0\x1b\xbd\xb0S\x91`\xad\xfb\xdc\xad\x98\x120>\xe7<\x95\xea\xccjI\xb7E\xb9\x84\xdb\xe3\x00\x9fLx\xbf\xae\xe5fj%\xd8\xcb1\x85\xcf\x5c\xb0\x85\x88\xec\x97\x11r\xe9\xb1S\x8an\xcf\xb6\xbd\xc2N\xb9\x9a\x02\xaaz\xa7\xaa\xee\x87\xa9\xf0p\x06\xf1%\x92\x89S\xc1\x06\xb8\x9b\x05V\x00n\x03V\xf2<\xc5\xd9\x98|\x03i\xc6\xbe\xb8\xbb\x9b\xdd\xaa\xaaO\xd5r\xd1\x5c\x8d\x03.\x0f\xf8\xd8\x1bO\x11\x91L\x94\x0cW\xd5\xa7\xf1\x0b\xa5\xc5N\xb5\xae\x13\x91M\x9ai@\xaa\xea\xab\xaa\xfa\x1dk>\xf8 \xf03\xe0\xa5\x06+\xd8\xe2E\xae\xa4M\x04\xd5\xa8\xb08\x17v\xe2R~\xfd\x14\x94k\xb1\xa5\xdbo\x06|\xabu\xdc\x8c\x9b)\xad\x92\x1fla2\xf3Z\xda\xab\xb8\xad\x96\x05\xf6\xf18\xc7wj\xed4\xb9\x18\x06\xdb5\xc0\xc3\x8e\x87\xbd\x0f\x93\xaf4+\x84r\x09\xf0{\xcf\xc3'\x02\xb7\x88\xc8Z\xcd\xa6|TuHU\xefS\xd5o\xaa\xeaZ\x18\xd7\xb6\x13\x89/{WP\xb0\xc9*\xd7N\xe0\xaf\x98@\x10\x1f\xbc\x82\x09/\xd7\x94?\xea\x01T_\x8d%\xc2U\xaa\xfah\xc3\x09\xd6\xe2{\x1e\xc7\x9c$\x22\x1d\x19\xea\x8f_\x04\xee\xf2\xae\xaa'\xa9\xea\xa6\x98\x82x\x87clX\x0f\x13_\x91\xbbr\x04\x9bt\xc9\x18\x1f\xf5X\x9cs \x0dD4\x00\x0c\x88H;\xa6b\xc5\x87<\xcf3\x1b\xf8\xa8\xaa\xcevl\x8bJ\xea\xb4\xf0o\xa3\xb5w\xa9R1\x14]c\x08\x13\xd4\xe2\x9a\xd4e\xd0sf\x9e\x0c\xc1\xaa\xea\xdf\x80\xfb\x1c\x0f[\x0bS\xd7'+\xe4\xd1o\xed8\xbe_\xb5\xb5\x81\x7f\xd8\xd8\xee\xa6\x87\xaa\xbe\xa2\xaa\x17\xab\xea\x97UuKL\xba\x92-1\xce\xeb\x17c\xc2\xadk)A\x1d\xdc\xb4\xfc1\x16\xe3-\xe0\x9b#d1\xb0\xa7\xaa>\x93\x81g=\x1c\x93\xf9\xce\x05\x7f\xb4\xae\x9a5\xa3=\xc6\x07\xf9.\xf0w\xc7c\xbe\xc0\xf8\x04#\x00\x00\x18\xb3IDAT/\x22\x97\xaajoFHc\xa1\x88\xec\x8e\xf1\xf7\xf3\x89v\x9afIv\x0fU\xfdw+\x8dh\xfb\x81z\xd8n\xbf\xb6S\xd4\x1c\xc6\xe9{\xb52\xdb\xb2\x98\xc8\x9b\x0e\xdbW;\x0a\xfe\xbb81\x8f\x94\xf9MB\xc9\xba\xee\x1b\x97{R\x1c\x0ax<&\x18f#\xcf\xe3\x07\x80\x03lN\x92b\xd5X\xebs\x14\xb7\x93\xb8~`UUm\xb1\x11\xb5\xe3\xcd5\xa9K\x1ffM!\x16\xb4\xc78\x80\xee\x17\x91[\x1d\xe5\xf8\xaa\x98\xb8\xff\x9ff\x88(f\x8a\xc8n\x96dW\xf48\xc5T\xe0.\x11\xd9_Uoke\x19e\x17I\xdf\xb2[\xd9\x0f\x8e5%u\xda\xb6\x9bj\xffy\xb5\x0a\x04\x9b\xa6J\xb8q\x11c\x1c\x98j\xc9cz\x0d\xcfs\xa8\xaa\xdeZfZ\xeeb*\xd0\x1a? \xf9\x0a\xfdJE$\x0f|\xc3c\x96~\x151z\xc7\xc4\xbd\x92\xff=\x8f\xcet\x82]\xc9\xcc\x121\xbc`\xbf\x8c\xbeQN\x13\x80\x1bm\x9e\xcd\x80\x80z`\x0d\xe0G5\x90+\xc01\xaazyF\x9ew\x13`7\xc7c\x16aR,\xc6\x86X\x09\xd6f\xcb\xfa\xab\xc7W\xf5\xbb\x19T_\x8fbl\xb2\xbe\x8b7\x1d\xc0e\x22rt\x18\xfb\xce\xcaG\xcb\xf4\xe5z,r9\xbbK\xa9j\xden\x8dr\xd3Z\x0f\x13\xf6\x5cK5\xe4\x1f\xa9\xea\xcf*\xdc[\xb9E\xa7R\xcf\x10\xc7\x22W\xd9\x884\x8b\xef{\xf4\x85?b\x92\x84\xc7\xe6\xa1\x92\x84/\xea\x0f\x89I\xf8\xee\x82\xb9$\x90\x1b9v\x82U\xd5\xff\xe2^\xff\xbc\x1d\xb88cn[\xd1\xf3^\x8d\x09\xa9\xad\x05\xdf\x16\x91\xdfY\xd7\x99\x00?\x05\x9b\xf5\xaa\xb2\xa3]\x1b\x8fk\xef\x0a\x9c\x80{\xee\xd3B\xdc\xc0\xe8\x89\xe8\xabU\xf7\xd5\xfe\xbd\x1a\xe4K\x09\x1b\x11Y\x1e\xf8\xa5\xc7s^\x85I\x84\x1ek\xa6\xaf\xa4\xa2\xa9N\xc4\xdd\x05\xe7\xfd\xb63do\xe4\xab\xfe\x0a\x13\x8b]\x0b>\x0f\x5c/\x22\xcb\x05.-K0\xa3\xf5\xe5zGr\xd5\xa3o\xa9\x87\x89 \x87\x09\xe49\xaa\xc61~?\xf0IU\x1drh\x9fj?\x12\xb5\xb6_\xbe\xcc\xcc\xf1W\x8c,\x84V\x8b\xb71\xa1\xc2\xb1W[\xc8%\xd4)^\xc4\xf8:\xba\xe2\xbb\x22\xb2a&\x19@\xf5\xfb\xd6-\xd8\x0c8\x07\x13\x8a^\x0b\xae\x00\xf6vH\xd3\x97\xa7\xfaE\xaeh\xdfj\x16\xb9\xaay\x1f\x11\xb9N\xc5/I\xf8\xcb\x8c\xf8\xefW\xba\xb7t\x11\xac\xaa\xbe\x81\x9f\x7f\xeb\x96\x98\xb2-\xd9d\x01\xd5\x0b\xecW\xb4\x96\xe2\xca\xed\x18\x97\x9a\x9bl\xc7\x09\x08\xa8\x846\xe0s\x98\x95\xf3\x895\x9e\xeb\x1c\xe0`U\x1d\xc8X\x1b\xfc\x02X\xc1\xe3\xb8\xcbH\xd0\xdc\x93tF\xab\x93\xf1K\xd4|r\x96CJU\xf5\xaf\x98\x80\x8b\x051\x99\x0cv\x0c\x1c2\xeaBO\xae`K\x02\xa5\x14\xec,\x8c\xefd\x7f\x91z\x8b\xaa\xc9\xce\xc7\xaf\x14|\xa56(~\xf6\xe51.X\xfb\xc5\xa0\xdaOP\xd5ox\xba\x93\xe5q\x0f6(\xf7\xef\xd5(\xd8\xe1\xeb\xd9\xf4\xa7\x9f\xf2\xb8\xe7\xc7X2Q\x95fF\xc1Z\xa2\xe9\x03\x0e\xf5\x982\x8f\x05.\xca\xf2\x14YU\xef\xc1\xa4\xf2\x9bY\xe3\xa9V\xc5D~}/+)\x1e\x13&\xd7J}9\xc9E\xaeh@G\xe4\x19\x11\xecB\xfb\xff\x85\x04\x1b\xf9h\xc6I\xb0\xa5\x9e}kLd\xd6{k<\xf7\x10p\xb8\xaa\x9eY\xc3\xbdU\xbbp\xe5Z\x11\xa2\xe2yDd2p\xbe\xc7=\xf7\x940)\xa8\xe3\x87\xa2\xe1\x0a\x16U}\xc0N;\x5c\xb1\x03\xfeyX\xd3B\xb2\x8fa*\xd4\xbe\x10\xc3\x14\xf0\x14\xe0o\x22\xb2\x22\xad\x89\xd1\x14l\xd2!\xb2\x85\x19\xb1\x22\xa5\xd3\x87\xc9*\xf5\x0a\xf0\x1a\xa6\xdcxa\xc9\xf1E\xf8\x15\xcf\x1c\x8d\x9c:\x81/`\xbcn&\xd4x\xce\x1e\xe0\xe3\xaazq\x8d\xf7U\x0d1\x8d\x16h\xe0S\x8e\xe7\xe7\xf8\x85\xac_\x82\xb1\xa1\x17\xbf\xe3\xfeL\x11\xac\xc5\xf7\x80\xe7=\x8e;CD\xa6e\x9cd_\x04\xb6\xc3?\x0bW!>\x04\xb4\xb2\x87A\xa5\xc1\x97\xb4\x1fl1\x81\x0c\xda\x19\xdal\xe0\xd5\x22\x82}\xd3n\x8b\x89\x7f\x11l\x17\x8c\xbdq\xaf\x18\xce5\x0f\xf8\x88\xaa^\x1f\x13\xf1\xd7\xd3MK\x81\x1d\x81\xcfx\x1c\xfb\x04\xc6-\xab\xd4G\xb4x6\x92~\x82\xb5\xae\x1e\x87y4\xea\x04\xe0\xc2\xcc\xb3\x82\xeaL`'\xe0\xee\x1aO\xf5+U\xbd\x96\x80V\xc4j\x98\xca\x01\xbf\xf7Tl\xc5x\x13\xd8AU\xef\xcfh{L\xc4\xaf\xe2@/&\x10\xa1.~\xccu\xb3\xe9\xa9\xea\xdf\xf1s\xa3\xf8\xb0\x88\x1c\xde\x04$\xbb\x00\xb3\xf0\xf5K\xcfS<\x06\x1c\xd3\xe2$\x13\xbb\x8d\xccS\xa5i\xb4\x10d\xab:\xf4\xa9\xea|U\x9di\xb7\xb9v\xcb\xdb\x8ca\xde\xb0\xd1\x8d\xc7c\x16c>\x1a\xd3\xb3\xfc\x1d\xd8\xb2\xd6zSE\xea>\xb2;\x8f\x16\xa9\x15\x97\x9b\xd67q\x0f(\xc0~\xa4\xde)\xf3\xb7>\x8c\xcd<\xb6\xf4\xa9\xf5^49\x1e\xbf\xdaM?\x11\x915\x9b\x80d\xfbU\xf5(L\x09\x0b\x97\xc5\x8f\x85\xc0\x81vJ\x1a\xd0\x22\x10\x91]\xect\xf6t\xa0+\xa6\x8f\xc4\xe9\x98\x00\x8273\xdc4[\x00{{\x9a\x06n\xaa\xe7\x8d\xd6\x95`\xad\xe3\xf2\x17<\xe4\xf9\xb2\xc0\xb5\x222\xae)d\x98\xea_\x80M\xa9\x90\x03\xb5\x08G\xc6\x95a=\xe3\xea\x15\x1aW\xfe\xban\xd7\x15\x91\x95E\xe4\x0a\xe0NL&\xac80\x17\xb3(v\x12\x95\xb3P\xf9\xb6M\xb5\x81\x06\x95\xda\xb2\x1a7\xadI\x98\x1c\xd2\xae\xe8\xc6T\xbf\xad\xe4\xdf\x9b\xd9E\xaeBr\xb9\x0b\xf8\x8d\xc7\xa1\x9b\x00\xbfk\x1a\xb6P}\x09\xd8\x1e8w\x94]\x7f\xab\xaaW\x04r]\xe2\xb7Y\x15k\x97\x88\x1c\x83\xf1\x1d\xfft\x8c\xa7~\x02\x13a\xf8H\x82\xefg4\xd3\x8d\xcf\x07\xb2x\xbfv;\x0b\x9e\xecq\x8f\xa7`\x16\x22+}\x5c\x86Tu\xa0\xca\xdc\x0b\xe9$X\x8bc\xed\xc3\xba\xe2@\x11\xf9N\xd3\xb0\x861\x19\x1c\x8dq\x12\x9f[b\x97\x19\x98\x84\x1d\x01\xcdM\xac\x13D\xe48L\xd8\xe6\x8f\xa9=\x1a\xab\x90\xa0\xfe\x08\xfc\x10\x98\xd3\x04Mu\xa4\xa7\xa2\xff\x17n\xa5\xc5\xb3i\x22( \x96\x85\xc0\x97<\x0f?ED\xf6j\xa6\x01f=\x036\x05\x1e,\x9a\xd2\x1c\x98\xa5d\x1bu\x9a\xa27\xcaD\x90\x04\xb1.+\x22\xdf\xc7\xf8\xd1\xfe\x08\xbfP\xcfJ&\x81\x1fX\x82\xadv\x0a\xef\x8b\xc2E\xaej\x94\xeeP\x19\xb5[\xe9\x1d\xef\x85_\x05\xdcnL\xb0\xd3\xf8\x1e\xb5\x07\xfax\xa3\xd1\x09\x9e\xbf\x89\xa9\x9b\xb3\x8a\xe3q\x13\x81\xebDdkU\x9d\xd7D$;\x00\x1c+\x22W\xa8\xea\x7f\x02\x15-\xa5nh\xa0z\xad\xf9\xba6\x0a\xef\x18L\xe9\xf2\x09\x09\xdcc\x9fU\xac\xd7\x96\xb8\xdf|\xc2m\x93\xafr\xbf\xd1\xdc\xb8\x8a\xb1\x12p\x1c&\x9a\xd1\x15\xff\xc0DzE\xe7\x9d]$,\xa3s.\xb4[\xec\xd5\xad\xdb\x1bL(\xf3D\xe4H\xc0'\x92d]\xe0\x0a\x11\xd9\xabV_\xc3\x14\x12m \xd7\xa5\x07\x9ed\xd5< \x22\xdba\xb2]\x1dL<\xeeV\xa5\xf0 \xc6\xce\xf8nR\x1f\x88\x1a\x09\xb6\x9aH\xae\xe2\x7f\xeb\xc2\xd4\xeb\xf3\xf9\x18\xf5\x00\x87\x15-X\xcd\xb6\xef\xc3D\xd7M\xb3\xff\xfa2\x83\x98\x05\xae\xd8\xdb(\x97\x022\xb9\x01c\x7f\xf2\xc1\xee\xc0\x19\x81\x7f\x02RH\xaa\xd3E\xe4\x07\x22\xf2<\xa62\xc0\x17\x13\x22\xd7\x99\x98j\x1agT \xd7L6!\xf0-`u\xcf\xe3\xbf\xa5\xaa\xffk\xf4C\xa4\xa5\x06\xd4w\xac\x9dew\x8fc\x8f\x13\x91\xc7T\xf5\xca0\xac\x9b^\xc5\xa6Z\xc1\x8a\xc8DL.\xe0C0\xf6\xf4$\x93\xcf\x0cb*8\xff\x99\xea*\x1b\x0f%\xa8d\xa3E\xaej\xce_)%`\xa1\xca=\x14\x93-\xcc\x07\x17\xd92N\xe5D]o\xbd\xfaD*\x08VU\xf3\x22\xf2i\xe0!;\xf5w\xc5oE\xe4\xd90\xb5\x0eh\x00\xa9\xb6aV\xb7\x0f\xc1\xb8\xdbu\xd5\xe1\xb2\x8f\x03\x17`\xf2\x094#v\xc2\xdf\x0f\xf8~R\x94\x85/5ULUu\xbe\x88|\x0ccKr\xf5\x03\xec\xc2Dzm\xa1\xaa\xef\x84a\xdf\xf4*\xb6Q\xd7\x8e\xb0\xacM\x82\xfe!`\x7f\xdc\x17i}\xf16\xc6\xf5\xca\xa7\x8f\x0f%x_\x85\x0av\xb46\x1c\xcdMk#\x8c\x9f\xbc\x0f^\x05\xf6W\xd5\xfe\xb4t\xd8T\x95\x89V\xd5gD\xe43\xc0u\xb8\xdb\x87W\x07\xfe\x22\x22\xbbd\xb0\xdcE@\xba\xd1i\xa7\xfc;`\xd2En\x8e\xdf\xaa\xb6/\xfa1Y\xe5~\x8a\xc9/\xfb\x81&m\xe7\xe51\xae\x9bc<\x8e\xed\x06\xf6M\x9b\xc0jO[\x0b\xab\xea\x8d\xd6\xf9\xfa4\x8f\xc3\xb7\xc7d\xab:\x22pBS\xaaW\xea\xa4`s\xc0{0\xeb\x02\x1ba\xa2\x87:\x1b\xf0\xcc\xbd\x98\xb0\xf2\xb31\x91X\xb5\x98\x1fR\xed\xa6%\x22\x9d\x18/\x08\xdf\xd9\xc0\xe7U\xf5\xd1\xb4u\xda\xf6T\x8e$\xd5\xd3Edc\xfcJ\x0f\x7fID\xe6\xa8\xea\x09\x81\x93\x1a\x07[~}7\xe09L\xb2\xf5\x17|\xb3\x81\xa9\xaa\xda\xf2A\xb1\x9b\x08Dd\x05Lv\xa6\xf7c\xf2]\xbc\x17\xb3\x0e\xd0\xc8\xc4B\x8b\x80_\x03?V\xd5\x99\xf6\xd9k\xb5\xed&\xed\xa6\xa5U^\x7f)\x92\xb5\xe4z5\xa6\xe0\xa9\x0fNU\xd5\xab\xd38\x0e\xdaSg\x9f#V\xc5-\x88\xd8\xff\x88\xc6\xe2j\x98$\xe0\xb5\x98\x05\x1e\x02\xben\xc9\xb5\x0dS\x12\xa7\xbf\xe89R=n\xdb\xb3<\x90U\xf5\xb5\x02%\xebK\xb2_\x05\xd6\x13\x91\x03\x83\xed,6l\x83I\x96\xb2N\xc1\x16\xfd\xff\x8a)\xb9\xc7!\xe0%\x8cm\xefEK\xa8s1\xa5F\xba\x9b\xf0\x9dh\xb2'7JM\x90W1\xb9\x1c~[\xc3\xec\x12\xccb\xf6\x01\xf6\xbe\xdb\xed\xd6m\xdf\x0fY\x11D\xed\x99\xef5\xf1\x90\xec\xae\xc0C\x22\xb2\xb7\xaa>\x1d\xf8\xb1\xe6w\xb2\x00x\xc4n\xc5Jl\x99\x22\xe2]\xc7\x0e\xc4\xa9\x98\x90\xd7h\xeb\xf2\xe8\x9fj\x07\xe1|K\x96s\xed\x7f\xcf\xc7\x94m~\x07\xf8/\xf0\x0c\xf0\xbf\x025\xb4\xac\xddV\xb2[\x80?\xf6\x00.\xb5\xed\xe9\x8b\xfb0~\xb2y\xea\x93\xc0<\x10l\x15$\xbb\xb3%\xd9\xe9\x9e\xa7Y\x1bx@D\x0eR\xd5\x9b\xc28I\xec]-\x04\x1e\xb5\xdbh\xd3\xe2\x8e\x22\xd2]\xd1nc1)\x04{-\xa1.\xb6\xbf\xdd\xc0\x1b\x98L\xff\x0b1\x8b\x22Z.V\xddf\xa9\x1a\x99\xe5\xa6_\x81\xa6\xd5M+j\xcf\xe31iFk\x89\x10\xbd\x17\xd8\xb3 \xbamQ\x96\xfb{{\x13\x0d\xdcW\x0b\x94\xac/\xc9N\x04\xae\x17\x91\x13T\xf5\xac@\x87\x0d\x7f\xa7\x03\x05\x0a\x14\x11\x99\x83\x89\xdf\xa7I\xa7\xf1\x99\x84\x88\x8c\x03.\x06>Y\xe3\xa9\xee\x01\xf6j\xa6\xbc\x0c\xb9&\x1b\x90\xafb\xea\xf9\xbc\x5cc\x9b\xfcHD\xfe`\x17\x16\x0228\xe63\xa0H\xeb\xadb\xa3\x5c\x04\xb1\xe6\xd4\x15\x9150u\xb0\xe2 \xd7=\x9b-\xe9M\xae\xe9z`<$\x0b\xa6\x86\xfd\xbd\x22\xb2r\xe0\xab@\xae-N\xce\xe5\xc8uG\xe0a`\xd3\x1aOu\xb7%\xd7\xa6\x9b\x95\xe4\x9a\xb2'\xa9\xbe\x12\x13\xc9n\x05<,\x22[\x86\xf1\x19\x10\xb0\x04\xb9~\x19\x93\x15l\xf9\x18\xc8u\xaff$W\x80\x5c\x7f\x7f?\xfd\xfd\xfdM\xf7`\x05$[kJ\xb3U\x80\xfbD\xe4\xa00\xac\x9aL\xc5\x9e\x88p\x22\xc2\xb2\x99P\xbc\x1a\x93\x89\xa0Vb\xed\x10\x91\x0b0I[jM\xe8\xd3\xd4\xe4\xba\x84\x82\x8d\x88\xb6pk\x12\x92\xdd\x16\xe3\xb0\x5c\x0b\xc6\x02\x97\x8b\xc8\x19\x22\x92# \xa05U\xeb\x8a\xc0]\xc0\x97b8\xdd\x15\xcdj\x16X\x82`s\xb9\x1c\xd1V\x8c\xfe\xfe~\xc4 \xb3\xa4\xa2\xaaob\x22_.\x8b\xe1t\xc7\x03\xd7\x89\xc8\xf2a\xb85\x5c\xc5\x95SsF\xbd\xba\xa8\xd2\xf9K\x1c\x9b\x85\xe7\xaf\xbb\x82\x15\x91\xcd1\xe5\x9b\xb6\xaf\xf1\x19\xf2\xc0\xb7U\xf53\xaa\xda\xd3\xec\x1d6\x07088\xc8\xe0\xe0 \x85d\x1b\x11n__\x9fb\xea\xe7\xe4D$\xab\xb9\x0bzU\xf5\xb3\xc0q1L\x93\xf6\x02\xfe+\x22\x9f\x0a|\x97\x0a\x92]\x12\x1d\x08c\xc9U\x95\x0a\xe4D\xbbe\x83\x5c\x1bB\xce\x22\xd2&\x22\xdf\xc2DV\xad^\xe3=\xcc\x07\xf6n%\x17\xc8\x5c__\x1f\xf9|\x9e|>O\x7f\x7f\xff0\xd9ZyK.\x97\x1b&YK\xb4\x99\xf5\x9dU\xd5\xb31\x11\x22\x0bj<\xd5T\xe0J\x11\xb9FDB\xe4O\x9a\x14\xec\x00&\xfc`b\xf0$\xa8\x95`m\xe9\xf5\x7f\x01gQ{D\xd5\xff\x80\xadU\xf5\xe6Vj\xf4\xdc\xc0\xc0\x00CCC\x0c\x0d\x0d\x91\xcf\xe7\x19\x1c\x1c\x1c\xfe-$\xda\x81\x81\x81%H6\xabf\x03\xfb\x82\xb7\xc6&\x8b\xa8\x11\xfb\x023D\xe4\xe00~S\xa2`#5\xba 4T\x0d\xe6\x80\x0e\x11\xf9\x01&\xd49\x0e\x0f\x9a[\x80\xadT\xf5\xd9Vk\xcb\x5c__\x1f===\x0c\x0d\x0d\xd1\xdb\xdb\x8b\xaa\xd2\xd7\xd7W\x92h\x0b:\xf5\x90}\x11Y%\xd9g,\xc9\xde\x1e\xc3\xe9&\x03\x7f\x10\x91\x1bDd\x950<\x032N\xae\x9ba|[O\xc2T~\xad\x15gc<\x05\xe6\xb7b{\xe6\x06\x07\x07\x19\x18\x18`\xc1\x82\x05\xf4\xf5\xf5\xb1p\xe1B\xf2\xf9<\x03\x03\x03\xc3\xa6\x83\x88h\x0bT\xac\x90pv\x9e:\x90\xec\x5c`w\xe0\xdc\x98N\xb9\x97U\xb3\x87\x86aZ\x17\xf5Zn\xd1F0k\x0b!\xe0`IT\x5c\xe4\x12\x911\xb6\xfa\xf2\x83\xc0\xfbc\xb8^/p\xb0\xaa\x1e\xa7\xaa\xf9Vm\xf4\xdc\xe2\xc5\x8b\xe9\xe9\xe9a``\x80\xc1\xc1A\x86\x86\x86\x222]B\xc9\x96 \xd9\x5c\x96U\xac%\xd9!U=\x1aS\xad6\x0e\xbf\xb4I\xc0\xc5\x22r\xab\xcdU\x1b\x10\x90\x05\xd5\xfa\x01LB\xf1\xe3\x89'?\xc9\x1b\xc0\x0e\xaazy\xab\xb7mn\xf1\xe2\xc5\xcc\x9f?\x9f\x9e\x9e\x1e\x22\xb2\xed\xed\xed\x1d\xb6\xcb\xf6\xf7\xf7\x0f/\x80E$[\xa4(\xc8\xbao\xa8\xaa^\x0c\xec\x02\xcc\x8c\xe9\x94\xbb\x01O\x89\xc8\x97\xc2\xf0MT\xc5\x96w\xd3rS\xaf\xd2\x8a\xed&\x22\xe3D\xe4\x1c\x8c\x87\xc0z1]\xe7_\xc0\x16\xa1\x8an\x01\xc1vww\x0f\x93kww7\x03\x03\x03\xf4\xf6\xf6\xd2\xd3\xd3C__\xdfR\xe6\x82\x02\x15K\xd6M\x05\x05$\xfb\x0f\x8cA\xff\xd1\x98N9\x11\xb8@D\xee\x10\x91i\xa1\xab\xc5\x80\xc9\x98\xb5\xec\xd1\xd7\xb3}\x08\xb3\xa5\xcc\x096\xbd\xe7\x93\x98\x8a\x01q\x09\xa4\x8b\x81\x9dT\xf5\xed\xd0Y-\xc1\xce\x9d;\x97\x85\x0b\x172\x7f\xfe|\x16/^\xe0\x86\x18O\xbb:\xa6V\xd1S\x22\xb2\x7f\xe8~\x01u@;\xb0'\xf0\x00\xf0\x1d`\xb9\x98\xce\x9b\x07~\x02l\xac\xaa\xf7\x87f.\xd3\xf8\xf3\xe6\xcd\xa3\xaf\xaf\x8f\xae\xae.\x86\x86\x86\xe8\xeb\xebc\xdc\xb8q\x00\x8c\x1f?\x9e\xee\xeen\xc6\x8d\x1b7\xece0~\xfcx\xfa\xfb\xfb\xe9\xec\xec\xa4\xd9\xd4k\x09\x92}\x13\xd8\xc7\x06\x12\x9c\x8b\xb1\x02\xc6\x81\xf5\x80?\x8b\xc8\xc3\xc0\x09\xaazG\xe8\x8a\xd5\xbf\x96Q\xfa\x9d\xcf\x22W\xe1\xb1M#\x9e0\xd9\xe4>M\xed)\x05\x8b\xf14p\x98\xaa>\x10\xba\xe3(\x04;{\xf6l\xc6\x8e\x1d\xcb\xc4\x89\x13\x89\xa2\xba\xa2\x12F\xaaJ.\x97\xa3\xad\xad\x8d\x5c.GWW\x17\xf9|~8OA\x7f\x7f?===\xda\xd5\xd5\xd5\xd4\xd31U\xbdLD\xee\x00\xce\xc7Do\xc5\x85-\x80\xdbE\xe4.K\xb4\x0f\x85.Y\x15\xb9&A\x94\xcd\xd2\x87\x05\x93A\xee L\xe9\xec81\x84\x09\x1c8QU\xfbBw\xac\x82`\xe7\xcc\x99\xc3\x84\x09\x13\x86}_#\x92\x15\x11\xc6\x8c\x19COO\x0f\xb9\x5c\x8e1c\xc6D\xd9\xb5\x18?~\xfc\x12D\x0b044\xa4M=\xb2\xcd\xca\xe8~\x22\xf2i\xe0\x17\xc0\x94\x18O\xbf\x0b\xf0\xa0\x88\x5c\x03|7T\xb6\x0d\xf0\xc4f\xc0g\xa8\xad\x5cv9<\x09\x1c\xaa\xaa\x8f\x84fv\x98Ftvv\xb2p\xe1B\x1e\x7f\xfcqn\xbc\xf1\xc6\xe1\x05\xae\xde\xde\xde\xe1\xff\x1e\x1c\x1c\x1c\x0e:\x88l\xb0\x91\xeb\x16@OOO\xe4W'\xcdf\x8b-A\xb4W\x02\x1b\x00\x7fM\xe0\xf4\xfba<\x0e~'\x22k\x86\xeeYV\xc5\x8e\xe6\x07\x9b\xa3\xb5\xa2\xb96\x00\xce\x00~\x90\x00\xb9\x0e\x00'\x03\x9b\x07r\xf5 \xd8\x05\x0b\x160o\xde\xbca\x9f\xd7h\xa1\xab\xbb\xbb\x9b\xee\xeenz{{\xe9\xeb\xebchhh\x98T\x0b\x93q\xb7\x92\x8a- \xd9wTu\x7fL\xa1\xb7Y1\x9f\xbe\x0d\xe3\xc1\xf0\xac\x88\x9c\x13\x12|\x07T\xc0\xda\x96TO\x07\xd6O\xe0\xfc\x8f\x02[\xaa\xea\x0fm\x85\xdf\x00W\x13\xc1\xec\xd9\xb3\xe9\xeb\xebc\xd1\xa2E,^\xbc\x98E\x8b\x16\x0d\x9b\x06\xc6\x8f\x1f?L\xac\x03\x03\x03\xb4\xb7\xb7\x0f\x07\x1b\x00\xc3\xbfK\x18iZ\x84d-\xd1^%\x22w\x03\xbf\x02>\x11\xf3\xe9\xc7\x00k\xb5r\x1cw\x05\xf5\x0a\xe5\xe3\xea]\xdc\xb4\xa4\xe07+Jw,f\xf1j\xb7\x84H\x15L\xd8\xf8\xc9\xc0\x8fTu0t\xbb\x1a\x14l\xa4\x5c\xfb\xfb\xfb\xe9\xeb\xeb\x1b\x8e\xe0\xea\xed\xed\xa5\xb7\xb7w\xd8<\xd0\xd7\xd7\x87\x88,\x11\xd1\x05\x14\x87\xce\xb6\xde\x88W}WU\x0f\x04\x0e\x00\xde\x89\xf1\xd4y\x8c[M@\x00\x18\xcf\x9331yU\x8fN\x90\x5c\x1f\x026S\xd5\xd3\x02\xb9\xc6@\xb0\x03\x03\x03\xcc\x9d;\x97E\x8b\x16-\x11\x1e\x1b%~\xe9\xeb3\x8b\x85\x91\x1fl\xa1z-$\xdaf,\x9c\xe8H\xb4\x7f\xc1\xd8\xc2.\xc4D\xb6\xd4\x8a\xcbU\xf5\xa9\xd0E\xcb\xaa\xd8\xd1r\x11d\x1e6\xc3\xd5A\x22r/\xf08\xf0U\xe2\xf3c-F\x0f\xa6\xe2\xc7\x07TuF\xe8f1\x11\xec\xacY\xb3\xe8\xee\xee&\xaalP\x98I\xab0\xcbV.\x97#\x9f\xcf\x0f\x9b\x0c\x02J\x92\xeclU=\xc2\x12\xed\x9f\xf0\xf7\x11\xee\x07~\x18Z\xd4\x9f\x9b\xb2L\xb2\x22\xb2\xae\x88\xfc\x18\x93\x95\xear`\xc7\x04/7\x80\x09\x9d]GU\xcfV\xd5\xa1\xd0}b$\xd8\xee\xee\xee\xe1P\xd8(UaD\xb2\x91+\xd6\xe0\xe0\xe0\x12Q\x5cmmm\xc1DP\x99h\x9fS\xd5O\x01\x9bc\xb2\xb9\xbb\xe2BU})\xb4d\x0b}\x11D:E\xe4S\xd6\xa6\xff,p\x0c\xf1\xba\x02\x16#\x0f\xfc\x01XOU\xbfl\x83j\x02\xe2&\xd8\x88\x5c\xf3\xf9<\xaa:\x9c\xa60\x22\xcf\xe8\xff#RU\xd5\xe1@\x84\x80Q\x89\xf6QU\xdd\x03S\xd5\xf6\x1fU\x1e\xb6\x1885\xb4^Y\xf3@\x94\x83`47\xadL(X\x11YGD\xce\x02^\x07\xae\xc4,`\xfd\x7f{\xe7\xaf\x225\x14\x85\xf1\xdf1\xf7Q\x14\x9bE\xb0\xb7\xf6\x01\xf6\x01\xf6\x01\x04\x1ba\x0b\xb1\x11l\xb4\xd2B\x10\x1b\x0b\x85-\xb6\xb1\xd0b\xb1\xb0P\xb0\x16\x15\xacV\x0bY\x16A\x11A\x161\x83\x9f\xc5\xcd\x8d\xd7\xd98\x7fv\x93M\xc2\x9c\x0f\x86If2\x19\xb8I~|\xf7\xdc{\xcf\xe9Z\x8f\x81s\x926$}\xf0\xdb\xaac\xc0\xe65\xb9\x12<\xcb\xb2D\x12eY\xd6pm\x9aA\x90f\x16\xb8f\x82\xf6\x85\xa4\x0b\xc4\x82\x8bo\xe6\x1c~[\xd2go\xb5\x95\xd1u`\x93\xf6\x97\xb36\xe99q\xda\xd5\xba\xc7YO\x08\xb0)4\x90\x96\xc8\xa6\xfd\xa4\x14s\xcd]k\x0e\xd4U\x1f\xdcZ\x12\xb4O\x81\xf3\xc4\xd56\xbb\x0d\x87|%.Et\xcdv\xb1]%\xdc\xee\xc3\xf5\xde?\x81\xffxOL\xf8\xb2\xee\x89\xb0{\x00l\xde\xf5\xcf_\xe9\xf3ys_]KA\xf6\xb7\xa4-\xe24\x9bK\xc0~\xf6\xf5MI^\x0fu6\x5c\xbb\x02\xa6\xf5t?\xbc\xac\x00\xd8\x85>\x027\x80\xab,\x1e\xa2r\xb5\x09\xd8\x1c\xa0\xf9vZ\xf1\x9a\x06\xba\xf2\x81\xad&'\xebZ\xfa\xc1*%\xdd\x03N\x13k!\xbd\x05\xeez\xcb\xac\xa4\xdav\xb1{UO\xe8\x0a\xb1\xf4\xb6\xabO\xc0N\x0f\x5c\x99Y\xbd\x046\x815\x9f\x9a\xe5`m\x15\xb4\x07\x92n\x11\xf3j\xfe\xf4\x16i%D0\xb6\x84\xdb\x0f\x89UX\x8f\xabO\xc4DD\x97+\xc7\xea\xa3\xd1=+\xe4p\x95\x84\x99\xd5\xfby,\xf6\xbf'\x08\xc1[\xb1\x1d\xd0\xfa\xc3\xb0\xba\xd7\xfe\x9b\x99m\x03\x1bG\xf8\xf9/b\xa1\xc1\x9d\x0eC\x0d\xae\xa3\x026\xbb\xc8\x7fmm\xb5\xa8 \x84@Q\x14\xff8\xd7\x10B\x0d\xd5\xc9d\xe2\x80u\xf5\xe1`\xa1\x9b\x84\xdb}\x87\x09\x96\x01\xec\x1e\xf0\x8cX[\xeb\x87\xdf\x16\x03v\xb0\xd3\x8e4A\xb5(\x0a$9D]C\x83+s\xba\xbf\x8b\xc2u0\x10\x96\xf4\xca\xcc\xde\x01ks\xdc\xea\x13\xe051f\xef\x1a\xb8NM\xbbW3\xab\xc3\x04\x1eku\x0d\x14\xb2]M\xd3\x1a\x82\x8bm\xd2.p\x0d8KLe\xe9p\x1d\x0b`\xa7\x1dl\x82j\xeeb\xd3\xbb;Y\xd7H4\xd6\x921\x8f\x80\x83j{BL\xea~\x118C\xacJ\xfc\xc5/\xed\xc8B\x04\x87\x88[\xcd\x1e\xc8\x13i/t\x22\x87\xaf\xcbu\xdc0\xc1w3\xbbSA\xf6\x81\xa4\xfd\xbcg\xe9\x1a\x9f\xfe\x00\x89\x0bY\xd4\x9e\xe4\xf2`\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x13\xdf\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x04\xc8iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a Oliver Twardowski\x0a \x0a \x0a \x0a \x0a \xef\xa3\xbf Made on a Mac! exclusive for Smashing Magazine\x0a \x0a \x0a \x0a \x0a icons\x0a flavour\x0a smashing magazine\x0a addicted to coffee\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski \x0a \x0a \x0a \x0a \x0a\x0a\xee\x04Q\xd2\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0e\xadIDATx\xdab\xfc\xff\xff?\x036\xc0\x02c\xdc\xf5r\xff\xcf\xc4\xc2\xc2\xf0\xef\xcf\x1f\x06\xe5m;\x19\x19A:@\x82\xec\x82\x02\x0c\xcc\x1c\x1c\x0c\x7f\x7f\xfc`\xf8\xf9\xfe\x03\x03\x13H\xf5\xaf\xd7\xaf\x19\xfe\xbe\x7f\xc7\x10~\xec$\x98\x06\xf1\xc1\x12 \xed\x7f\xbe\xff`X\xac(\x0b\xa6A|F\x98\xe5W\x0d\xf5\xe0vh\x9f\xbf\xc4\x08\x10@\x8cx]u\xdb\xcd\xe9?3;\x07X`\xf7\xd7\x9f\x9f\xd2\xf7\xed\xe5g\x0aK\xcc\x9c\xcf\xc6\xcb\xc3\xc0!\x22\x0c\xc6\x1e\x82\xdc| \x05\x8c'\xb45\xfe\x08\x0b\x0b1'=\x7f\xc9P\xaf\xa6\xcc \xff\xf9\x0b\x83\xee\x95\x07\x07\x199\x84\xa4\x94.\xa8I\xdde\xe7\x03+d\xf8\xf9\xe9\x13\x83\xfa\xf1\xd3\x8cL\xdf\xdf>\xbd7\xf9\xf9\xdb\xc7\xdf\xde\xbec\x00a\x83[\xcf\xe6\x83\x14\x00\x04\x10\x86\xab@v\x06?\xbc\x19j\xc4\xc2\xc0\xbd\xf5\x17\xe3\x87\x97\x0eNn\xed\x0d\xd5\xa7a\xf2p\x0d`\x17\xb3\xb130\xb3\xb320\xb2\x001\x13\x13\xc3\xff\x7f\xff\x18\xfe\xff\xf9\xcd\xf0\xf7\xe7o\x86\xd5_~\xbd/?\xb0W\x08\xaca\xb5\xa3\xf3Wc\x1ev.\x16.N\x06\x16..\x86\xc2[w\x18X\x7f\xffa\xf8\xcd\xca\xc2\xd0\xaf\xa6\xc2\xf0\xe7\xdb7 \xfe\xce\x90\xc3%\xb6\x09\x1c\x1e'\x1e0 \x92\x18@\x1a`\x18\x08@\xc1\xae\xc7\xc6/\xba\x94]P\xe26\x1b\xafP7\x90\xaf\x86\xac\x06 \x80p\xc6\x1d.\xc0\x82.\x90j\xeb\xfc<\x85\xfd\xbf\xc8w`\xaa)\xfc\xc3y\xe7\xfc\x81\xad\xda\xc8\xf2p\x1b\x8c\x1c}.\xafb\xff\xa9\xc3\xc8\xcc\xcc\xc0\xc4\x04Nf\x0c\xff@\xc1\xfa\xf7/\xc3l3{\xc7\xce\xa6\x9a\x03p\x1b\x22\x92\xb3g\xac\xe2\xf8\xa5\xc3\xcc\xc6\xc6\xc0\x04\x8c\x0b&\xa0&\xb0\x06\xa0\xe2\x7f\xbf~2\xa4\x9e>\xb4\x1fd8\xdc\x86\xdb\xee\xce\xffY@\x91\x06\x8c\x07fv\xa0\x06`\xc4AR2(\xd2~2\xfc\x05\xc6\xc1\xbe\xaf\xbf>%\xef\xdb\xc3\x0f\xb6\x81\x91\x89\x99\x81\x11\x18IL\xac\xac\x0c\x13\xdf\x7fbx\xf4\xfe=X\xc3d;+\x86\x9f\x8f\x1e3\xfcc\xfd\xcd`\xcf\xfe\x87\x0f\xee\xa4\x9f/^0\xfc\x05F\xda\x7f`\xa4\xe5qs1D\xbdy\x03I\x89W\xaf2\xfc\xfe\xfa\x8d\xe1\x17(\xa6\x81\x11\x07\x02`\xdf\x81\xc3\xf8\xdf\x7fpDE^\xba\x06\xf2-\x18\xcf\xb9\xff\x00\x92\x9e\xfe\xc1\xe3\x09b\xc3\xdf\x7f\xff\xfe3\xff\xf9\xc3\xf8\xf7\xf7o\x86E\xca\x0a`\xa7\x81\xfd\x00\xe4\xff\x01\xfa\xe1?0\xb6M\x1f\xbf;\xfd\x1df\x83\xc5\xd3\x0f\xec\x7fAY\x15(\x09J3 g\x801\x90\x0d\x12\x03\xc9\x01\x93\x85\x19JpD\xb00C26\x083C,c\x00Y\x061\x1f\x9cn\xfeC3\x0b(\x19\xfc\x03\xe3\xbf`~\x0e\xaf\xd4\x96\x1d\xab\x17\xfa\xa2X\x90h\xed\xfc\xbc\x9a\x97I\x02d\x183\x1b\xc8``F\x02&w\x06I)\x86\xe2\xc3G\xc1\xaeg\xff\xf7\x87\xa1\xc3\xc8\x80\xa1\xf0\xd2U\xa0\x05\xff\x19\xb89\xd8\x18\x9a\xe5\xe5 \x86\x03\xcb\xb0\xff\x7f~\x01K\x97?`Kf\x98\xd8\xf8\xf4\xb4\xd4o\x85[p\xc2\xc2\xec\xb7\x00+\x0b\x0b3\x0b$\x0f\x811\xb0xb\x06\xd2\xac\xc2B\x0cI'\xce\x80}\x00\x03\xc6@\x8339\xd9\x18\xfe\x02\x0d\x06%\xf0\x7f\xc0b\x0b\x94\xb0A\x18\x94N\xbd>\xfd;~\xef\xe2\x09+x$\xbf\xfe\xf8\xe9\x17\x0f;\x1b\xcb\x7fP\xd0\xb0B\x0c\xfe\x0f\xb4\x80\x01H\xefz\xf8\x88\xe1\xf3\xd7\xaf\x90\x88\x86\xc6\xf2\xfe\x8b\x97\x18\xd2\xb4\xd5Q,\xf8\x0b\xb7\xe0/\xc3\xf3'o\x1f\xc0\xf3&\xb8Bx\xf5\xf9\x18(\xd2 \x11\xf6\x17R\xb6\x00]\xd2u\xf7\x1e\xc3\xbcWo\xc0\xdenw\xb6gX\xae\xa5\x06f\x83\xcb\xa5\xcb\xd7!\xae\x06\x05\x11(.@\xfa\x80\xfa?\xfe\xfd\xfb\x1b\x94\x101\x22\x19\x98\xdb\x8e\x03s\x9b\x05<\x92\x99Y\xc04<\xf7\xa1E2\xacD\x04E\xee\xbf\xbf\x90H\xfe\xf2\xfb\xcf\x1f\x87g\x1f\x94\x80Y\xf91\xd6d\x0a\xb4D\xa5Q\x90\xeb\x98\x17\x0f\x87(<\x992\xe3I\xa6\x7f\x11\xc9\x14X\xa0\xec\x07\x1a\xec\x847\x1f\xa0YVQ-\xc0U\x10\xc0\xc3.\x8e-\xa3}\xfa\xfb\xef\x8f\xf3\xb3\x0f\xe7\x81\x1cw\xa0\xc1\xef\xb1\x99A\xf3\xa2\x02 \x00\xf1\xd5\x16\x12E\x14\x86\xcf\x5cv*Mm\x8b\x14\xcb$B\xb4\xa2\x88\x08+27\x0d\x02\xa9\xb7.F\x10H/\xf5\x10=\x15\x85\x14b\xa4 \x06\x11\x05\x15Q\x16\xdd\x08_J\x8aX%\xdd\x84\x902\x10\x1f\xacvki\xcd\xdb\xd6n\xa4\xdb\xaa{\x9bK\xff\xb9M\xcej\xa6\x0f\xd2\xc0\xcf\xfc3\xe7\x9c\xff?s\xce\x7f\xbe\xef\x9b9O \xa29\xbe\xe4\xe9\x1a+\xabk\x0b\x9d\xaf:\xeeV\x18\xd1e\x1blFJ\x8e\x88\x94\x9f:R{u\x14{\x99\x10\xc2\xa1\xbc|g\xe3\x9d\xebGf\xbdD\xb5\xf5\x97R\x1e59\xbb\x9f\xa4\xaay\x02\xb9$R=\xa4\x8a\x10\xaf |\xd7\x903\x86F\xde\xe4\xaf}\xf0\xf0\xe6\xd5\x133Jp\xa6\xaa\xa6d\xd5kW\xd3N\x1bJ\xe7\x88*\xb0\x13\xcc\xe2\xd3*\xe2\xa4\xc2\x0e\xe6\xc1\xf8\x82\x9e.\xd7\xf3\xf5\xd3&\xa8\xac\xaeY]\xd0\xde\xd6\xb9]\x91\xd2p\xed\x93\xe0\xec\x1c\x104\x158\xad!\x86\xa4\x0cQ\x99_\x1e\x9f7)\x89e\x93\x17\xb9\x5c\x1dfp|z\xb1xP0\xf8) $l\xe4n\xf1I\x1b\x83s\x18\xd3\xa8\xc4\xd6aY3e\x82\xb2\x03\x15\xcf\xf6\xceGv38\x86\x08\x8c\xac\x0c\xb6M\x845\x8d\xb6\xd1>\x92\x99d\x97\xcfsh\xca\x04\xfb\x7f\x0c805\x9a\xcbB\x06\xcaD\x02\x09\x0c]yp\xec\x0b\xc4\xe4?\xa4\xc4\xc6\xedP\x84t\xac\xa4-ez\xea\xdc\xf9=\xc7\xc8\xa6\xd2\x0d%3\x12\xe9\xac\xaa|}h\x0c\x10\x13\x89\x02\xaaYS\x80\xae\xb8=(\x80\xb7\x0d\x08\xe7bI1\x12\x07\x07\x91\x00\xbe(SL\xc21\x16z?\x95Y\xbe\xe0\xc3g\xdfQ\xb2\x81\xb8L8j\xb2\xa5\xaa+\xda\x8a\x90\x84\xdfK\xe8l\xcfG\x14\x14e\xe2\xe7fe\x22\x1bh4\xbe4|\x1c\x8e\xb1[A\xe9\x96/p\xbb\xddK\xb5T\x98\x94M\xa2D\x13\x07\xb3Q\xb2Q\xfb\xbe\xa2\xfc\xcc,\xd4\x05\xaa\x90L \x12!%zze.J\x04\x03\x16\xa2!d\x03\x96\x13\xd7\x14K\x02\xff@\x7f\x5c\xcd\xb1\x93\xf54\xc0&\xb2\x19\xb6b\xc0\xfcv\xceh\x8c\x0b\xe2\xfd}\x84\x875\x16\xd4L\x04\xe4#&\x12B\xf2&\x0f\x99\x18\x0f\x8e\xc14\x0f;Q\xe8\x82\xc7\xcb1\xda\xbcZ\xfc\x01S\x1b\x91C\x87\xd8X\xf0\x03\xaa\x1eKN\xd0B;$\x9dR\xb0[\xbdti\xb0\x7f\xdcQL\xe5\x0a\xf8\x0d\x81 %\x1a.#\xc9\x9d\x06sE\xe2\xc3\xc9\x09\xee\x855C%=\xb8>\xd6(\x1d\xb6\x8e\x8e!\xfe\xbe(\xe0G'\x1dETO\xc3U\xef\xf5Q]\xa4\xd11\xe4=\xf4\xbd\xf6+\xfa~\x12T\x00{u\x02\x1f\x17\x8aLhI\xac\xc6\xf93\xe5d\xbe\x07\xc6\x04>\xa6\xa2\x0b\xab\x0b\xfc\xac\xaa\xaa\xb1e`8\x0f\xff~&\xc3\xf5\xbeQ]\xff\x92\xa6\xeb2^\x12L\xe0\x1cw\x04\x1c\x8c\x83\x9e\xf9\x87\xc0\xf8\x98\x91=\xc7$\x08\xfe\x96\x07\xb7\x9cd\xac\x02J\x87B\xf7\xf9\xd2\xe8L-hj\xc2R-f\xd5\xe0w\xb8M\xa5}\xf1\x98\xc7\xa1\x88\x1fB9\xa6\x85kX\xaa\xb6w\xcb\xed\xa5\x164\x15\xff\x82\xa6\x13\xecE8\x1a\xac\x1a\x1e\xdb\x06\x13\xf5\xfe\x93p ICsv\xc6\xe1\xc5\xb2d\x9b\x09\x1f\xdc\x18\x19\xef\xbf\x1d\x8en\x86\xe0\xdffL\xfa\x90\xc4\x0e\xb7\xe6\xd6\xec\x8c\x8d\xe9\x12(\xb0)\x18\xed\xe9h\xf4{\xed\xc8\xf8e\x08\x5c7kU\x01\xc1\x96\xe0\xdf!\xb0\x0c\xf8\xcd.G\x92\xbc\x09\xd0v\x05D\x0f\x19\x86\xee\xd1\xe3\xd1f5\x12\xee\x86v\x5c\xf3A\x88\x13\xfd/\xb2\xe5\xb7\x00\xddZklTE\x14>\xf7\xde\xdd\xeen\xdb\xdd\xd2\xad\x14[\xa8\xf2\x08\x10\xd3F\xa2\x16$\x0a\xe1\x19\x1b@B\x9a\xa0\xc1\x1f\xe0#\x1a4&\x9a\xa0`CQ\x8b)/\x8d\x1a\xe3\x8b?&*\x88\xc44)Jh\x03>\xd0\x00\xc6\x07\x06\xaa\xad\xb0\x81\x96\xb6\x92\x02\x05J\xbbw\xbblw\xf7\xcexf\xee\xdc{g\xb7]\x9e\x02\x89\x9b\x9d\xdc\xb9\x8f\xdc=\xdf\xcc9s\xbe\xef\xcc\xde\xf0\x1f\xb8\xa5\xb4\xe8j>+V\xd7,\xea>\xd73\xebBX/\xe9\xed\xd3\x8b\xe2\x89\xb87\xacG\x82\xec^\xc0\x9f\xdb\x93\xe5\xce\x8a\x0d\xcb\xf3\x9f\xca\x0f\xf8\xff)\xbc-\xb8\xf7\x9d\xf55_\xdf\xb2\x19x\xadv\xe3\xe8\xe6P\xdb\xba\x96c\xad\x0f\xac\xf4A\xf0^\x8d\xe6\xf8U\xe4f\x8a\xf4b\x90\xc2\xd2\x0aM \xf6i\x04\x97\x88?\x08\xf4\xbf\x15\x85\x9e\xd2\xf1\xe3~.\x9b8\xb6\xfa\x8d5U\xed7\x0c\xc0\xea\x9aue\xfb\x0f6}\xbc@?_:\xd7M\x03\x01M\xd1\xcc`QE\x88+V\xf4\xc8\x81\x94R\x7f\x90N\xa4he\xcb\x01\x81\x08\x05cO\x1c\xc2\xbbr\x0bZ\xa6\x95Ozn}Mu\xf3\x7f\x02\x80\xf1\xd4\xc6\x1f\xf7o^\xa1\xc5\x8b\x1ft+~\xdb`Uq\x0c\xe7_\xc5Q\xa0\xbc\xa4\x04\x90>\x05\x94H\x00\xac\x14K% \xc4<\x1e\x88\x1b\xfa\xdbFV\xd7\xbc\x99\xd3\x9e\xb5*aW\x0d\x80\xb1\xf7=\xfb~i\x5c\xa2\x9f\x9f47\x0b\xf2\xb8\xe1\xaai\xb0\xa2J\xa3\xce\x8e\x9a\xa4\x0d\xb9\xe5\x16(\x0b\x84H\xf6v\xfd\x84\xd8\x86\xb3\xd4\x03rJ\x17)\x9e%\xcc\xef\x12\xa4\xef\xcb\x9c\x82\xa6\x87\xa6O\x9d7T\x8d%#\x00\xa6i\xeaw\xff\xb0\xfd+?\x1d\xedC\xcb\x99\xf4`#n\x1b.\xfavi\x84\x1b\xaf\xa6\xceJ\x9a;\x0dr\x1b\x22\x04\xb0M1\x88]\x8a\xb3\x81`?\x8a\x08\x1f\x0dC{e\xc5\xec%r\x8d8#\x80\xaa\xd7k\xa7\xd7\xef\xde\xfb\xf9\xce\x00\xbd\x93\xcb&I\xde\xa4\xa8\x90AyT\x91\xc0(\xa2\xa0\xe9\xcc\x80\xf8:\xa3-\xb8\x8d\x9coAR1\xd6L\xf0\xe201\xe8\xc2>\xe8\xa8\xac\x98\xb5l\xe3\xda5\xfb2\x02@\xb7q\xd55|\xdf\xf4\x89\xeb\xe2D?\x06\xa9m8\xd4d:\x92\x1d\x07\x04f\x96\x8c\x82\xca;JL\xc3\x0dS\xe3\x99\x0d\x9fd\x14\x88\x12\xbbp\xaa\x1b\x86\xf1T\xdc\x1bZ<\x7f\xce$t\xa7\xe4\x90y\xe0h[\xe7\x9b\xcb V\xcc\x8dgn#\xdc\x81\x1b\xaf\x09 \xaa\xcb\x99\x05$z\x0f\x8f\x19\x03E\xa5e\xb0\xf5\xf7\x83\x0e\xe1\xc3\xe7w\x5c\x08C\x17R\xa6b\x9f\x17vt\x9f\x03\x0d\x99?\x88\xba\x09\xfb,\x9b2\x19\xee\x8bF\x91Z\xc5\x81\x0e\x9a1\xb4\x8fI\x0dF\xf9\x14\xc2g\xd4\xafj\xdaR\x88\x15\x1dj\xeb\xd8\x80WW\x0e\x09\xe0H\xe8\xd8\xe4'I\xdcG\x19\xa9W\x09w\x09*\x5cD\xa5\x9a\xe9\x03*\x8a(\xd6g\xd7\x85+\x94\x93$\x8c\x981\x1d6\xec\xfdI\xc4\x00\xe5`\x0e\xe8\x11P\xf4~QU2@\xa1f\x8d\xf7\xe5Y3`\xec\xd93\x9c\x03*\x9c^\x1bNY\xd1\xa6\xd0D\x94\xb6\x1c\xd7\xba\x87&\xb3\xb7\x85\x8eO\xcd\x98\x89O\xb4\x1e\xd7\xfc\x05\x1eWR\xd3$\xff6E\x99j\xf1G\xcd){\xb1\x99 \xbc\xaf\xc1\xc8H\x04\xd6\xcf\x9c\x06/4~\x8b\x0b\x08MYR\xad \xc6\xd5\x00\xde_P\x01\x81\xb6VA\xc9\xb9\x7f\xdbF\x13\x19D\x8a[\x99n\xe47\x0c\xd7\x89\xb3\xddZF\x00\xd1\xfeH\xefE\xbfJ|.\xa2\xc9\x00\x14\x95\xbd\xdc,\xa9Y\x05|\xca\xfb\xaa\xe8\xb3\xd1Bi\xfb\xcd.\x18\xa0\x90j\xbcS\x16\xe3\xc7\xda\xfa\x9d\xb0\xe1\xae\xf1|\xf6l\x03\xa5Q\xb7\x00\x90!\x00D\x93\x06a6^\xaa\x06u\xb8%nD\xe4\x8a<\xa5r\xc5F\xce\x9eB\x86\xe1\xcbu\xf4\xe3g\x9aZ\xa0\xd3 \xf6}\xd6lA\x97\xdaZ1\x07\x0a\xb2}\xceR\x89\xf7\xa3\x08l\xf9_G\xa1\x1dc\x84\xa4I\x17\x13\x90\xf5\xac$\xa0\xf1\xbb5\xc2m\xfb\xe2r\x9a\xe9\xa5W\x87\xf9V-\xcc\xf1\x14\xa6\xb8\x91\x22\xfc\x1f\xfb\xdc\xff\xc52\x9a=a\x02\xb8\x83A\xbe\x221\x09\xca\x82\x9b\xeduE[\x9a\xc1\x08\xeb\xfc\x9d\xee\xe1\x85\xe0\xc5e\x15D`\x82\xd8\xf8K\xf6\xf4@4\x14J\xf1wb\xeb\xe6\xd4et\x87\xce\xd5\xcd&T7\xef^\x89&\xdbT=\xcc\xf7\xf8\xa2\x1c\xcf\x08\x1b\x84\xa2\xa4\x89\xc0\xb4\xf3kHd4-\x91\xd1\xb4\xd9\xb5\xee\x0b\xe3?C\xe3_\xb9\x22=\xc0\x1eD\x10\x1d\xa7\x0dR\xb5<\xe0+1\x97OG\xef)82\x0aa\x86S\x1e\xe0\xd7K%l\xc3\xe9`*\xb1\xb9\x8f\x0b\xd6\x8dh\xd3GW\xcdF\x11D!\x1e>]\x9b\x9f]>?\xdb3<#\x99S\x1d2\xa7\x88\x9a\xfc\xa5\xc8\x9c\xb3(P\xdb\xd7\xd3\xc9\x5ccd\x80\xc9x\xcc\x8e\xf0\x04\x1a\xdf}]z\x80\xedP\xe0\xe1\xc3Uy\xbe\xbb\x1f\xc9\xf5\xde~EtZ\x19\x82N\xd3\xcb\xd3\xe9\xbaH\xec\xf4\xa6\xde\xe8\x9f\xf8\xd4\xf3\xe95\x88\xebVd\x08\x84m\xca.e\xa3\xf2b\x9eo\xdcc\xb9\x9e\x22\xcd\xe4\x1a\xd7,h0\xe9\xd1mz\xec\xd4{}\x17[\xf1\xd2\x166\xe3hx\xe2\xa6\x88z\x04T\xc2\xea\xc3\xd8\xe6O\xf1\xb8F\xdd\xefu\x07F\xbbT\xefHM\xf5\xe6k\x9a;\xa8)|\x17\xba\x87\xd0Do\x92$N\x1aF\xac=Ib\xbf\xc6\x12\xe1\xdf\x06\x92'\xf1V\x03\xb6:ks\xea\xa6hb\x1cU\xf6\xd7\x86\x1cl\xd9\x0c\x83\xf8\xab\x83\x07[\x96h.\xd1T)\xcf\x10\xd1\x92\xa2\xc5Ec\x85NV\x8fa\x1b\xebL\xb0\xf4g\xaa\xcf\xfco\xcb*\xff\x02G\xbd\x8aL\x08S\xf3a\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\x9f\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04TIDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91\x02:\xac\xed\x98\xafs\x08t9g\x17ct\xbdw\x97R\x82T\x01\x8f\xae\x1e\xedB\x08[\x99\x8c\xf8\xe8\x9c\x94\xae1\x96v\x15;4\x80\xb8h\xb9E\x0c\x845\xbe,\xd0\xb7z n\x22\x0bG\x1f\xa4\xef\xe7\xb5B\xd7\xcb\xaeb\x8c\xd8\x88\x00\x99\x07\xf3\x08\xfe\xd7\xb6\xe5-@{\xd6\x8e\xc3 \x0cC\xdb\xaa\x88\x13\xb0#\xb1\xb0p\xffs0q\x02F\x16\x16\x90\x18\xda\xbeH\xa9\xdc\x88\xd8NB+*%\x12J)?\xdbI\xec\xf7^\xbe\xfe\x81\xbf\xaf\xf9\xd9\x81\xec\xc0\xc9\xdb]s\x13MM>D\xe9\xa6+Iu\xe0\x84\x02WY\x8c\xceB\xd6p\xe0\xec\xaa\xaa~\x1aYpU\x9b\xf89G\xc4\x11\x80\xf1 \xcc\xb6\x02\xf9\xa2\xccA@\x0d\xceq\xa3\x8f\x80\xe1\x1d\xf3<\xc7O!\x1fl\xe7\x1c\xf0!\x8f\x10\x98\xe9jVIk@2^B\xee\x1c(\xa1j:\x95\x9f\xde\x9b\x12\x0c\x14Se!\xc9hwt0g\xd7u\xfd\x10\xe0h\x8fk\xb8g/\x01H\xe4 \xda\x01\xed\xb4\xc11\x0c\x83\xd9\xda\xd9\x93+q\x8ek\xb8Gkp\xf2\x08h8\x1b\xb7\x0e\xa45\x14\x1a\xed\x5c\x89%\x0a\xe8+T\x1c{\x90X]R%v?N3\x85\x8f\xfal\xdb\xb6+\xb2\xe2|Y\x16l\x92\x069$9u\xd3D\x9a\x8b,\xfd\xaf\xeb:\xa3c[\xdel\xa9\x15\xfa\xbe\xef\xcd.e\xdb\xb6^Q]r(\x18J\xbc\x0cy\xd8J\x1cR\xc8\x10\xe9q\x1c\x0dG\xc7\x9e8\x04zl:\x17E!F\xde\xf6\x18\xa9i\x9aL\xda\xad\xeb\xfa\x9a4\x854,\x99>S\x96\xe5\xa5i\x1as\xd0\xe6\x0a\xfd\x9c\x03\xf6\xb7\xfbL\x14\x1a\xa5/\x0eq\xe4\xa8dp\x88\x03\xb1\x8e\x848\x19\xf3>\x91\xd4SH\x1d*D\xa6\x18\xae\x81\xd2Y\x958C{\x02d\xa1\xeem`\x07\xab\xd9\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0c$\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x00\xaf\x00\x00\x00\xc8\x08\x06\x00\x00\x00\x9ec\xb1\x97\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\x9bx\x00\x00\x00\x07tIME\x07\xe0\x06\x03\x07\x197a\x97\xf1\xfe\x00\x00\x0b\xb1IDATx\xda\xed\x9d[\xacU\xc5\x1d\x87\xbf\xb3K\xe4P\x0f\x88H\x11\x8d \xa0 PQj#RlAk\x9a\x16k\xd4hkA\x81Tj\x9b\xde$\xa4M/\x0fM\xdd\xa8/\xbdh55\x02j\x0d^\xb0$\xbd\x88J\xa1\x89i,\x8d}P\xc0\x14+PD`s\xca\xd5\x00\x86{\xb9\x1d\xfa0s\xd2\x13\x8a\xb0\xd7\xac\x99Y\xb3\xf6\xfe}\xc9$\x07\xce\xde{\xfe\xb3\xd6w\xfe{\xd6\xac\x99Y \xca\xcc@`Z\xb36\xbe\xa2\xf3_jq_\x03\x86H^!q%\xaf\x90\xb8\x92WH\x5c\xc9+q%\xaf\x90\xb8\x92WH\x5c\xc9+q%\xaf\x90\xb8\x92WH\x5c\xc9+q%\xee\x99\xe9\x16\xe03\xfb\x02\xc3\x81K\x816\xe0l\xfb\xff\x07\x80}\xc0{\xc0Z`\xa7\x0e\xff\xff1\xc8\x8a;H\x87\x22\x8e\xbc=\x81[\x80\xcf\x01\xd7\x03\x03\xea|_\xbb=Q\xaf\x02/\x01\xfb%\xae\xc4\x8d\xc5U\xc0s6\xa3\x9e\xc8Y\xf6\x03\xcf\x02\x9fhbq7:\x1e\xbb\xaaT\xac\x9f+\x81%\x1e\x84=U\xe9\x00\x16\x03WH\x5c\xc9\xeb\x936\xe0\x11\xe0h q\xbb\x96\xa3\xc0\xc3]\xfa\xcb\x12W\xf2:3\x02\xf8g\x04iO.k\x1a8\x0b\x0f\x04\xd6{8F\x92\xf74\xdc\x0a\x1c,@\xdc\xcer\x00\xb8Y\x19W\xf2f\xe5k\xc0\xb1\x02\xc5\xed\xda\x8d\xb8[\xe2J\xdez\x99\x0c\x1cO@\xdc\xae\x17se_\xaf\xe5\xab\xab yO\xc3\x0d\xc0\xe1\x84\xc4\xed,\x871c\xc9\xca\xb8\x92\xf7\x94\x9c\x0flKP\xdc\xce\xb2\x03\xb8P\x19W\xf2\x9e<\xb7\xa1\x05x\x01\xe8\x9fp\xcc\xfd\x80y6\xd6\xb2d\xdc\xa5h\xaeBp\xa6%\x9cqO.w6y\xc6m\xfa\xcc\xdb5{\xf5\x04\xd6\xd9nC\x1e6a\xe6*,\xb6?o\xb6\xff\x7f\x91\xcdB\x13\xed\xf0\xdb\xc0\x9c\xf5l\x03\x86\xda\xa1\xb4T3n\xaas\x15\xfe\x03\xec\xb1e\x17\xb0\x0ax\x1b3\x96\xff&fh\xb4T\xfc g\x06XoG(Z\xea\xfc\xa3\x99\x0cl\xc8Y\xe7\xf7\x9a\xec\xe2,F9\x84\xb9\xfd\xff\x1d\xea\x9fdU(\xdd\x81\xad9\x1a\xfc8p\x96c\xbdsr\xd4\xbb\xc5\xb1^\x89[_9\x0e,\x02n\x22\xe1\xb9\xdf\xb7\xe3>\xf6z\xaf\x87\xfag\xd8\xcfr\x89\xe1V\x89\x1b\xa5l\x04\xee!\xcc\x1c\xf0\x5c,tl\xd0,\x8f1<\xe0\x18\xc3\x1f$n\xd4\xb2\x16\x98\x94\xcahO\x0f\xdb\xcf\xc9\xda\x88W<7\xa0b/\xf2\xb2\xc6q\xd0v?$n\xdc\xb2\x14\x18V\xb4\xbc\x9fu\x08\xfc\x08f\x99\x8fo\x86\xd9\xcf\xce\x1a\xcf\x04\x89[\xd8\xc5\xdd\x8f\x8b\xecJ\xfc\xd4\xf1\x02-\x14.\x17p?\x91\xb8\x85g\xe1Bnl-p\x08vl\xc0x\xaeu\x88g~\x83\xde\x80(Sy\x1f3'&*oe\x0crk\xe0a\x93\x0a\xb0=cL\xcb\x95q\x93(G\x81\xe91OD\xd6I8/G\x88\xe9O\x19c\xda\xac\x8c\x9b\xd4\xd4\xd5j\x8c\x13Q\xc1\xacM\xcb*{h\xb6d|}\xaf\x88\x19W\x93lNO\x0bp\x1f\xf0\xb3\x18\xf2\xb6f|\xcf\xae\x08\x07 \xeb\x86$=\x22\x9d\x98yh_\x85z\xf9a\xe8\x0c\x5c\xc1\xecb\x93\x85\xf3\x224\xfcc\x19_\xbf'\xd2\x09\x99\x86\x99\x8f!\xea\xe3>`fHywg|O\x8c\x89\xe0Y\xeb\xf8 \xd2\xc9h\xb7W\xd45yY7\x0fa\xa6\x1f\x04aY\xc6\x0e\xf9\xb6\x04G\x1b\xde\xd0hC\xd2e/f\xff:\xef\xa2\xfc+\xe3{\xfa\x13~\x9c7\xeb\x9c\xe25\x91\xe5\xada\xee\xea\xa9\x0bQ\x1f=1\xf3gz\xf9\x96w\x99\xc3\xfb\xa6\x06l\xe8\x14\x87\xf7,/\xe0\x84\xa8\x0b\x91\x8d\xcb\x80\xc7|\x7f\xe8\xa7p\x9b\xdb04P\x03]\xe66\x8c)\xf0\xa4h\xdc7[\xf1\xba\x81Lw{\xd1\x965\x88E\xf8\x9fU\xe6\xb2\x81\xdf.\x8a\x9f\x90\xae>p\xfd\xa5\xddv#\xbc\xf1\x84c \xf7{\x8c\xe1A\xc7\x18\xe6$\xf2\xb5(\x81\xeb/\x8f\xf8<\xf0\xd7\xe1~+p\x86\x87\xfag\xe2\xbe\x92b|B\xfd:\x09\x5c\xff\xe61\x83}~e\xaf\xce\x11\xcc\x5c\xdc&\x84\xb7\x02O\xe6\xa8\xf7\x1d\xd2\xdb\xbfA\x02\xd7W\x9e\xf1y\xd0\xef\xc8\x19L\x0d\xb8\xab\xce1\xe0\x8a\x1dU\xa8\xe5\xac\xf3K\x89^YK\xe03\x97c\xc0H\x9f\xd9w\xa5\xa7\x0e\xf9\xaf1\xfb3\x8c\xc0L\xfci\xb3?\xdfh\x7f\xd7\xee\xa1\x9e\x7f\x90\xf6\xae9\x12\xf8\xcce\xb6\xcf\x03>\x81\xb4v\x86<\xdd\x92\xec\xf1\xa4O\x0c\x81\xab\x0eq\x9d\x03\x8c\xc2\xec:\xb4\x90\xe26U\xdc\xeb{\xe4\xe1\xe1\x12\xc8\xfb\xcb\x12\x0d\xce\x87\x16\xb8\xea!\xc6~v\xc4\xa9\x88\xc4\xf5M\x9f\x07\xbb\x95b\xb6\xf0\xaf\xb7\xac$\xfb4\xceF\x16\xb8\xea1\xce\xab=u\xe9\xb2\x94\xd7}\x1f\xec\x8b1\x13\xc2S\x13w3\xf9\xf78k4\x81\xab\x9e\xe3\xbc\x00\xb3_Y\xcc\x0b7\xef\xd3l/w\xbc\xf3\x16\xaa\xec\x01FSn\xca\xb23z\xaf\xc8\xdf\xbeSB\x1c\xec1\x98\xcd\x9c\x8b\x16w;\xc5\xce_H9\x03W\x03\xc5y\x09\xe6\xd6{\x8c\xf3\xfb\x5c\xa8\x83=8\xe7\x0d\x8c\xbce\x1da&\x015J\x06\xae\x06\x8c\xf3\x1b\x91\xce\xf1\xaa\x90\x07\xfb\x5c\xcc\xde\x08\xb1\xc5}\x1e\xe8Mc\xe2+\x03\x87\x94\xb7\x02\xac\x88p\x9e\x8f\xc6\xb8\x08\xbf\xd1^4\xc5x\xee\xc4m4>>2p5p\x8c\xb1v\xcb\xbf\xca\xe5/+\x0b\x8b\x81\xa7\x22\x9c\xd4\xd9\xc0\x1f\x9b@\xdev\xcc\xd3\x8dR^\x91\xf1\x22qvJ\x1f\x18Z^\x11F\xe0\x94Wd\xec#\xce\x1a\xc1\xde\x92\xb7\x9c\xd4H{M\xdc&\xc9+\xca\x9a\x81cl\xa7\xd5K\xf2*\x03\x87\xe0x\x84:*\x92W\x17qM\x83\xe4\x95\xc0\x92WH`\xc9+$\xb0\xe4\x95\xc0\x92WH`\xc9+$\xb0\xe4\x95\xc0\x12X\xf2J`\xc9+$\xb0\xe4\x15\x99\x05n\xea\xfd\x81%o\xb9\xa9a\x9eP$y\x85\x90\xbcBH^!$\xaf\x90\xbcBH^!$\xaf\x90\xbcBH^!$\xaf\x10\x92WH^!$\xaf\x10\x92WH^!$\xaf\x10\x92W\x08\xc9+$\xaf\x10\x92W\x08\xc9+\x84\xe4\x15\x92W\x08\xc9+\x84\xe4\x15\x92W\x08\xc9+\x84\xe4\x15B\xf2\x0a\xc9+\x84\xe4\x15B\xf2\x0a\xc9+\x84\xe4\x15B\xf2\x0a!y\x85\xe4\x15B\xf2\x0a!y\x85\xe4\x15B\xf2\x0a!y\x85\x90\xbcB\xf2\x0a!y\x85\x90\xbcB\xf2\x0a!y\x85\x90\xbcBH\xde\x93\x99\x0e\x0c\x91\x02\x92\xb7\x8c\x0c\x04^\x93\xc0\x92W\x02\x0b\xc9+\x81\x85\xe4\x95\xc0\x92W\x02\x0b\xc9+\x81\x85\xe4\x95\xc0B\xf2J`\xc9+\x81\x85\xe4\x95\xc0B\xf2J`\xc9\xfbatD\x88\xeb#\x09\x0a\xfc\x17`\x90\x94)\xb7\xbc{#\xc4ua\x82\xc7j\x10\xb0T\x19\xb8\xdc\xf2\xee\x89$J\xaa]\x08e`\xc9{Z\xc6\x01\xbd\x12=f\xca\xc0%\xe6\x93\xc0\x89\x08ej\xe0vTs\xc6\xb7\xa9I\x04\xfe\x22\xb0%\xc2\xf9\xde\x01\xdc\x1e\xba1=\x80c\x11\x1a\xb3\x22\xf0hH\xd5C\x8c\x1b\x1b\xb8\x0b\xd1\x07\xf8m\xa4D\xd5\xb5\xbc\x00\x9c\x1b\xb2a\xab#5\xe4\xeb\x89\xcb\xdb\xa8\x19x\x08\xb0\xa6\x00q;\xcb:`X\xa8\xc6=\x1f\xa9\x11;\x81K\x12\x97\xb7\xd12\xf0X\xe0\xfd\x02\xc5\xed\xda\x8d\xb8&D\x03\xa7Fl\xc4\x9a@_#U\xcfq6B\x06\x1e\x05|\x90\x80\xb8\x9de\x0f0\xdaw#\xfbF\xea\xf7v\x967\x81\x0b\x12\x97\xb7\xec\x19x\x10\xb05!q;\xcb\x16\xe0b\xdf\x8d\xfd{\xe4F\xb4\x03W'.oY\x05\xee\x01\xbc\x93\xa0\xb8\x9d\xe5m\xa0\xd5g\x83\xbfU@#\x8e\x03O\x00\xfd\x12\x96\xb7\x8c\x02\xff*aq;\xcbC>\x1b\xdc\x13s\xab\xb8\x88\x86\x1c\x06\x16\x02w\x02W\x00\xe7$&o\x99\x04\xbe\xce&\x85\xd4\xe5=\x0eL\xf0\xd9\xf0\xd9%ht\x91%u\x81+\xf6+9o;k\xc0\xa3\xc0\xe7\x81\xe1@\x9b-\xc3\x81/\xd8\xdfm\xf2P\xcfJ\x9fc\xff##_\xb8I`\xbf|%g\xdb\xd6\x03\x93\x81\x96:\xeaj\xb1\xaf\xdd\x90\xb3\xce/\xfb<\x00\xcfH\xd0R\x0a\x5c\xc9y#\xe2q\xe0,\x87z\xbb\x03sr\xd4\xbb\xcag\xf6\x1dl\xfb\xa0\x92\xb4\x5c\x02_\xef\xd8\x8e\x0e\xe0^\x0f\xf5\xcf\xb0\x9f\xe5\x12\x83\xd7\xbe\xef\xa3\x92\xb3t\x02?\xe9\xd8\x86Y\x1ecx\xc01\x86\xb9\xbeG\x1e\xda%gi\x04\xee\x0e\xecv\x88\xfd\x95:\xfb\xb7Y\xba.\x8b\x1d\xe2\xd8\xe5\xd8e\xf9P&J\xcc\xd2\xdcJ\x1e\xe7\x10\xf3\x11\xe0\xd2\x00\xb1\x0c\xb3\x9f\x9d5\x9ek|N9\x5c\x82\x99\xb0#\xceL\xd1+2\x5c\xeeT>\x05\xbc\x17 \x96w\x81\xa7\x1d\xde7\xc6w m\xf6jP\xd95\xed\x0c<\xdf!\xd6\xb1\x01\xe3\xb9\xd6!\x9egC\x042\xa2\xc0;o\xea\x03\xd7\xc7\xb2\x8c1n%\xec\xc2\x80\x0a\xb0=cLo\x84\x08h\x0df\xcb\xfc\x0eD=\x0c\xc2\xec\x0b\x11S\xe0\xacSLW\x04>\x9f\x1d\xb6\x8e,\xf4\x09\xf5\xd7\xf4{\xe0\xbb\xf22\x93\xc01\x17u\x9e\x97\xf1\xf5[\x22\xc4\xb4%\x15y\xc1\xcc{\x98%/3]\xc4=\x1d\xa9\xae\xb6\x8c\xaf\xdf\x1d!\xa6\x9d\x19_\xdf+\xf4vOU\xe0\xe7\xf2\xb2.j\xc0W#\xd5u(p\xa6v\xa1o\xd66\xc4\xd8\xab\xecG\xc0L\xdb\xc9\x16\xa7\xa6\x1d\xb8\xc1\x0a\x1c\x83\x03\x19_\x1fc\x07\xa3\x8b2\xbe~o\xcc\x134\x1d8\xaa\xd1\x85$F\x1b\xde\xca\x18\xe3\xb6\x04G\x1b\x96\xc7\xce0\x9f&\xce\x06\x16\x1a\xe7==\x0b\x1cb\x1d\x170\x9e\xcf8\xc43?\xf6\x16\xa7\xafcv\xdc\xf9\x9bz\x0a\xd40\xb3\xa36\x14P\xf7j\x87\xf7\x84\xdc\xc1h\x8a\xc3{\xd6\x14u\xe2\xba\xd9\xbe\xf0Au\x15\x0a\xc1e:\xe4\x11`h\x80X.s\xecN\x8e/:\xfb\x0c\x05\xfe*q\xa3\xd3jG\x1c\xb2\xc6\xbe\x08\xff\xb3\xca\x968\xc4q\x0033\xaepZ0\xcbQ\xd6J\xdc\xa8\xbc\xe8\xd8\x86\xfb=\xc6\xf0\xa0c\x0c\xbfK\xad\x0f\xd8\x0d\xb8\xc7\x9e`\x89\x1b\x9e\xdbp_I1\xc3C\xfd3q_IqK\xaa\x172\x15\xcc\xb6\x9a\x8b(\xc7\x92\xec\xb2\xaea\xeb\x9es\xe4g\xae\xe3Ww+\xee\xab8N\x00\x9b\xf1<\x11=\xe4\xc0\xf5\xb71\xb3\xed\x0fI\x5c\xef|?g\xdbj\xc0]u\x8e\x01W\xec\xa8B-g\x9d3\xbb\xf67\xcb\xc2G1\x93\xa8Ga6\x1a\x19\x89\xb9m\xd9\xdb\x96\xd6\x04c\xae\xd9+\xfbZ\xa2\xc7\xb4\x0d\xb3\x9dh\xff\x9c\x9f\xf3o\xe0%\x9bdj\xf6\xdf\x00\x030\x0bt'\xda\xaf\xfa\x019\xeb\xd9j/\xf2\x0f6\xfb8k\x95\xc6^\xea\x93e\x8c\xb5,\xdfb\x93\x10\xc1\xe5-\xd3^e-\xc0\xab%\x10\xf7\xcfR6\xbc\xbce\xdc\xa7\xf7|\xd2\xdc\xde\xb4\xb3l\xe7\x14[\xdc\xea\x09\x98\xfe\xfb\xb8E\xdd\xf2\xcd\xc3\x0e{\xe1u$\xc1\xd8\x0ec\xee\x03l\x93^\xe12o#\xec\x8c>)\xb1\xe1\xc9\xe3\xc0\x1dR5\xac\xbc\x8d\xf4L\x8a\xbbIc\xea\xeaQ\xe2M\xceoZy\x1b\xf1qV7c\xe6\x0f\x14%\xee~\xe0&)\x1aV\xdeF~\x90\xe0p\xfc\xec\xdd\xeb\xf2\x00\x9dQ\xd23\xac\xbc\x8d\xfc\x00\xc1N\xce\xc6l\xa7\x1f\xa3\x1bq\x04\xf8\x05\xe6f\x94\x08(o3\x88\xdb\x95\xcb1sM:\x02H\xdb\x01\xbc\x0c|\x5cJ\x86\x97\xb7\xd9\xc4\xed\xcah\xccf\xe2\xfb\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x05\x0cIDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91]\xb9\xaa\x0e\x5c\xa1`_Y<\x0d@\x1b\x8e\xc51\x8a\xa2\x9fz\x16ZU'~\x0e\x88\x93\x10\xc1x\x08f\xbd\x02Q^\xb6\xf1M\x8e\x03Q\x22[\xf7p\x18\xde\xb1\xae\xebyFG\xd1v\x0e\x00G#]\xb4\x9f\xaaY]\xa2\xa4.\xe3m\x92\x81\x13@\xb6g&y1\xc7S\xa2\xeaP\x04\x5cF\xdbz\xec\x0b,\xcb\xb2\x85\x1fS0\x8ec\xcf\xf7}\xb2\xdcE\xdd\xbb\x0c@:mp@\xee\x8c\xe3\xe8\xd5u\xad4V\x92$\xdbXT\x0f\xfb\xbeW\x8a\xa5(\x0a\x92:~5\x02\x12\xcd\xa6\xcf\xa1\x1b\xe0\xf1\xb6m\xad`\x01\x08G\xd7u*B\xd0\x146\x83o\xdf\x8b\xa1\xda<\xcf\x9b>\xe4\x1a\xa2\x83\xb1\x7fZ\xc0\xa1\x94\x18\xb5Pq\xaa\xc1\xa5\xea.\x01\xb0}\x9c2\x18\xe2\x19\xd5r\x9b*1\xaf\x87a\xf0\xd24%\x81\xed\xcf]\xa0^\x12Os\x9e\xd5\xf7\x9a\xa6Q\x05r\x14I\xb0we\x1a\x8d\x1e\xe2\x1d\xcf\xc20\xf4\xaa\xaab\xdfu$*o\xe9t\xb1\xe5\xea}\x1e/\xcbR\x19\x07\xe9B\xee\xbe\xa6\x07LJ-\xa9\xbbJj\x82\x12\xc3%T\xfa\x91\x94\xff\xa1}\x00\xeeU\xc1\xd6Y\x5c\xc9\xf4\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x12\x5c\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3>a\xcb\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x11\xfeIDATx\xda\xec\x1dKl\x1b\xc7u\xf6GR\x9fFR\x92\xba@\x93\x149\x06=\x09\xf0\xa97\xe5\xd4\x18E\x13\xb5\xb7\xa4A \xdb)z(\x82\xc8I\x8a\xc4\xf2O\xb2\x1d\xc7\xf9\xb5v\x80\x06\x05\x1a\xa0v\xcf\x05J%=\xf4\xd0\x22\xd2%9\xa5\xa0zk\x03'rk\xd7N,QTD\xf1\xb3\xdcO\xdf\x9b\xcf\xee\x90\xa2\xa8\x0f?\xbb\x5c\xcd\x03\x06\xf3\xd9\xe5\xeep\xe6\xfd\xdf\xcc\xac\xe6\xfb>QppAS\x08\xa0\x10@\x8d\x82B\x00\x05\x0a\x01\x14(\x04P\xa0\x10@\x81B\x00\x05\x0a\x01\x14(\x04P\xa0\x10@\x81B\x00\x05\x0a\x01Z\xfdX\xd3\x82\xf2\xf0\xf3\xbf{\x0a\xb2q\xcd0\xc5E\x9e\xeb\xf5u\x05\xbb\x031/A\xee\xf1\xcc\xc5,W\xfc\xfd/\xe6\xc3[\xfdh\x10\xe0\xb3\xcf>#\x13\xbf\xfdt\x04\x8aY\xcd\xb0&\x0cC'\x86iR\xc4\xd0\xa0\xacC\xae\xeb:\xab\xebXgH\x13\xe4P\xd0\x826\xad\xe1\x1a\xe4Dkr\x9f|?\xa9\xff\x9d^\xff\x8cf\xf7\xe3=\xc4g\x83\xe6\xf3\xdc\xc32\x1fk\xd6\x8em\xcd\xcaa\xee\xf1k\xe13\x9a<\xb3\xc5\xfd\xb4\x8d\xf0:\xfc\xd8#\xe1\xfb\xe85\xcf\xe3\xd7<^w\xe95\xd7u A\x83c/\xc0O&\x17~\xf9\x83\xf5\xc3\x87\x0f\xef{\x0e\xcdv\x90\xf4\xa1\x87\x1e&\xc4\xb0\xb20\xc0\x13\xe9\xa1ab\xc0\xe0\x9a\xe9\x0c\xcd\x8dT\x8a\xd5\x01!h\xdd4\x88\x09\x93j\x18\x06\xab#\xb2`\xc22\xcc\xb6\xc9\xdbp\xb2MzMc\xbf\xd7\x19\x12\xb1:\xdc\xc7sQg)\xfc\x8dN\x9f\x83\xbf\x0b\x9f\xad\x1b\xfc:\x7f\x07\xce\xb6\x0b\x03\xebz>\x1dL\x8f\x97=\xac\x8b\xf6\xa0\x8e\xf7\x84m.P\xa2\xe7J\xf7\xba\xd8\xc6\xee\xc16\xcfg\xcft\xa4\xe7\xe1\xfd\xae\xb8\xdfo\xf2N\xf9\xf9A\xbf<~\x0d&\x1c\xb0\xc3\xad\xd5X\xbb]\xa1\xb9]\xda\x9c\x80Gea\x0e\x1e\x8fL\x04\x0c?\xf7\xeeSZz8k\xc2\xcc\xbex\xe40\xf9\xee\xe8\x10P\xbe\xc1\xa8M\xd7\xa5\x5c\xa2jMo\xa0z\xb2\x85\xfa\xc3{\xeb\xdb\xe4{\xb7\xfb\x1dJ\x1c\x9d\xc8\xf9\xd6{ej\xf7x\xa5\x91\x8a)u2\xb2\x0c\xcax\xebV\xca\xaf\xa7j\x9fs\x13\xda\xee\x85\xf5f\xdc\xc4\xa3?\xa8\x7ff\xfd\xf3\xf0~\xf6\xe2\x80#\x00B\xdc.\x94\xc8{\x7f\xfd\x07q\x00\x91\xfc\xf2\xfad\xf1\x8f/\xcfG\xc2\x01\x98\xcc7\x08\x9d-\xdd\xa4\xf2^\xd3\x0d*\xef\xb1\x9d\xb1\x5c#d\xbd\xc1\xe45a\xe5z\xb3v\xf9\x9a<\x89MD\x82\xbe=b\xd0\xeeI\xef\x16r\x13\x07[\x97&D\x93&B\x93&\x82He-\x98\xa8\xc6z\xfd\xc4Q1\xa3\xfb\x12\x921d\xd2}\xc6\xee\x83\xb2\xc7\xde%\xae\xcbeZ\xf7\x10\x81 \xd7<**(\xd5\x1a\x16\xd1\xac\x0c\xfc/\xe4.\x83\xe3\xd0\x14\x19\x02\x04\xf0\xfe\xe2\xbf\xe8$X\x99A\xc6\x82Si\xc6\x9a-K\x12\x01Z \x02\xccF\x11`hA=\x14\x01\xa1hh&\x02(\x9bo\xf2\x9b\xf0\x9aTo!\x02\xdcF\x11\xe0ne\xc72\x8b\xaec\xe9\x92\x08\x90Y\xba\xd3\xeay^(\x02\xe8\xb5\xba\xe7\xbbu\xfd\xf2\x1c^\xaf\xd94w\xb8\x08`\x98\xd0\xfe\xbc\xb5\x8f\x00\x1aS\xf2\x00-\x19\xbb\x05N@s\xc4R\x9a\xf3:*\x83[r\xbd\xc5\xb5\xdd\xe6{\xff\x0d\x22\x80F\xc7\x8fQ\x5cP&\xbc\xecKezO\x8b\xeb\x9cj\xeb\xee\xf1wz\x9e\xcf9\x18\xe4z\x93\xfb\x83~\xe1\xf8i\xf4>\xce*(\xa7\xa5\xd7\xd1\xda\x02\xae@\x8c\xf6\xa6PW\xf6\xd6\xc1\x06S\x0dA\xdf\xb8lBwJ\x07]*\x8a\x03(\x0e\xd0\xae\xc7\x0a\xcd\x13\x94\xab.\x14A:y\x0e\xf1\x01M}\xb7\x06\xed\x90\xe3%\xccAG\xa0R\x8e\x9aa\xa2\x9d_k'\xf7H\xf8\x5c|/\xd9&\xd7\xc2\xfb\x88p\xb0\xa0\x86M\xd3Ne\x9eS\x0d\xdf\x93\x1c6\xfb+\x87\x1d\x0f\xdb\x85\xd6\x8f\xef\x22\xc1;=\xea\xf9\xa3\xda?\x98\x7f\xb4\xddeu\x9f\xd6=\xd6\xae8\xc0Au\x17G\xcf\x01r\x88\x91\xa8\x9d\xfaN\x0d\x8dm\xe2\xd7\xaa\x80\x9d\x1au\x9e\xa0\xd6\xed\x01G\xa0\xda\xb7k\x10Og.b\xac\xbb\x8e\x01\x1a,5\xda\xa9\x1fA34\xeeO\x80\x1c\xee\xf1\x0d\xe4\x12\xac\xeeC\xdd\x83\xba\x07u\x1f\x9f\x89\x89\xd7\x85\x19\xe8If\x1fz\xfe\x5c=\xf4&\x1a\xbc\xae7\x98\x81^\x9d\xf7\x8d\xd4\x99m\xf5^\xc1z\xf3-4\x03\x85w\x10\xda[\x98\x81\xde\x16/_\xe8e\xf4d\x93P*{\x0e\xe3\x00\x1e\xa7x\xafVc\x94oW\x19\xd7\xa8Uh?|\xbb\x94\x8b\x8c\x03,\xbe8\x81\x0e\x88\x05\xea\x9bV\xd0S\xe0\xbe\x80\x85[\xef=?\x1f\x19\x07\x18\x18\x18@'\xf5\xa4E\x9c\x85j\xd1\x1fG\xear\xaaU\xea\x86E\x0f #\xeem\x82A\xba\xd6\xd2\xad\x1bx\xff$w\xae\xb8\xae7\xf3(n\x09,\x91m\xbd\x89\x84H\x9e\xbb\xba@L\xf3\xe0M\xa3\x97o\xab\x1b\xb8Ep\x88\xbb\x91\x83\xebD\xe8-\xad\x03FA9\x08\x061\xfd\xc1\x05N\x8b\x04\x97r\xcb9\x9b\x98\x93\xed\x86\xf3\xdbB\x80\x91\x91Q\xf2\xb4\xf9\xcf+\xd0\x89\xf1\xffx#d\xa5\x96\x06\xf6kps\x85\x86\xf3XNB_=u\x07\x07uf\xdeh\xda\xf6\x89M\x9c.!\x8c\x1e\xb4\xe9\xba@,\x9d\x22\x9a\xce\xd9\xbc\x8e\xc8\x17\xe4:\x0f&\x89\xdc\xe0Q5\x8fF\xd8X.\x92+\x95\xbd\x80=\x8b\xc1g9k\x17~\xfaP9l\x9d\x84\xfb\xd9'a\xec\x81\x89q?\x90\xe7>\xf1\xeb\xe4z\xa00\xd22\x0b\x07\xa3Hx\xd0\xa8\x92\xef\xe9\xeb\xe30>W\x08\xf9\xd9\xd1\xc8\x82A\xc7\x8e\x1d{\xd14\xcd+\xc5\xcd2\xb1,\x8ba\x94\xc5p\xca@\x0f \xa5<#\x98H\x16\x1c\x12uy\x22YT\x8fM\xa8\x11L\x94!r\x9at\x8a\x5c\xf8\x5c\x13\xdd\xca\x90S\xf72\xb6\x99&\xb1L\x8b\xb5C\x19\xdd\xcf\x16\xb6Y\x16\xadcnY)H&I\xa5Rt\x22k5\x9b\xd85\x87\xd4l\x1b\xca5Rs\xb0\x8c9P\x18\x96\xe1\x9a\xe3\xb0\x84\x119\xc7q\x89\x83\x919\x9a\x03\xa2\xd0(\x9dG\xeb.\x97\xd5\xae@ D*\x81(\x02\x99\x02\x04\xf2\x03$\x22\x9c\x8b\x10\xd2\x10G\x90\x83G\x1e\x9bx\xd7uY\xee0\xad\xdf\x81~\x0e\x0f\x0db\xfb\xf4\x07\x1f|p5*%p\x14)\x08\xbb|\xe4\xc8\x13dll\x8cN\xa2\xe0\x00\x82%3*g\xf1}\x22\xb1\xe5z\xea\xaf\xbf\x1e\xb0o]f\xf7\x9c\x13P\xb1\x22\xa2\x8d\xac]7\xf4\xba\xeb\x06\xe7\x02ZP\xe6\x1c\x82+\x81\x94\xfa\xc5dQ\xc5\xcb\xe5\x14\xcf&\xd1\x97\xa8_P\xba\xc7Y\xb2\x17\xb0f_b\xdb\x0d\x14/\xd8\xbf\x1cP\xe2u1\xe1Dp\x04N\xf9\xbe\xa4\xda\x87\xebAx\x9d#\x02\xbeomm\x8d|\xf8\xd1G\x94{\xe1\x1cDi\x05dS)k\xb6\x8cr\x9f\xcd]\xc0\xf2\x0d\xbd\x99\x08\xd8\x8a\x04\xf5uy\xb2\xb5@w\x08\xeau!f\xad\x1e\x19\xeaD\xc0\xd6IGNdH\x08\xe0z\xf0;\xa4d\x9d!\x81\xe6i\x1c\x01\xb4\x80\x8a\xe9\xbb=\x8f&\x1f\xeec\xd1;x>\xde\x87\x16\x89\xef\xb1h\xddN\xec_\x0a\x09\xcb\xac\xbd\xe5\x847\xb2~\xae\xafk\x80\xa0\x82x\x90\xa3U\xab\xd5ld\x22\xa0PX'33'?.W*\x13\xc2\xa0\xb0\x80\xc5\xd2\x1c\xd8n\xbd\x08\x08W\x08\xe1\x05d\xeb[D\x00\x9f\xa8\xa6\x22\xc0\x14\xa2@\x88\x00\x83\x8a\x04\xca\xf2\x03\x11`R\x11dI\x22 L)`\xffV \x02l\x1bE@\x8d\x8a\x00\x9b\xb3~!\x02\x1cl\x97D@ \x06\x90\xcds\x11\xc0V\xe6\xb8ME\x80\xacW\xb4\x12\x01!\xcb\x97\x94GN\xe9\xe2\x9a`\xfd\x0eg\xfd(\xae\x08\x0f\x18\x0dd\xd2\x0b\xc7\x8f\x1f\x7f\xbc\x9d\x15Am;\x82\xa0\xc3\xb3).\xff}B\xea\x947=\x90\xf5a\xd2D\xaeim&}\xcb\xb3\xbb\x994\xad\x13}\xde9m\xff~\xaeD\xf3\xe8c\x0a\x10\x1d\xc7>\xf2X\xc0\xe5\xcbo.B\xc7\xb2\x83\x83\x19e\x9c\xf7\x08p\xacq\xcc_x\xe1\x85E\xe4z\x91\xbb\x82\x81\x9dN\x01\xb5/\xe3\xa2\x8b\xd0l\xea\x88\xa7R\x01\xe1\x22\x82\x8b\x13\x5c~\x07\x1ca\x19\xc7\xfc\xd0\xa1Cdtt4:\x04\x10+\x9d\xde\xfd\xf5o\xd6!\x9b\x04\x99T0\xb9\x1f@A\x17\x22w\xa0\xfb\x0cd2\x05\x1c\xeb\xb7\xde~g\x1dM\xe6t&\x13!\x07\x08\x22[>9\x7f\xfe\xc2\x12(*\xd3\x99\xb4\x05U\x97)H\xe8\xc7\xf6\xbd@\x93U\xb0\x97\xa1\xf5\x03\x87\x95P*\xd3\xe9\x14*\x83\xd3\xe7f\xe7\x96B\xa4\xb0\xe2\x13\x0d\xbct\xe9\x8d\xeb\xe8\x9d\x1a\x1a\x18 j\x1bH\xe7\x00\xc7rhh\x00\x95\xea+\x17.\x5c\xbc.\x13\x1ei\xd3\x15\xdc\xf1p\xf0\x993gO\x00\x96.\xa0>\x80&\x0b\xf5\xa09\x8a\x13\xec\xc1\xaa\xa2I\x98\x9f8\x868\x96.\x8c\xe9\xcc\xcc\xa9\x13\x9d~_\xb7\xd6\x03L\x82\xcd\xbd,\xccC\x05\xfb\x874\xf5]XT\xeew\xe3\xf9\x1dQ\x02\x1b\xd3\x85\x8b\xaf\xaf\x97\xcb\xe5IBw\xd18\xd4\xef\x1er\x02\x9f\xa8\x0d\xa9\xadd>\xd3\x9d\x90\xf2\xd1\xb9\x84\x0e\x9fJ\xa521;w~}\xbb\xf1\x8e\x85\x12\xd8\x98@V\xa1\xa2258\x90a\xee^\x05{\x86a\x90\xfb8\x860\xf9K\xddzGW\x97\x84\xa1\xc2R\xadV\xaf\xe1\xbav\x1aq\xc3\xfdm\x1c\xc3\x15lO\xf9T\xee\xc3\xcc\xe0\xd8\xcd\xce\xce]oEh\xb1S\x02\x1b\x01\xcc\xc3\xa3\x86\xae\xe7\xd0\xf1\xd2\xcbKIC\x80x:\x82\x1a>\x9f\xb2\x0f\xc8m\x14\x8b\x8f\x8e\x8e\x8du\xa4;U\x16s\xc8%q\xdbz\x22?\x1d\x0b\x94\x9f+\x16\x8b\x1d9\x97\x10\xbf\xec\xc3?\xd3\x92K\xe2X\xa9o\x07\x1fpH$\x02\xb0\xe8\xa0MY\xb7\xf8 \xe4~u\x00\x22>\xef\x96\xd0\xf3\x8d\xdb;$J\x1d\xfa\xac\x94\xc0\xee\xe8\x80~\xbbJ\xe0\x96O\xc0vb\x9d\x80R\x02\x15(\x0e\xd0K\xe8\x04\xc56\xfb\x88\xb3\xe2\x00}\x82\x00q;L:\x89\x87[\xc7S\x09\xe4\x8fu\xda\xb1\xdf\xa5\xbeu\x8ar\x1d\x85\x00\xbdR\x029\xc59\xf1:\x1f n\xfdI\xbc\x08\xc0ux\xaa?\x07Z\x07pT\x7f\x14\x07\x88\xd6\x92P\x1c \x0a\xb3\x88?\xd7\x89\x19\xc59\x8a\x03\xf4\xc8\xfe\x8f\x15\xc5\xf91\xeb\xcfA\xd2\x01\x94\x15p\xc0u\x80\x18\xd8\xdd\xbe/\x8b$\xc5\x01z\xac\x04:\xaa?\x07Q\x09\xd4u\x16\xa3\xc2c\xd5q/^4\x94/\xc5\x10\xb8\x16\xe0\xc6P\x07H\xa7S\xc9\xe3\x00\x06L\xbceY\x04\xbf;\xbcY\x8a\xcf\xe9`q\xb4\x02\xc6\x06\xeeK\xa0\x19\x08044L\xd7\xe2\xdd?6JVV\xd7\xf6\xbf\xb2\x97\xec/\x1aX\x1fE$\xfc{?\xf1\xe1\x00\xc8%\x1f|`,Z\x0e\xd0\xd5\xe8\x98\xa6\x91\xe1\xe1o\x11\x0d\xfe(r\x82r\x17v\xfb\xec\x15\xbes\xe8\x81\xd8 \xc0@&\x13\xbd\x0e\xd0u\xad\x18&\x7f\x08\x90\x00O\xe1J\xa5\xd2{\xd7\xde\x03\xa7\xc2\xee)_\xac\x22\x12\xab\x89E\xc2\xb6\x91\xfbF\xf6\xf57\xf0\xfb\x07\xdd\x00\xcf\xd7\xa2E\x80^\xd9\xc5\xf4\x98x3zue\xdf}\xe0\x08`\xd7\xe2\xa7D\xb6\xc7\x01b\xec\x19\x93w\xf6\xe0\x9a\xc0\x9dv\x08m\xf9\xc2\x07\x9e\xff\xc7s\x97\x7f\xed\xbc\xdd\xff\x1b\xc7\x05%m\xea\x00\xc9\xb3\x8b\x9bAq\xb3\x88Y\xae\xdd\xff\xeb\xb8\x8a\x03\xf4\x94\x03\x08Y\x8e\x1a3~\x7f\x08\xbfQ\x89\xfb\x05\x90#\x88S\xc5\xd8\xd9\xc3\xec\xd4/\xfcZ\x19\x9e\xf5\x8b\x94\x8f{\x0b\xd0\xec\xc3\x93\xca\x8b\x1b\x1b\xcb\xc7\x8e\xff|~\xbf\xffW\xd7Hl\xfd\x08m*\x81\xc9\xe6\x00\x88\x0c_\xdd\xbd\x83\xc5\xe9v\xfek\xca4b;^\x89\xe5\x00\xb8\x1bH\x9c\xec\x81 v\x09\xa1B\x89\x07N\xe3)\x22x\xad\x0c\x93\x8c\x94_*\x97\xa8\xb5\x81\x8e'<\x08\xe2\xee\x9d;\x94\xfa\x81\x1bL=\xfb\xdc\xd4|;\xffU @\xf2t\x80\x04\xf9\xc6\xf1d\x91{_\x7fMVVV\xe8\x07\x9e`\xe2\x17\xa0y\xf6\xe9g\x9e\xbd\xd9\xfe\xffL\xc5\x96`\xe2\xed\x07h\x03\x90\xe2\xc5\xb7{\x90\x1bh\x9aN,\xcb\xa42\x1f\xf5\x03\xa4\xee\xbbw\xbf\x22\xb7o\xdd\x22\xf7\xee}\x8d\xd4\x89\xbb\x7f\xb3x\x06\xd03\xcf>\xb7\xd4\x8d\xff\xe8&O\x09\x8c-\x07\xc8\xae\xe5\xf3\xb3\x8d\xfd+\x14\x0a \xd3\xef\x92[\xb7\xfeK\xf2\xf9<\xbd\x0f\xc4B\x16\xf2\x85''\x7fz\xb3\xdb\xff+\x8eV\x93\xd6\x8e?\xffo\x7f\xff8\xb2h\xdd\x8e\x18\xf0\xe7?\xfd!\x9dNO=\xfc\xc8#\xc4\x86>\x16\xd6\x0b\xa8\xd4-\xc3\x84#k\xcf\xfe\xf8\xc9\x9f\xcc\xf7\xa2\x1f\xe8\xab\x1f\x1b\xbd\xaf\xab\xef8|\xf8p4\x08\xf0\xc9'\x9f\x92{\xab\xf9\xd8\x8a\x81\xbf|\x98}\xca4\xcdq\xfaG5-\xfb\xc3#?\xea\xf9\x11/\xdf~\xe0~\x8a\x04O|\xb8\xb6\x93\xe1\xbaM\x95o\x94\x15N,\x97\x11\x9co\xb3\xd8\x88_\xde \xf9\xf7\x8fG\x83\x00\x0a\xfa\x1f\x14\x02(\x04P\x08\xa0\x10@\x81B\x00\x05\x0a\x01\x14(\x04P\xa0\x10@\x81B\x00\x05\x0a\x01\x14(\x04P\xa0\x10@A\xe2\xe1\xff\x02\x0c\x00)3_$\xa0\x879\x5c\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x1cT\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x11\x09IDATx\xdab\xfc\xff\xff?\x036\xc0\x02c|\xbb\xc6\xfc\xff\xee\xc3\xff\x0co~108\xfa\xffcd\x00\xe9P\x92c\xf8\x1f\x17\xc4\xf8\xff\xd6a\xa6\xff\x02\xbc\x0c\xff\xe3\x83\x19\xfe3\x82$\xf8y\x19\xffss10,\x98\xc6\xc2\xf0\xf2\xf9\x7f\x86\xe7O\xfe1\x80u\x9c\xdd\xc8\xf8\xff\xd3e\xe6\xff\xe6\x86\x0c\xff7\xccf\xfa\x7fk/\xe3\x7f\xb0\x04\x08\xa7E0\xfeW\x90a\x00\xb9\xe4?\x88\x0f\x10@\x8c\xb8\x5c\xc5\x04\x22\xcenf\xfe\xff\xfa\x0c\xd3\xff\xcd@c\xd8y\x04^\x81\x9d\xcb),]\xa8+\xf3\x8f\xe1\xe0\x1a\x16\x86\x0f\xdf\xff1\xc8\x89~\x14\x05k\xe1\xe3a\xfc\xc3\x0ft\xa2\x8a\x02\xc3\xff\xcdKY\xfeOl`\xfc\xcf!$u\x80\x01H(\xdd;\xc8\xf8\xbf<\x8b\x09l\xf1\xc9\xf5\x8c`\xcb\xc1.\xaa-\xe2{\xe4d\xc9\xf0\x9f\x93\x83\x01\xa4z\x1eH\x0c \x80\xe0\xce\x85a\xa0D\xe1\x85\xe3b_\x7f~e\xfa\xdfT.\xfe\x1a\xc8\x97E\x96\x87\x87\xa1\x87=\xe3\x7fA>F\x86\x95\x1d\x0c\x0c\xf2\x9c\x8c\x0c\xb7\x8f\xfcgP\x97}%\xa2(\xf4\xff\x11\x0f\x17#\x03\x1b\x0f\xef\xe3w\xaf>\xc9\x81\xfd\xcd\xc1\xc5\xf9\x8e\x99\xe1\x87 \x07;P\xb1\x0c\x03\x83\xa733CF\x02\x03\x83\x8d\xd7_\x06#}\x06\x06V\xa0\xb1\xec,\x8c\x0ck\x0eI\xb6\x83m\xf0wc\xfd\xe6\xef\xf0SP\x80\x8f\x81AN\x96\x91AI\x99\x89!4\xfd7\xc3\xd2\x99\xcc\x0cr\x12\xff\x198\x99\xff3\xb02\x03581\xdc\x82\xbb\xad\xbd\x8a\xe7\xd9\xc7\x8bL\xff\xab\xb3\x19\xff\x07\xb93\xfcWUd\xf8\xcf\xca\xca\xf0\xbf\xa5\x88\xf1\xff\xcaI\x8c\xd8=\x0d\x04\x1c@\xac\xc7\xc6/\xba\x94]P\xe26\x1b\xafP7\x90\xaf\x86\xac\x06 \x00\xddd\xf3\x92@\x14E\xf1\xf3t\xb42\x9b\x19\x13\x1b?\x92\x08\xe9?\xd0E\x84\x10D\x85\xeb\xdc\x05\xd1\x22\x08\x0a\xa2U-\x8aV-\xda\x05\xad\x5c\xf5\x07\xb4Oh\xd3\x22h\x11J\x8b\xa04\x0a\xa1$\xc5DM\xf3\x83\xd0\x99\xd7u\x84\xc1\x84\x06\x86a\xee\xbb\xf3\xce\xbc{\xce\xef_\xef\xfe\xbbL\x83\x85\xa5EW\xaa^\x11\xb5\xb7g\xa9M\xb6^\x0d\xae\x1b\x0a\xb4\x18o\x16>#\xdfO\x1a*U\x8el\x1e\xc8\xbc\x03\xc5\x0a\xc7a\xcc;\xd3*}\xbc\x1a\x0a\xd4\xbc\xb5\x12\xceE\xb6\xd7U\x08fJ\x8c\x05Pi\x9fj\x1d\xd8'_\xfcR\xee\xe5\x8f\x82\xdf\xcb\xb8h\x07|\x0a0\x1b4aw\x03xH\x03\xf3Q\x0d\x93n\xc0!\x01m\xd8\x0a\xa9t\xc3\xad\xfbP\xa9\xd2n5\xa0\xde \xb5Q\x0d6\xd1\x8a\xfa\x8f\x8a\x8505\x937f\xc6`\x1bn)\x86\x82,\xf6\x8eBs\x87\x9d\xa0\xe9\x82sJ\x9e\xd6\xbe8\xca%\xae?5\xe2g\xefDc\xba\xc2\xd9\x11\xd3\x9b\x14\x8a\xf6t\x80\xe1\xe2\x92a9\xdaA\xea\xc6\x0c\x99\xeaVR\x10\x84>\x80\x03S\xe0\xf4\x9f\xcc\xe9\x00$;C\xe2^\xc5\xb8\x0c\xdc\xde\x11\xc3>\x0e\x17\xd5C\xab\x9eD\xab\xd4C\xaf\x1biK1\xc9x\xe3\xd1\xc4\xbbT\xce\x05\xc1\xe9\x03\x1d\x90\xd81\xe3\xf1\xf3\x1e$F\xbci\xc6]\x93<;\xd1|\xbeL\x03h6\xe9pT7\xd3\xd03YS{\xf3\xa0c5\x9c\x1b\xc8\x92_\x18\x19[\x1b\x92\x95\xa4U\x9e\xb8\xa6\xf7\x10\xdd\xce\xfe\x9e_\x01h-\x7f\xd0\xa6\xa20\x8a\x9f\xf7^\x9a\xd4\xbc\xa4%ml\xfe\xd4\x82\xff@\x07)\xa2\x0e.A(Am\x07'\x15\xa1N\xe2\xd2E\xaa\x88\x16'\x11\x04-\xddb\xbbI\x07\x97\x0e\xe9\xaa\xa0\xa0\xe8`Qh\xa4\x94P\xb4C\xd5\x92?Mm\xd2(1\xb6yy\xd7s\xef\xd3bm+t0\xf0A \xef}\xdf\xbd\xe7\xfb\x9dC\xb6\xcd\xd2v?\xfaV?\xf0J\x97F\xee\x07r4\x85\x5c\x90\xa8d\x0d\x91M\xe9\xe2\xfdK]\xcc\xbe\xd5E\xa9@S\xc1\x10\xe9I\x97\xe0\xb3\xafX\x0d\xff\x8c\xd1?\x1a?N\x5c\xcfw\xdf\xe8\x15\xf8|\xa4yW\xb2\xb1Z\x0d\xab\xa6\xdf\xbbh\xbb\x03\x87\xa8ya\x83\x19\x94\xa0\x1df>v\xe4{\xe8S\x06\xf0\x9b\x92:\x0dm\xad\xc0\xbd\x01]\x0d\xa9T\x05J$\xb4\xf0\x05\xc8\xe4\x81\xe7\x13\x02\x0f\xc7\x04\xcc\x1d\x1d\xa3I\x9b\x01ec*\x0d\xbcy\x07\x0c\xdf5\x18/BIb\xf2y\x0fa\x93\x12\xc9\x88\xffZ\xd1k\x87\xcf\x87\x1ay\x03{\xdd\x929db:\x99;\xfe{\x88\xc7\xad\xa9\x17\xee<\x10X\xa0\x8fs\x05\x81\xcc\x02\x90e-\xd3\x91\xb7\xafj\xca\xe7-\xdcU\x93\xdfI\x1a[h\xd6\xc9\xbe\xf0^6\x9f\xdf@\xd1\xaf!\xfb\x87o\x95__8]\xdd)\x1b&\x1eA\xe1\xb8X\xe4?\x81\x22\xbf\xf3\xe2\xe5o\xb4{\x95\xc8\xd6h\xddN\xe0l\xb7\xa6\x80\xb82\x14y\xc1\xc6]\x9bf\xf6\x16f\x1b8v\xa0\xdc\xdfjVB\x12O\x19\xa22\x96e\x90V\x7f\xd0\x1b\x96n\xb9\xfca\x0a\x86Sl\x5c\xda\xac\xc7\x7f\x8f\x8a\x9f\x02\x10_u\xa1m\x95a\xf8\xfd\xce9Y\xba\xa4ij\xda4Y\xb5C\x1c\xe2\x85?\x9b?\x9d7RP\x14VA\x04\xdb\x09\xd2\x0dq:E\xaf\xbc\xd4\x1b\x05A\xd11P\x18S\x19S\xf1\xef\xc6\xe9P\x06\xd59\xc1Y\xd9\xac\xe8\xe8\xa4\xd8\xcd^T\x9d\xd2-\xcb\xbadK\x9b\xe6$9\xe7\xf3y\xbe/E\x1b\xe3\xa67\xee\xe2\xe5\x90\x9c\xe4}\xbf\xef\xfdy\x9e\xe7\xbdtX\xf4\xbf\x04@\x0d\xfaH\xb6o\xef\xc8\xe6s\xbf&k\xd5\xb2\xab\xcf\xcdu\x84_\x8ff\x16\xd6\xdf\xda}\x8c\xfa\xea\xa2\x11\x9a\x05\xd2\x12\xa3\xc0\xc6\xaag<|\xf4tm\xde\xd1s\xd3J\xcf\x1cRz\xea\xa0\xd2?O:\xf8\xde\xd1a\xe8\xe8\x91\xe1\xe4I\xfcvk+?-\xf9\x80mzg\xff\xc2\xe1\x0f^.\xa5\xbdN%>X\x13H)\x8b\xbe6\xdds~\xde\xd22\xbb\xaa\x08\xdb\xb8A\xc9\x1d\x9bB9\x9e\xeb5\xd0p\xc1\x14\xc1y\xcf\xba5\xc5\xc3\x8f\x0d\x95\xd2Oo\x83\xbc\x98\x0e\x815\xf8\x91c\x8d8\x03D\x90 \xc0\x0cTm\xab\xde\xfdH(G\x8f\x01^\xfc\xd9AB\xfd\x05\x03d;K\xdf\xdf3PN\xbf\xf1\xa16\xc3\xf4\xea{\x7f\x82\x17i\x9c\xc6@@n\xa9\x01\xd8\xa6\xc1\x17\xe3\x13\x16\xb7\xa2\xc0\xa5T\xc4\x04y\xa2e\x00\xbcx\xa9+V\xea\xfb\xe4\x0b^]\xdb4\xc0^xM\x8b\x1b\x81N\x888\xe2\x02:\x1cHIdVj\x90\xf6\x1f\x1f\xd0\xc69\xb9\x80\xcfk\xae\x12I\xb7\x17\x9fi\x19 \xee\x14\x1ft]\x9bW:\x9e\xa3jA \xe6< \xf5\x11\x97\xe0\xc4\xf1\xec\xe7\xb1\xefB9[\xb4\x9cA\xedD,:\x8f[\xafN\x973\x7f\xed\xae%\xb5vm\xdd/gH$KE\x0e\xc3C\xb6\xa1\xe0=]\xdai\x0e0\x8b\xf5\xc08\xa3c\xb6#\xb9\xb8#\xa1$\x89\xdc\xf6t+\xe9\xc5\x89\xf7~\xaa\xe4\xae\xfb\xea2s\xc4\x93$\x0e\x14A>\x1dR\xdam\x0b\x02m\xb1\xa1\x11\x0d\xa1\x08\x9a\xd8\x18\xca\x0f\x11\x15\x110F\x8c\xa8\x89\x22\x04\x01\x95&\x22FMC\xfc\xa3?\x8c\x01\x91()H5\xe1%B@%\x92 \x89\x9a\xa01\x01,\x88 \xa4\x11\x09\x06\x03\xa1%\x0d\xdan\xbb\xaf\xee<<\xdf\xbdw\xbaC\xa5\x85\x12\xf8c\x93\xb33\xbb\xdb\xde{\xce\xb9\xe7\xf1}gz\xd37\xb8\xd9?\xd6\x8dZ\x88c\x82Q0\xd5\xb1\xd4\xb00[!T\xecj\xfd5\x806G\x07q\x13\xa7\xb3,G8.~\xbb!\x1b\x0f\x04\x97\x06\x13\x86Y\xe5,\x8d,-\xdb7\x94ut\x9d\x8f:N\xaf%\xa1\xd8\xb5H6ex'\x0fG\x9c7\x1a\xca0\xf7j\xd1k\x95_\x8f.\xd7\x1cB\xeca.\xff\xf4n\xfd\xc4\xd4\xf4\x8dM\x99\xca1\xe3]p3r9\x9d\xf3\x0c\xfdr\xccN\xec\x1c\xea\x88'\x01\x13Z\x1b\x1a<\x08\x9f/\xc8L\x08\xb3:\xaad|s\xfbDA\x09\xce\xe5<\xa7Q\xd3\x1a\xcb\xf9p[\xa4\xad\xbd\xb3\xf8;\xfe\x8d\x15W*\x09\xd7\x95\x03\xc0\xa9|i\x9ezw\xf7\xa4\xc6\xc5\xa9\xe1\x15\x9c\xd7\xa79\x10\xcc\xb0G\xd3\xee\x15z\xf2Ir\xda\x038)\x0d\xd0\xcaC|\xa5!(\x96\xe9l\xa0p\xea\xcfN\xfc\xc1\xb1u\xce\xa3#'\xf93~\x1f\x8e\x15_\xa2p\x02!\xb6\xc4\x9f\x84\x0d\xd9\x00=\xd7X?\xb6\xa2k\xd6\x8c\xfaT\xe5\xb8\xdb\x0c\x86\xa0\x9el\xed\xd1@\xc3{\xabA\x1b\x01\x03\xb4\x11\xbd\x8c^\xa4\x01\x1a\x1bK\xf4\x93Q\xca\xa7R\xaaN\xa2\x03\xe0\xfdO\x87=:|\x9c\xc9kG\x01\xc4\xa09\x1a|\x9fq\xe2m\xbd\xa2\x14X\xfa\xe5\xfe\x1d\xe0j\x9d\x00\xc9\xb7s|e[\xdd\x84;<\x0bJ\xa1)\x86\xc1\x9c\x19<\xfa\x1d\x1b}\x0f#\x90\xe5\x0b\x19\xdc$\x846\x82!1[\x82S@\xb7\x90\x8ag\x94\xd2\xe8 =iU\xecq\xbf\xe7{O\xe2r\x7f\xae\x10\xd2\xd8\x1dW\xc0\x0c\xcc\x18:\x93\xc2nOW\x1ea}\x9e\xf2Y\xf7\xa0\x06\xb0\xf2\x1c\x9d\xb4\xa3\xaa\xf8B]\xe5\x08\x12\xe8\x8d\xf0\x22\x16\x06\x8c\x80\x11>V\x07\x22\x00\x9c\xc6\x1c\xe1\xfez\x88\xa1\x99\x09#\xe3\xbc\xdbg\x00\x94\xed\xd6\xb4\x08\x88\x01\xe8\xe0\xf3\xaf]j\x09\x90\x0a\x8ci|\xf2!g\x11 \x1a\x0c\xc8J\x13`\xff\xe4\xb5^\xa8\x82\x11\xcf\x04\x91\xc4\x7f\x0c`\xe5\xd1\xfc\xf7\xc5\xa9}Z$\xe4\x86\x10*\x19\xed\x1d\xffx}\xd4'\xbd\x1fW\x03\xab\xb2\x84A\xc3\xca\xd5\xb8g\xd9\x0b\xca\x88<\x1b\x91\xcd:\xca\xe3)!\xc9Lg\xa7\x9a\xf4\xae^\xaf8\x05f\x1a\x91\xa8r\x0a\x94\x96p\xd5T@\xc2\xdf\xabf\xb4:\xbd\xae\x1e#\x7f\xf0T\xc5~\xd6o&\xf0\xdb@}\xe0y\x91ON\xc8:n\xc8s\x94\xd7D\x80\xfb\xf9\x98\x18\x1b\xa6\x02\x09\x99eo\xf7:\x08\x1f\x83\xd6nr\xe9\x95E&\x09fX.s\x00\xcb\xc6\xe3\x14P9A'N\x13\xbd\xbd\xca\xa1Q\x5c\x08\xc6\xd6(\x5c\x07O\x87\xb4\xf2\x92g\xea\x19\x18\xae\xf8,\x93\x13tW\xad\xa0\xdf\xcfx\xa1\xda1\xdd\xb5\xad\x7f\x95<\x07\x84;\x90\x01\x0f\xdb\xd9l9b\x19I\x88\xec\xc4b\xc1\xe1\x1a\x165\xb5\x87\x92a\x95\xd0\xf1>\xa4\xc4\xa0\x92\x95]\xb9N\xd0\xcc\x87\x88j\xc7Yd\xe6l\xca\xb2\xf2\x9bw\xd9t\xe0 \x83\xbd\xc9\xea\xa4b1\xb5\x86$\xc5\xb2!\x19r?O\xbd\xa8\xbd5\xac\x8bE<\xba\x85O8\x14\xce\x0ec\x03\x1e\x19\xcc\x80\xaa\xbc\xed\x14\xc9\x05\x9c@\x9c\xf5\xbd\xf4c\xd6F\xc1(\xdc#\x0c\xe0\xd5\xe1\xe5\x0e\xfd\xca%\xf1\xbe)\x82\x9e\x9e#h\xfe\x8b6=:\x9bh\xf1B\x8c\xcfL\x90G\xc5\xe4\x1d\x94_OM\x12\x1d\x05\x0f\x1dG\x95d|\x07G\xfa\xfb\xc2\x80D\x82uc\x1d\x07\x83\x12\xe9\xd7\x17\x1bn<\xe2\x9a`5H@\x11 \x83\xb8\xf1\xab\x04\x1eF`\x8e\x17.R,(\x1aE<\xaba\x7f\x09'\xb5$0#M\x9a\xb74G;6YT=\x1a\x15\x0c\xa7gJ\xf7\x8a\xbe\x06\xd2\xff*\xe4E\xbe\xe8\xf4\xc4\xa3\x04\xcf\xc6S\x12\xc3]\xb3\x1d\x019\xb0\x01\xc7R\xb9\xd0\xe4\x9a\xaa\x5c9\xbc\x8a\x045\xfd\xf87\x0bc\x8a\xa2\x22]=\x22Jq\x08\x14GX\xc4\x8b\xf1\xde\xa4/\xbfq\xe8\xcf\xb39\xd9\x0b\xa6>f\xd3\xe6\xb5&U\x8d\x82q\xb6\x1c\x0e\xa3\x10XX\x1bt\x8et\xec{B\x9f\xb8\xd7\x17B\xf2\x13W]\x8f\xb6\x86\x80\xa7\x8e\x0df\xc0\x8e\x9fO\xc6\xe7L\xbd'W\xeeW\x02?\x99,\xb3\xe0\xfd\x82\x01\x1e++\xe4{0\xe20\xd3C\x9cL\xf36\x87:\xe4d\x95\x930\x83\x90\xe2\xfa\xb7\xc4\xa1\x8fW\x9a2\xfeK\xa5\x91jV,K\xa6\xaer\xa6p\x95\xb3\x02\x89\x8c\x03cJ,\xef\xd7n\x8f_d\x1d\xb7\x0dh\x00\x10\x22\x97\xd2O\x8f\x9e\x8a\xbd9\xa3>=\xf22\xc5\xf53\xacP\xa0\x1f@B\x98\x8a\xc8\x1a.Xi\x87\xb6\xecV\xb5\xfe\x9f\xa4K\x97\xfeV\x06\xe0o\xc1w\x17\xbd\xe6\xd0\x1c&\xcc\xb3\x1f4\xd4\x03\xa8\x88?R\xe6\xb5L\xafo\x1f\x7f_\xff\xd4M\xfen\xd7\xb7\xb1\x8b\x07\x8e\x87\x9b1\xc2\x1f\x14N\xf3/\xac\xc6(\x8d7\x9c\xff\xf8\x03\xe9QV?\xef\xcbf#{\x82`\x92\xa3\x167\xd8=\xef7;\x5c\xaf\x85\xac\xfb==\xae$\x82\xc9\xa4\xca#$&\xc2\x05\x86\xee\xdeG\xf4\x0bS\xe1e\x0b\x0c\x15v8\xc5\xa2\xcb\x1bZ8T\xe8\x03\xd8o\xcf\x8f\xb1\x8b\xefl(\xdd\xcc\xba\xad\x19\x0a\x16Z\xfa\xd2\xdc\x9e\xc6W\x9f\xed\xae\xb6\xac\xa0W\xf4\x8c\xc4P\xf48\x95\xf2\xe8\x83\x8d\xaa\x1f\x00\xdf(\x9c\xc3l6]\x80\x0e>\xfe\xc1i\xf4\xeaJ\x83N\xbeb\xa9\x90'\xe3\x0f\x05\xe4$\xce?\x11m\xc4g{K\xcem\xd9[\xfc\x1e+\xbfn\xc8h\x14'\xc1\x97M\x1f5v\xd5\xcd\x9d\x9e\x19\xe174\xbf\xd9\xec?\xe4\xd1\x0f\x874H\xcb\xa8\xe7yxl$\x95\xcd\x16\x00\x9bD\xa3\x98\xcdk\x84\xea\xe8\x12\x89\xb5V4(#\xd0\xd9\x11NR\xd8\x80\x96\xd6hG\xd3\xce2\xc0\x87\x05\xc1gX\xd7E)5\xa4nZ\xb9<9i\xd1\x13\xe9\x0a(\xb1\xea\x13\xaf\x0f\x0e\x17\xc4\x93\x0a\xa73\x81\xcfrj\xd8\xe7+\x8fZo\xfb\xb5^\x03\x02L\xbe\xa2\xba\x10p\xfe\xb5\x7f\xb5\xbf\x14P\xba\xe1jPz\xc8\x9cXC\xecy\xf0\xca\x94;\x93c\xc7U\xa7*\x19I\x0a\xdf\x10\x0c\x9e2\xda\xdb\xbe\xe2\x00t(\xa50 \x1f0\xc0\xd1\x06\x80XY\x91\xe26+\x968\xc3\xebn\xc5\x89\x0f\x04\x9do8\xa9\xd7\xb0\xfbI\x96Y\xc3Jr\xb7&\xa2\xb9D\xc8\xb0#\xdcu\x22.\xe3)\xdbv\xe5#\xb8l\xde\xc8\xe7!\x8e\x95\xb5=+\xeb\x8a\xa2$\x99E\xe7\xf9\xef\x80\xf5\xbf\xb8\x12L\xbei\x06\x08!\xf0\xaf\x0d\x98\xa7\xc6`\x83\xfeW\x07\xb4\xf8\xb0\x16K\x8b\x11\x18m\xb9Zl-\xbdZ@01\x8f\xc9(\xc8G\xa9\x81\xe63\xff\xdb\xb1\xca\xbf\x98y|\xd8\xf0]\xd6\xbb\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0fv\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04+IDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91\xc8\x18\x05`\x10\x86\xa2\x11\xba\x09\xba\xb8\x07\x0c\xcc\xdb\xb2\xd0Q G@V\xc6\xa3\xa2\xbe\xa5\xd4\x8a\x11\xa9\x14#%p\xf3P\x98&\xb8]\x89o\xb7't\x1e\xca\xb0\x8e\xf8\xea\x02\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x1a\x1b\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0d\x90iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a Oliver Twardowski\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a\x08\x8b\xe74\x00\x00\x0c!IDATx\xdal\x8bA\x0a@\x11\x18\x84\xc7\xa3\x94\x5c\xe4Y\xb9\xb7{(\x1bWp\x02%\xc9\x06\xfd/\xca\xee\xcdj\xfaf>FD\xf8\xcbsK)\x85j\xad\xe4\xbd?Ov\x8d\xd6\x1a\x09!0\xe7\x84\xd6\x9a\x89\x0ds\xce\x14B\x00\xe7\x1ck-\xbc\xc6\xacc\xa4\x94hC\xa5\x14z\xefg<\x86s\x0e\xd6ZH)1\xc6@\x8c\x11\x9f\x00b$\xe8*\x90\x8b@\x18]\x22\x95\x95\x95\x95\x01\xe4* \xd8\x04w\xee\xd6\xad[\xc1\x96\x83\xc0\xbf\x7f\xff\x18V\xacZ\xc9\xc8r\xf3\xd6\xed\x9fZZZ\x0c\x9c\x1c\x9c\x0c s~\xfc\xf8\xce\xc0%(6\x81\x99\x99\x83\xb3\xf2\xf6\x8d\x1b\xac\x5c\xdc\x5c\x0co\xde\xbce\xd8\xbd{7\xc3\xf4\xb9\x0b\x8e\x03\x04\x10\x86\xab\xde\xbf\x7f\xff\x9f\x91\x91\x11\xcc~\xfc\xf81\x83\xae\xae.#V\xd7\xc2\x1c\x07r\x18\xc8\x1d G*))1@}\xb0\x09\xa6\x00\xecl\x98\x89\x99\xb9\x85\x96\xb22R\x0c\xcc@M\x8c@\xf8\x1f\x08\xff\x02\xc3\xfa\xd7\xef\xdf\x0a\x0d5U\x0cp\xdf5w\xf6>\x88\x8f\x0c\x95\x07[\xc9\xc4\x04\xf6>\x88\x06\xf9\x14\x149w\xbe\x070(\xb1\xafch\xec\x9a\xc0\x0d\xb6AGSM\xfc\xea\xb5k\x0c\x0d\xf5u\x0c\xdf\x7f\xfc`\xf8\xf5\xf37P\xe1o\x06V\xa0Fv66\x86\xae\xde^\x86\xab\x7f\xaf1\xdc\xbdw\xa7\x0d\xc5\xd3'\x8f\x1f\xff\xcf\x02\x0aG\xa8\x13\x19\xa0rO\x9e>\xfd\xe5\x1f\x18\xc8\x0eb\x03\x04\x10\xce\xb8\xc3\x05PB\xe9\xe5\xcb\x97\xffA\xc1\x0aJu z\xce\x9c9\xff\xf1j\x80y\x1a\x04@A\xeb\xea\xea\xca\xb0p\xe1\xc2\xb7\xc8\xf2,\xc8\x1c\x90\x22XZ\x00i\x14\x16\x16f\xe0\xe0\x00\xa6\x01\x5c\x1a@\xf1\x01R\x08\xb3\x05\x1b\x80G\x1c#\x0b\x87oCC=\x03;\x07\x1b\x033\x13\x0b\x03(0@\xa9\xfa\xec\xd9sL\x11\x11\x11\xd0@\xfb\x0f\x898#\x1b\x87\x9b\x0deEj\xa0\x08\x03k\x86\x06+H\x0e\xa6\xb1\xa0\xbaQ\xf2\xfe\x95s/\xc0\x1a^\xbdz\xf5\xff\x070\xc2\x80v\x01c\x99\x19\x96\xd0\xc1\xb1\x0c\xc2\xbf\xfe~f\x10\x17\x91c\xe0\xe5\xe5e\x04;\xf6\xeb\xd7\xaf\xe0\x9c\xf6\xef\xdf_\xb0\x89\xff\x18@\x18b\x03(y\x00\xf3#0\x89\xbfA\xf8\xe1\xc6\xcd\x9b\x0c GTWW3\xfc\xf8\x09J\x1a\x7f@y\x86\x81\x03\x98,\xd888\x18Z[[\xc1\x8a\x15\x15\x15!~\xe8\xea\xea\x02G\x90\x8e\x8e\x0e8\x0b\x83\x82\x16\xe4\x0f\x90\xdbO\x9d:\x05wbYY\x19#\xd8\xf4\xfa\xf5k_\x90\x05\xb0x\x87\x19\x0e\x8b\x7f\x98\xa5\xdb\xb6m\xfb\x0dL<;\x80\x5c?\x82\x99\x01\x09l\x86\x19\x08K\xbe0\x0c\x8b$\x18\x1f\xc8\xfeCT\xf6\xc4\x9b\x8f\x11\x86A\xc2\x96\x85\x05\x9e\xce\xf0\xc5#F\x10\xf1\x89\xcb>\xb9r\xfa\xa84(\xe1\xfe\xfd\xf3\x97\x81\x81\x113\xab\xc3Jr\xf4\x8c\xba}\xf7\x9e\xbf\x05\xa5\x95)\x7f>\xbf[\xc0\xcf\xcf\xcf\xf0\xf1\xe3G\x84\x05n\xfe!&+\x17\xcc>}\xe3\xc6\x0d\x14\xcd\xb0\xa0\x00\x07\x17\x13#\xbc\xb4\xfd\xff\xef?V\x1f\x80\x0c\x9e\xb9p\xd9\xf3\xa9\xd3fI\xff\xfb\xfa\xe6?<\xf6{\xda\x9a\x0e\x01S\x048w\xa0\x1a\xce\x08,\x0d\x98\x19\x98\xa0\xe1\x8e\x1c\xc9\xff\xfe\xfe\x03f\xd1?\x0c\xff\x90\x22\x1d\x04\x1a+K$\x81b\x19@\xe6t\xb8\x05\xef\xdf\xbee\xfc\xf3\xf3\x07\x86\xb7A\x18V\xb0\xa1\x07\x118\x18\xff\xfeEIU0\xf0\xe6\xcdku\x94|p\x1d\x184\x1cl\xacp\x05\xb3f\xcdb055e`f\x05\xd6)\xa0\x88\x00\x05\x0f\xdc\x8c\xff\xe0\x0a\xe0?(~\x80Au\xfb\xee\x1d\x86W\xcf^0dde\xc2\xf5_\xbct\x19\xe2H \xe6\x00b6&V\xce@\xa0k}A%1(>gL\x9d\xe4\xe9\xef\xef\xc7\x0ar\xf9M`\x9d\x04\x8b\x07t\x97\x82\xf8\xf2\xc0*\xd3\xd5\xcb\xe7\xc8\x8dk7\xde\x81J=\xa0\xb1_\xff\xfd\xfeV\x0c\x94\xfbJ\xf3\xa2\x02 \x80hn\x01\x13\x03\x8d\x01\xceBj\xc5\x8a\x15\x1f\xdc\xdc\xdc\xf8\x91\xcb!\xa46\x0e\x03\xa8A\x07,\x0c\x8b\xf3\xf3\xf3\xfb\xc8\xf6\x01rY\x04K\x9a >\xa8\xfe\x05\xd6W\x0c!!!\xbd\x13&L(\xa3\xc8\x02\xf4\xfa\x1a&fkk\x0b\xae\xe7\x80\x19\xb3\x93\x9c \xda\x04L\x9e\x5c0\x1f\xc0\x0c\x05\xb5\xe2`I\x13V'\x82|\x03m\xd1\xf9\x91\xe4\x03`p\xfcA.\x87p\xf9\x0c\xd8T\x01\x17\xed\x14\xa5\x22\x98%\xe8\xcd\x1aB\xcd\x1c\xa2#\x19V\x1e\xe1\xb2\x88,\x0b`\x05\x18\x0c#\x17\x13\xb0 \x22T\xd9`\xb4\xb2\xc1l.a\xc6\xf9\xb3'\x8bo\xda\xb0\x99\xe9\xfe\xa3'H\xf5\x01\xa2,\x02A\x90\xe1\x90\xe2\xfa/\xc3\xa3\xa7\xcf\xac\xe7\xce\x9au\xfd\xff\x9f\x1fG\x90\x1d\x08/*`\xb5\x0f\x0b\xafPL_g\xeb\x82\x00\x1fofP\xab\x01W\xe3\x15\x5c!\xfdg\x80\xd7v\xa0:\x03\xd4,\xd504\x7f\xfa\xe9\xe5c\x19\x0c\x1f\x04\x06\x060\xc8k\x1b\x8a\xdd\xb8tn\xe1\xb5\xcb\x97\x98.\x5c\xb8\x00om\x81\x1b\xba\xf0\xa4\x0a\xec\x0a0\xfcC\x04!\xc8\x96\xff\x0c\xf0\x16\xe6\xdd+\xe7\xa4M\x80-\xd1s\xe7\xafh\x80j3\xb8\x05\xa1!\xa1\x0c<\xa2R\x17~|\xfd\xc2\x04M\xd7X\x1b\xc5\xc8\xcdV\x06P\x9f\x03-\x0e\xee\xdc\xb9\xc3\xb0{\xf3z\xb5\x88\xc4tS \xf7\x14\xdc\x82\xe5\x1b\xb7\x0bO\xef\xeb\x90\x04\x951|||Xk4t\x0b 5\x1a\xc8\x82\x7f\x0c[\xef\xe8\x80\xc5\xbd\x94/\x83\x1bd\x96\xe6\xa6+A\xf9\x10n\x81\x90\x90`6\xc8\x8bl\xc0\xd6+J\x98\x03#\x96\x89\x19\xb5M\x04\xaf.AA\x04j2\x03q\x80\xe6M\x94\xd4\xe7\xe1\xea$\x82\x12\x07\xc0F\xab&(\x92q\xb5\x85\xc0u2\x13\xc42P\x98\x83*yX}\x0cNMhM\x18\x0eV6\x16\x14\x0b\xde\xbf{\xc7\x04\x0a\x1el\x19\x0dn\x01\xb4U\x01s%\xb2\x05\xe8y\xe1\xed\xbb\x8f\xffP,x\xf1\xea%\xc3\xeb\x97\xaf\x81\x958\xa8S\x021\xa4\xa6\xae\x86\xe17\xc8\x10`\xd0\x81\x1a`\x7f\xffA\x0c\x84t\x9f@]X`\xf7\x03\xd8\xb9\x017\x0a\x80)\xae\xa5\xb9\x19\xdeF\xfb\xf4\xe5\x1bj2}\xfe\xec\xf9\xdf\xe7/\x9f\xc3\x05g\xce\x9c\xc9 ##\xcd\xa0\xa8\xac\x08L\x98\xb0\xc8\x85\xb4, =#\x06p\xe4\x82\xc4@\x96\x1e;v\x8c\xe1\xe5\x8b\x17p\xfd\xef>}A\xb5\xe0\xd9\xf3\x17\x1f\x81\x1d#\xb8``` \xb8\x15\xa7\xaf\xa7\x0f\x0e\x22`\xd3\x82\xe1?\xa8\x01\x06\xce\x07\xff\xa1\xfd\xe8\xbf\xf0\xb6\x91\x88\x88\x08\x03\xb2\xfe\xe7\xaf\x10\x9dG\x90\xd3\xb8\x80\x98\x93\x99\x8d\xab\x0bH\x0b\x80,\xb5\xb3\xb7\x93]<\x7f\x8e!'''\xd8\xf5\xa0f\x0bz\x91\x0d\x8b\x0b5MM\x86\x8d\x1b7\xfeN\xcd\xc8\xd9\x0e\xeeH\x01\xed\x06\x9az\xf4\xef\xafos\x80\xfc\xdfX[\x15\xabV\xac\xf8\xa6\xa4\xa8\xc8\x096\x14\xad\xab\x0d\xe6\xa3\xb5\xe4\xfe\x02}r\xfa\xec\xd9\x0a`G\xa4\x93\xee\xcd\x16\x80\x00\xd5Y]l\xdbT\x14>q\xe2\xfc\x96&\xa4\xab\xda\x15\x125C\xeb\x16\xd4\x07`aU\x10B\xea\x03\x8ci,\x0f0uT\x14\xf1\xd2\xbd\x80x\x00UE{\xe2\x05\x09\xc4\xf2\x02\x12\x12o\xa8Bh\x08\xd1\xc1\x84\xd4\x02BCle\x12\x0fL\xea\xd4\xf2\xb3\xad\x1b\x1d\xd5\x92%M\xdb8\xd9\x92\xd4\x8d/\xf78\xbe\xce\x8dcg\x0c\xd4V\xb3d\xd9\x8e\x1d\xfb\x9e\x9f\xfb\x9d\xef|w\xd3?\xb0m\xb4\xa8\xd5F)\x93|\xb7\xffE$\xc2\xee\x1d;u\x9a|\xab\xb4\xf5\xfbntttx[\x22\x80\x9copp\xd0\xcf\xa0\xd1\x8cs\x98\xbd\x17g\x8c$I0;;\xab2*j\x10E\xcb\xea8\xe5\x86\xc9ma\xbe<#\xe3\xb9\x0d_0\xf8j\x85\xac\x0d\xab\x19\x0a\x16\x94L\x11\xe4\x9e\x81@\xe0\xfd\x89\x89\x09\x85v;\xefnY\x0a\x19\x99\x9d\x19\xfbfB\x15\xdf42\xc3\xba\xba\xba \x91H\xd80\x22\x85B\x01VVV\x80r\x80\xd7\xe8\xed\xe3\x9b\x1d\x01\xe4\xb7\xc7\xe8\x80\x9c\x0d\xad\xb3\xc1\x08\xbeF\x19\x89 \x7f\x8d\xf7i\x04 \x14\x0a\x01\xc5Tt\xe61^\x15\xdd\xb2\x08\xf0t\x8c'\x99\xc6\xb9a\x9c\x17f\x91\xdb\xd2\x142\x0e\xd2\x18\x89V\xaaI\xab\xc9\xbe\xe9\x06\xd0\x81\xd9\x8c\x9e\xb6\x9a\x0b<\xd5o\xa0\x9c\x06\xa3\xf0\x9d[f@.\x97\x93)\x9e\xbb1\x87q\x10\xc6\xb9\xc0{\xde\xd8\xbb\xb0\x9a\xc0\xa8\x15nHc\xf1\x9d\xff\xbb\x0e\x98\x85T\x10=\x03\xd4W\x1f\xf6E\xfbw\x07\xda\xefks8]\xd5\xb66/\xadCNJ\x88\xed\x8d9n\xe2C\xfe\x89\xaa\xfe\xd1\xa6\x0a\x01\xa5\xca:\xb9}\xbb\x5ci\xf7y\x1c\x17\xe7\x7f\x97\x96\x16\xaf.\xd0\xaf\xbf\xa1\xc8\xa5\x9f[\xa5]C\x04b\xfb\xf6\xe9\xe7\x81\x07#\x89\xce\xe0\x8e\x8f\xcf\x9e\xfb\xb1\xab\x7f\xef^\x01\xe1\x0e\xd4%\x9b\xb2\x88\x15\x15_\xa2\xaaS\xa0\xc1\xe3\x7f\x98\x88D\xa7|j\x13f\xa3^qct:;;\x83\xa2S\x0c^\xba|\xf5l\xf4\xd1\xfd\x99T6\xfb\xea\xda\xd2\xb5\xaf\xf0\xd9\xc7b1\xeb\x14::4\x04\x82o\x87\xed\xc9'\x06>\x99\xfa\xf2\xf3\x17w\xf5\x86]W\xae,\xc0\xaf\x17.\xc0\xadb\xb1\x0e\xba\x8a\x9e\xb8M8\xcc\xe7:\xdeE\x96Njz[CJib\x9c\xaa\x5c\xe2-\x85u}\xf4\x85\xd7\x16\x16\xc0\xe3\xf3a\xb1\x13\xceL\x9d\xee\xfe\xeb\xfa\xf5\x93O=}\xe8\x8b\x99\xf3\xbf\xbc\xf2\xde\xdb\xe3\xc42\x85\x9e}a\xd8\x1e\xea\xd9\xf9\xd9\xf17_\x7f\xde\xe3v\x8bX\xf6\x91\xbb\x08\x9a\x87\xf9g\x05\xcd\x00#\x225Ld\x1b\x13V5w\x13\xa2\x1d\x88a\xafu\x00\x99\xf29\xf8Sz\x0b\xfa\x03\x1f\x81_\xec\xa7\x0d\x82\xa8\xb6\x1f~\x7f;\x94\xca\x15\xf9\x9d\xe4\x07\x937\xd2\xe9\x91o'OVM#\xd0\x1b\x0e\x1d:r\xf8\xb9\x03\x1d\xc1\xa0X*\x95\xd4\x96\xfc\xdf\xc0\xa8\xd9\xde\x80\xf3\x84\xe8Ak\x1a\xbcR\xeb0\xf0\xdcY~\x84\x1a\x80\xaag\x8e\xb6\xa2A\xda\x91\xd7\xdb\xd0\x8e\xe0\xfdb\xe2\xe0\x81g\xa6\xbe\xff!N/gL\x0d\xf0z\xbd\xc3\x91]a/.\xe3\xdc\x09\x9f\xf5\xc1\xe29W]\x9b\x8c\xd0\x92\xc78p\xd6-\xe2:\x0c\x8b\x8a\xd7\x11\x82\xc3}\xbf\xe9iZ\x85:R\xe1\x98\xfa\x1e\xea\xf5\x9e\xf1\xb8\x8eX\x1aP\xc8KB~u\x8d\xac\xbb]wU\xc8\x10\xc2\x99\xfa\xc2_\xf3Q`\xdeV\x88\xc2\x19\xa2\xa8\xdd\x10\xd37\xee\xe4\xb4\xd5\xd5<\x91\xa4\x82\xf5$\xcef\xb3\x90]\xce\x12oMv3\xdd\x10\xfbQxqPfI\x1bxp\xb8D\x10\x1dNUu\xa0\xc8\x01lI\x9a\xad\xfe\xb2\xb5Y\xdc\x11a\xd8\xea\x1d\xae\x1c \xfe+\xf47Y\xae@e\x9d\xde\xa3\xe7\xf8\xbb\xa5\x01\xf9\x22a\x0bx\xa6\x06\xdc\xa0\xadw\x8e2\xc3\xa2\xc3\xbc\xbe\x9d8\x91\xacy\xb0\xaa\xa8G\x94?\x00{~R\x1b z\xb8\x8a\xb1\xdf \xaa\x8a\xc4\xbc\xca\x16\x03\x98N\x86\x08`\xb7i\xaa\x07FL\xed\xba\x05phdol|\xbc\x01\xdd\xf03\x18\xc8\xb5b\x09\xd27\xd3\xd6\x06\xa4Si\xf9\xef\xc5%\xf0\xf9\xdc \x10\x1d\xf8\xf4^xd\xe4%8uj\x12\xa2\xd1\x87\xe9\x1e\xd5=\x5c\xe4e\x19\x04\x1f\xb05\x17\x1d[\x1d\xfc\xeb\xe9B\xa0\xcd\x1fPE\x8f\xb9\xb99\x98\x9f\x9b\x87\xa1\xa1\xa3\x90I\xa7T\x80\xad=lC0\x06\x85\xbekye\x0dn\xa6Z\x18\x90JgNO~\xfd\xcd\xc1\xee\xee\x0e\xd9\xe5t\x9bR\xed\xc7\x07\xe2\xee\xfd\xb1\x98}\xcf\x9e>XZ\x5cT-\x13\xc2aK%\x18\xc7\x81\x91ah\xb4\xa1\x19\x84Z\x13\x93\x80\x1e\xa0\xff\xef\xd9\xd9\x03^\x8fO\xfei\xe6\xfc\xba\xd9w76d\x92\x97nU\xd3\x99\xe5\xd9\x86\xefh\xcb5\x82f\x8c\xa8\x1d\x9d\xdc9;\xda\x93\xc9\xe4\xa7\xf1x|w$\x12\x11\xd9R\xb6\x91\x16\x1b\xa9\x89\x95\xbek\xa6\xf7\xd29\xa8LOO\xff166\xf6\xb2\xc6\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x10`IDATx\xdab\xfc\xff\xff?\x036\xc0\x04\x22\xd6\xaf_\xff\x7f\xf2\x8e\xdf\xc6\xe7.\xdd\x0c\x84\xcb\x80t\xec\xb9\xfc\xdf\x18H\x1b\x17,\xfao\xbc\xe4\xc8\x7f\xc6c\xcf\xb7\xfeg\x01I\xde\xb8tX\xee\xe2#\xdbGy\xee\x0c\x0cJ\xe2\x8cbi\x8b} :@\xf8\xd9\xbb\xff\xec :))\xe9?\xd8^\x98\x042\x06I\x02\x04\x10#^W\xa5O\x7fo|\xf0:\x83\xf1\xd2\xa5K\x0f\xc3$\x98\xa5\xa5\xa5\xff'\x87Z\xcf*\x9fz\x8aAA\xc7e\xd7\xed\x93+oo\xdd\xba\xd5\x8a\x85\x93\x93\xcb\xb8v\x15\x03c^\xa4\x19\xc3\xbb\xabKO\x1d\xfc\xbf\x82\x81A\x86\xc1\x1b\xd9R&ss\xf3Xcc\xe3[ >\x86\xe5&&&\xd9@\xaa\x06 \x80P$\xe6\xcd\x9b\xf7\x9f\x95\x95\x95\xe1\xcb\x97/\x0c?~\xfd\xd7,\xcc\xcf\xba\x81\xeeZ\xb0\x86)S\xa6\xfcy\x22\x90`\xae\xaf\xc4\xc3\xa0/\xcf\xc0\xa0(\xca\xc0\xb0\xfd\xf8c\x86\xff\x1f\xafO\x0e\xf6s\xb3B\xd6\x00\x0eBNNN\xe6\x9a\x10\x1e\x86'/?2\xa4L|\xcf\xe0a\xa9\xc0\xa0\x22.\xcb \xc4\xf7b\x11##\xa3\xf2\xf6{K\xee\xa0\xd8\x00\x023f\xce\xbe\xc6\xcd\xc3\xffA\xc5$(\x9f\xf5\xeb5\xe3\x87\xf7oO?q\xee\x02\xc3\xa53\xa7\xfe\xbe|\xf92F__?\x7f\xd1\xa2EAX\x83\x1c\x19\x03C\xc1\x15\x14\x05@\xfa2J\x1c\xe1\x88\x0d! V\x01b[\x05\x05\x85\x1e -\x06\x10@8\xe3\x0e\x17`\x811\xfa\xa7\xccQ\x17\xe6c\xbb\xf1\xe7\xcf\x1f\x86O\x9f>1\x14\x14\x140b\xd3\x00\xb6a\xc6\xccY\x87\xdeqZ\xf5\x7fcUx\xa4\xa7\xc8\xc3\xa0'\xc7\xc0p\xe5\xd8\x9a3!!!\x8cXm\xe0\x17\x14g\xf9\xce\xad\xfe\xc8V\x99\x95AM\x92\x81A\x80\x8b\x81!a\x9f\xa4\xc9\x97/\x0b\xfe$$$\xb0`$\xb3\xdf\x12^\xf9\xaez\xac\x0c\xa6J\x0c\x0c'\xae\xbea\xf0\xeb\xfd\xcf\xe0\xe7n\xcd\xf0\xf7\xef_f`<\xc8\x1f~\xb0\xed\xff\xbeK[\xff\x03\xd9,`\x0d\xf2\xcc\x17\x8d\xb5e\x18\x18|*O0\x1c\xb8+\xc2\xe0c\xc8\xc8`\xac\x004\xe8\xf7o\x86\xedWV>\xf8\xfa\xff=\xc3/\xde\x0f\xa0\xa4\xf3\x12l\xdd\xb7O\xaf\xa7;\xd5\xbd4\xb1\xb7\xb2`P\x00&\x0bM)\x06\x06\xee?\xf7\xe4\xae}\xffn\xb4n\xf3\xda\xad\x8c2\xdf$A\xea\xee-\xfb\xb1\x1c\xac\xc1\xd3\xd3\x93\xf1\xe9\xd39\xff\x85\xb8\xf42\xb5\x95\x8c\xce>\xbeq`\xe2\xd3O\x9f,_\xbc}\xcf0\xbbu\x15kpp\xf0\xe6\x8f\x1f?\xde\xdd\xbd{w\x0eJ<\x5cx\xc8\xc0\xaa-\xcd\xc0\xce\xca\xc2\xf0\x05\xc4\xf7\xf7\xf3\x03&tF\x86\xa7O\x9f\xf2\x9f9s\xe6\x13\xdc\xd30` \xcf\xf0\x1b\xa6\x18\x046n\xda\xc4\x08L\x06\x0c\xd2R\xd2\x1fQB\x09\x1f\x00i\xfa\xcf\x08\x0c5\x7f\xbf\xff(1\x0d\x0c2t\xb5\x9c\xd0\xb4\xc4\x0fr\x1d\x10\xbf\x02\x09\x02\x04\x10\xce\xb4dW\xf9\x98\x91\x9d\x85\x91\x81\x87\x8b\x85ML\x80\xfd\xaf\xbc\x04\xe7\xbf\xaa@\x8e\x7f\x0c$\x02\x0c\x0b\xe6\xcc\x9dwQP\x80_\x8f\x99\x8d\xf7\xf8\x8b\xef\x82\x8b\xee\x7fW9+&\xc8\xca \xcdt\xcd\xf8\xdf\xe7\x07q\xdf\xbe~\xb2\xfc\xf7\xef\xdf\xa5\xb4\xb44}\x92,\xe8\xea\x9d`$-!z\xf6\xc9?\xfd\xa0\x97\x7f\xe5\x1f\x09\xf3\xb12H\x0a\xb13H\x080\x021\x03\x83\x08\x0f\xd0\xff@\xcc\xc3\xc1\xc0\xb0\xf3\xc85\xb9\x7f\x9f\x1f\xae{\xf5\xea\x95a||\xfc\x05\xa2r\x0f\x1f7\xc7\xd9\x1f\xecJ\x99\xfc\x02\x9a\x8f4\x04\x99\x19\xc4\xf9\x18\x18\xc4\x81\xa1\xc9\x07L\xe7B\xdc\x0c\x0c\x1f\xbf\xfef\xc8\xee\xbb\xc8\xf0\xe0\xa3\x10\x83\x9a\xaa\xd6#s\xc1o\x99\x8c\x7f\x9e\x9d\x079\x12\x1a'@U\x0c\x12@\xcc\x0e\xc4o\x81\xf85\xd0\xf1\xff\xe0\x16\xb0\xb0\xb00\xa8i\x9b\x9c\x15\x13`\x06\xbb\x16\x94\x81\xbe\xfc\xf8\xc3\xb0x\xebU\x86\x85\x07~0(k\x9a3\xa8\xa8\x980\xe8\x02\xc5\xf9\x81F\x89\xf2\xe8\x9f}\xfd\xfe\x02\xd8``Q\xfd\xea\xc0\xd3u\x5c\xc8.\xff\xf7\x83\x91\xa1\xb8\xb8\xb8\x1a\xd9\x82\xbf\xdf\x1f\xef\x9f\xa8\xac\xe6\x9a\xdb\xb3\xea>\xc3\x8e\x93\xaf\x18$U,\x80%\xa6>C\x10\xb0>\x11\x01\xfaH\x98\x1b\xe2+PP\xdd<\xb3}\xe2\x8f\x1f?\xfe\x82\xb2\xdey\xceml\x17\xce20ln<\x0e\xb7\xc0\xb7\xde\x92\xe1\xcd\x1bf_\x94H^\xb4x\xe9q.Nv\x8b\xc7\x7fu\x83~p\xa8?\x12\x00\x1a(\xc2\x0b4\x18h (\x1e\x04\x81n\xfc\xfb\xfd\x85\xc3\xe9\x93\xc7{\x1e=y\xc2`fb\xf2\xd3\xca\xca\x8a\xcb\xde\xde\xbeA\xc6\x9f\xad\xe4\x1f\xc7/N\x98Yl'\x15N-\x5c\xb80\x00k2\xed\x9f\xbaH\x97\xf1\xf7\xbbK\xec\xec\xec\x0c \x0c\x0a>\x10\x00\x95nLLL\x9a+W\xad\x0a\xda\xb8aCkNN.\xc3\x8b\x97/\x18\x9e=}\xfa\x13(\xcd\x0b\xcc\x8e\xbf\x09&SR\x00\xb0\xfa\xf2\x92\x96\x92\xda*\x22*\xca\xf0\xe6\xf5\x1b\x86\xa7\xcf\x9e\x9e\x03\x0a\x9b\x03-\xfaC\x15\x0b\x90,\x0a\x93\x96\x96Z!\x22\x22\xca\xf8\xe6\xf5k\xa0E\xcf^\x02-\x91\xa0\x9a\x05H\x16e\x01}4\x15\xe8\xa3\x97s\xe7\xce\xa5\xbe\x05\xd8\x00@\x00\xda\xab6\xa6\xad*\x0c\xbf\xdcK\xbf\xeem\x07\xed\xed7\xeb\xed\x82\xdbb\x04\x87s\x8e!?V5T\x9d\x08\xd1\xc4\xed\xc7bBp\x89\x05\xdd\x12\xc7\x92\xc5\xe9~\x18e\x12\xc3\x22.\xeb\x9f\x85\xc5\xa8\xc9\x12\x11\x13\xc7&\x8bs]R0E7\x83\x8aF\x8c\x84\x0czA>C\xcb(-\xdc\xd2\x16\xdf\xb7\xed\xc86\xc9>\x0c;\xc9InNr\xdf\xe7=\xefy\xcf\xf3<\xe7\x81\x03\xdc\x91N\x9f\x7foL\xf1j\xcb\xb4fMw@,\xbaw\xc3\xc56N\xa3\xdcM\x94\x8c\xca\x96\x16+Y\x96A\x8e/\xb5\x1fj8\xb8\xe7\x7f\x03||\xb2\xd5n\x15\xf8\x7ft:\x1dD\x92\xeb\xbc\xc3\xf3\xf6\xce\xa4\xca2\xb3\x9e\x0f\x09\x9a\xc5\xbf*\x17\xe7&\xf7\x13\x18\xde\x8b\x82\xda\xda\xda\xb1\xfb\x02hiiQ\x9aL&Y\xa5\xb3\x9e\xed\xb8V\xd2h\xd1\xab\x80\x18\xd5jP\x83]\x9faT\xa2\x88?~\xbapt~>\xf2Rx>\xa9\xae\xdb\xb7W\xbeg/\x82&,\xa6\xe2\xf4?_\x1e\x7f\xbcQ4+\xc1*\xa8\xc1\x94\x97\x0b\x0e\xd4(\x13\xf2\x90\x9e\xcfp\x91\xc9\xbd\xab1\xf0CW\x01\xc3LGo\xfe\xff\x8e;hj\xfe\xc4a3\xe5K\xbf&\xaaK\xcd\x06.e\xccS\x80C`\xc1\xa4\xcb\x90\x1c\x05'v\x0d\xcf'\xe0\xb5c=\x10\xc9\xdd\xc0x\xb6\x5c\xb9:\x13\x9e3\xd6\xbd\xbeo\xe6\xae;P2\xa9\x93\xc47\x0eA\x9b\xb2\x90zaI(k!+2j\x05\xc0\x99\xef\x87\xe0\xd3\x8b\xd3\xc0\x1a\xcb\xe1117\x95L\xf6@\x22\xbep\x0a\x7f\x7f\x05\x9b\x81\xba\x11-\x09\x088\xa9l\x13\x98xt\x05\x80e\x99\xa7\x91\xc4`\xa3M\x99\xae5eN\x81\xf3\xb0A\xa7\xae/\xc1\xf1\xb6!\xf0\xf7\x85`\xeb\xb62\xb0\xe5g\xd6S\xd7S\x80\xd2\xe9&\xbf\xd4\xd4\xd4t\xb6\xf4\x85G+5\xe6\x1cH,\x02\x8c\xf4\xcc\xc6p\xfd\x11\x04\x09\xa6\x01(8\x0d\xd1\x88\x96r]&\x80\x0eg\xffp\x04\x1aN\x5c\x81\x84\xa1\x1c\xcavl\x06+jA\x1e\x97\xa1\xf0et&\x08\xb0\xdc\xd0\xd0px\xc7\x9e\x87+\x19u\x1c\xe2d\x09\x94\x00\x05Oi8\x14\xa1~\x041gw\x90\xebG\x90\xea\xf5\x86\x8c<\xe6\xb2\x00\x1f|\xf6'\xf8\xfa\x95`\x14+\xa0\xd8\x81M\xa0\x84\x95\xb2Q\x12CC\xcbtG.\x15\x17\x17\x1f\x92s\xa2\x99\xc2\xdc4\x0c%\x0aR8k\x1a\x80\xd7j\x0f\xb0,[m\xe0\x92\xcc\xb5\xf1x\xeax\xdb \xf4OpPR\xf4P\xba\x5c\xe9\x0e\xd2f\x00(x\xbe&\xc9\x0c\xe0}@}\xf0\xf8\xfd\xfeN\x8b1Yv\xbb\x9a-'\x80([\xb5r\x0f\xda\xdb\xdb\x13\x82\xd1\xfc\xcb\xe13r\xbd\xb0\xe9Y\xd8\xe2\xc8(\x18\xa9\x9a9\x9b9u\x14\x01\xf6\x06.|9::R\xe8\xf1xX\xb7\xdb\xed-\xacQ\xbfy{\xf7,\x8fr\xe3\xadG\xbe\xda\xba\xc2E\x9cV\xaf\x9b\x9e\x9a\xd8~\xf0\xb9\xc4\xd1'\xd1\xe7S\xbd\x0b\xb0'\xc8\xae:\xf1l\x1c\xf8M%\xfc\xfb\xf7\xee\xf3\xe1ph\xe3\x13\xdbK\x99\xfa\xfa\xfa\x1a\x9f\xcfwL\xf6\x09W#\x91\x08\xdc\x98\xd1iya\xe0\xbb\xc9\xd3\xe4,n\xa1\x8af\xef\x17\xce|\x95\x01\x15\x19\xae\x95\xf8\x83\xafO\xdau\xf0\xfc\xc4}lq\xbftK_)Z\x95\x103a\xb7+\xee]N1\x91L&^\xac\xd9\x0f\x9f\xae\xb8\x17\xb4v&i\x7fT\xd1\x8bsrr\x16\xa4\xa5\xa5MT\x14\xc5I\xb8\x02\x81@\xd0\xe7\xf3\x9d\xe9\xe8\xe8\xa8[\xb3f\xcdz\xa3G4\xf0\x17\x02\x08>\x9f\xc0S\x86\xc9\xce\x1e\xdb+\x8b\x16\x0d0\xf9\xb7`\xb8\x05\x8dvcL\x19Yh\xd5gg\xa1\xfeX\x08\x12 \xc0\x80E\x84\xb4\xb1w\xc1\x0c\x97\xaeg)\x1eh\xa5l&ykVo*\xa27\xa7\xf1\xd9\xfc\xcdo\x942\x95\x85\xdb\xc6`m\xf1\xc1\x94)S\x0a\xd3gXY\xf6\x8a\x829\x132\xa8\x80\x1b\x95Psg\xce\x9e=\xbb\xaa\xa5\xa5e\x1fb\x5c\x8a\xb7\xba.\xef\x003\xeci\xbc\x91G\xd7i\xac7\xdd\x9d>\xae\xc7\x04K.C\xb3M~\xfdK\xf7\x00\xec=\xdc\x09\xbftF\xe1\x88\xe77PY783o\x82\xeb\xd3t\x85o\xc6\x80m\xd0\x8a\xd9\x8c\xdf`Uo\xfaE\x1d<\x99\xe7\xfb\x83\xdbFWWW\xef\x9cV0~&\x97\x81\xa9\x97\xde\xb1k9<\xba\xef\xcd\xc0N\x1f\x97[\x5c\xcdW\xef\x5c\xb9r\xe5\xdc+\x5c\x88{\x17W\xa0\x86\xdc(\xda\xdb^\xec\xce\x1f\xf71\x01\xd0\xfa\xc9\xde(\xf8B\xfd\xb0\xb5\xe1\x0c\xd4\x1f\xe9\x05^F\xd0\xee|\xc8\xc9\xcb\xd5:{\xe4b\xdaL\x0f\x22L\xb3/\x1b\xae\xc3sz\xec\x9c;\xf5s1\x817\xf6\x8b\xd7I\x83\x8c\x1f?~z\x5c\x0aA<\xa67c_\x5c\xb8\xfa\xaa\xf8_\xdb\xf8\x9c~!1@\xcf\xd0\xb3\x97\x05qO\x10,{\xeb6\xd4\x89\xa28\x8f\x0293\xd3\xfdR\x9f5g\xcf\xfa\xbd\xbf\xc2y\xbf\x00\x81H\x02xG\x0e\xb833\xb4\xaa\x84V\x87R'o\x04\xb3\x96\x89\x8c\x14j\x827\x89\xd0gg<\xdf\x14uy/Ti\x95I$\xb2\xb3\xb2\xb2r\x01\x1d\xaf\x94\x94\x94\xd4\xba\x0b\xd9Y\x8c\x92p\x10\x8eico\xbc*\x81S\x17N\xea\x9bg\x80\x0du\xeeK\x1c\xda\xbe}\xfb\xf2\xcbV\x00k\x80TYY\xd9\xfc-[\xb6\xbc\xaf\xaa\xea\x93\x1d\x1d\x17\xaa\xc2\xf1\xce\x19}\xc9\xb1\xb5\x98E\xfc\x93\x1c\xfa,\xd2\x0c\x9b\x19\x88\xdcdpp\x9b\xf7Mr\xf4\xb9\xaf\xe7\x5c\xd6\xd7?\x1e^\xc5\xb1\xccT\xca>\x9c\x95Kx<\x9e\xe3(\x8ce*\xd4\xa9\x0a\xb26\xe4\xdbm\xb3\xc2\xb7\xa6\xec\xfd\xf6o=__;m\xaaV5v\xc8y\xb4\xad\xad\xe5\x13z\xf6\x9a;\xf1{\x1flr\xdb\xad\x89\xefX\xc62N\xab\xcd\x19\xdb\x0f\x03\xdc\xa8]AG\x01\xee\xc4l\xd2\xcc\xfd\xa2\x01^\xdb\xc4\x0c\x12\xbcF\x10\xab[\xcf\xb1\xc2h$\xbc\xd4\xef\xf7_\x87\x13\x02\x81`\x08v\xd77@^\xeedx\xb8\xb4\x04z{z|8\x8b\xaf\x1d8p\xe0\xc3\xd6\xd6V\xa5\xa0\xa0\xa0\x22++\xeb\x0efR8w@\x8a\xcaI\xa1O\x0bg\xa6OP\xb9\x88\x18N\xb6\xc9?{\xbd\xde\xaf\x1a\x1b\x1b?\xc4\x8f;\xaeZ\xf6]\xf9Z\xbdn\xbf\xd3\x16?\xbb\x1a\x92\xf1'06X*\x03I\x0b\x99\xe5\xa0iD\xd2lB\xd0o\x92\x1e\x22\x8b\xc7\xe3\x09L\x81\x0d(Kn}\xa2b\x89\xebDS3|\xb1u\x07\xfc\xd4r\x1a\xac\x96$\x5c\xec\xea\xc6\xb2%B\x1d\xd6\x17\xa8>54\xbal\x98`\xc0\xa0\x8a0l\x98Jm\xf6\xff$\xa7w|\x0f\x96\xdf\x8eo\xce\x19\x88\x05\x8aS\x89\xfey,\xaaQ\x84\xebF\xe0\x19h=h^\xfc\xday$\xb8\x0bs\xf8\x97\xa5\xa5\xa5\x1d\x83\x9f/**~`\xce\x9c9k\x9b\x9aNd\xabj\xcc\x82\xe2\x90\x08\xe2\xca\x04!\xe0\xf7S\x5c\xd0Q\xe32\xac`\xeaGl=\x80~O\x81\xfa \x16\x87\xcf(.e\xaa\xe2P\x80\x17x\xe8C\x22\xc1?\x89\x84\xf0;K\x90\xc8\xb6\x11[\xd0 \x11j\x1fU \x91*$\xe22\x89\xfc\xb1\x22>?\xb9\x96v\x04\x8eD\xd6\x8d\xe8\x8a\x0c\xc9,\x16E\xe9#W\x9a\x02\x8a\xc3\x89D\x04\x8dH0\x18\x00\x9f/\x80\x9a)\xd2I\xb5\x14\x1d\xc9 \x99\xd0\x88-)\x91\xc8<$\xb2\xde\xe5RFa\x0c\x81\x19#\xc1\x00\x12\x09\x04R\xd1H\xa4\x89\x0e\x0e\x91D\xcd\x88\xae\x89\x91\xc8\xdd\xa2$\xaeE\xcf\xca\xfb\x83H\x9f\xe6Z]\x81\x80\xef\xae\xc6\xc6\xaf\x9aG|Q\x8f$('\xcf\x91D\xe9\x15%M\xc9V\x9c\xca\x9d\x1b7n\xf4\xfc\xab\x18\xf8\x87}\x96\x7fr`\xc3\x1a\xca\xd7:h4\xcd\xbcG\x7f&\x1a=\x1a\xd1\xe8\xd7p6\x9bM\x88\xc5b~\xbc\xf6\xa1]2\x8c\xf6\x82\xd0\xdfv%\x86hu\x92\x86\xd1y\xa3:\xd4+\xf5;\x16r\x1c?\xc6H\xf1\x9e\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\xbf\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04tIDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91\xdd*6\x01\xd8/\x83\x14\x88A\x18\x8a\xba\xe8E\x04/\xe0)\x04\x8f\xad\xe0\xd2\xad\xe0ifR\xaaHL4.fV-H\xc1V_4&\xf9\xbe\xb9\xe8\x05\xfc ]\x83\xa4jJ\x1a7\x1c\x90\xb8A\x0c\x8d\xa9z\x02\xc0\xe4\x90\xc8\xa8\x14M\x01\xb8\xc2\xbd\x5c\x01\x9e\x9c\xb2\xbe}\xc7\xd1L\xfdwI&\xc7\xab\xe0,\x16\x01V\x8d\xb3\xfe\x08@\x01\xc1y\xa5\x94\xde\xff\x95\xfc\xca{?\xf9c\x0b\xe0`p\xe1\x80\x8a\xf6\xe8\xd8.\xbav+\x12\xc5\x01\xf6A\x8cQi\xadY\xe7\x1f\x03\xc6c\x98RR\xce9\xd1\xf1\xdc\x02\xf0`\xd8\x1a(\x99\x92:\xcc\x02\xa8\x81\xad\x85\x10n5AE\xf0\x0atq\xdb1>9\xe7\xdb\xfaZk\xef3\xc6,a,\x80\xb2\xc4Z\xcb\xfa\xe6\x080\x86\xfc.\x0dP\xb9H\x04\xe0 +\xe1\xbbr\xf4t\x8a\xe0\xb4P\x16\x8e\x97*|\xc1jo\x88\xf8\xad\xaahWl*\x01\xee\xfc\x85k\xc1_d\xcbG\x80\xf6\xac$\x87A\x18\x06\xf6P~\x00_\xe0\x19\x9cy8\x0f\xe0\x09\x1c9s\xe5RM$W\xa9\x1b/\x09iK%\x22UA\x08\x81\x9d8\xe3\x99\xe9\xc7?\xf0\xf7=\xffJ\xe0J\xe0\xe4\xe3\xeey(\x86&\x89Qr\xb8\xb2\x5c\x07\xcd(\xe0\xceb1\x0aQ\xe0\xe0\xd9]\xd7}ue\xa1U\x09\xf8\xb5D\xcc\x1d@\xf0\x10\xcc\xd4\x81\xa4UN\xf1M\x8b\x03\xa5\xa8\x08\xcdX0\xbcc\xdb\xb6\xf2\x12\x92h\xbb\x96\x80F#-\xda/yV\x87\xce\x80\x15|J2x\x09w\xec\xa6\xd3u\xfc\xbc\x87W\xdfK\x83O\xdd\xdb\xf7\xfd\x85\xb0\xa4<\x99\xa6iBYj4N[\x90\xa2\x12\xf2\x94\x0d~\xf0\xc2\xdb\xb6M~x]\xd7\xdb<\xcfO\xc3O\xa2\x8eUw\xc0\xa3\xd9\xb4s@3\xd4\x10\xc68\x8eo\x87U\x0a\xbej\x1f(\x1d@.\x88\xa1\xbe\xef\x83V\xfcY#\x93\x14\x83t\x8dyY\x96\xe0\xaa\x0e\xc3\x10`X\xe2\xe0\x96\xaa;\x94\x00\xef\x8e1Rh\xd2g\x9a\xa6`\x05K\x22Z\x93C)\x1f\xfbP\x02\xa9\xe0\xf9K\xe9\x1e\x9a\x0e\xea\x1d6?44Y\xd2V\x03\xf3&TTBZ\x12\x1c\xc7\x91\x00\xf48f\xa9\x83r\x93^\x93xUv\x807\x1amP`9d\xceJ\x80\xff9P|\x88s\xa1.\xf7\x19+\x99j(t\x04\xb3s\xc8]5Q\x1fSj\x8f\xef\xea\xf1\x04=\x81{\xa8\xf4\xe5J\x9ca<\x00\xdb\xfa\x15g\xec\x8b\x8b\xb0\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x1b\xdc\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x10\x91IDATx\xdab\xfc\xff\xff?\x036\xc0\x04\x22\xd6\xaf_\xff\x7f\xf2\x8e\xdf\xc6\xe7.\xdd\x0c\x84\xcb\x80t\xec\xb9\xfc\xdf\x18H\x1b\x17,\xfao\xbc\xe4\xc8\x7f\xc6c\xcf\xb7\xfeg\x01I\xde\xb8tX\xee\xe2#\xdbGy\xee\x0c\x0cJ\xe2\x8cb\x1a\xe6\x8a\x10\x1d \xfc\xec\xdd\x7fv\x10\x9d\x94\x94\xf4\x1fl/L\x02\x19\x83$\x01\x02\x88\x11\xaf\xab\xd2\xa7\xbf7>x\x9d\xc1x\xe9\xd2\xa5\x87a\x12\xcc\xd2\xd2\xd2\xff\x93C\xadg\x95O=\xc5\xa0\xa0\xe3\xb2\xeb\xf6\xc9\x95\xb7\xb7n\xddj\xc5\xc2\xc9\xc9e\x5c\xbb\x8a\x811/\xd2\x8c\xe1\xdd\xd5\xa5\xa7\x0e\xfe_\xc1\xc0 \xc3\xe0\x8dl)\x93\xb9\xb9y\xac\xb1\xb1\xf1-\x10\x1f\xc3r\x13\x13\x93l U\x03\x10@(\x12\xf3\xe6\xcd\xfb\xcf\xca\xca\xca\xf0\xe5\xcb\x17\x86\x1f\xbf\xfek\x16\xe6g\xdd@w-X\xc3\x94)S\xfe<\x11H0\xd7W\xe2a\xd0\x97g`P\x14e`\xd8~\xfc1\xc3\xff\x8f\xd7'\x07\xfb\xb9Yax\x8f\x93\x93\x93\xb9&\x84\x87\xc1P\xfa#C\xda\xc4\x07\x0c\xbd[\x19\x18~\xb1\xca2\xf0\xf2\x09.bddT\xdeq\x7f\xe9\x7f[7\x8b\xff \x1a\xee\xa4\x193g_\xe3\xe6\xe1\xff\xa0b\x12\x94\xcf\xfa\xf5\x9a\xf1\xc3\xfb\xb7\xa7\x9f8w\x81\xe1\xd2\x99S\x7f_\xbe|\x19\xa3\xaf\xaf\x9f\xbfh\xd1\xa2 \xacA\x8e\x8c\x81\xa1\xe0\x0a\x8a\x02 }\x19%\x8ep\xc4\x86\x10\x10\xab\x00\xb1\xad\x82\x82B\x0f\x90\x16\x03\x08 \x9cq\x87\x0b\xb0\xc0\x18\xfdS\xe6\xa8\x0b\xf3\xb1\xdd\xf8\xf3\xe7\x0f\xc3\xa7O\x9f\x18\x0a\x0a\x0a\x18\xb1i\x00\xdb0c\xe6\xacC\xef8\xad\xfa\xbf\xb1*<\xd2S\xe4a\xd0\x93c`\xb8rl\xcd\x99\x90\x90\x10F\xac6\xf0\x0b\x8a\xb3|\xe7V\x7fd\xab\xcc\xca\xa0&\xc9\xc0 \xc0\xc5\xc0\x90\xb0O\xd2\xe4\xcb\x97\x05\x7f\x12\x12\x12X0\xe2\xe1\xb7\x84W\xbe\xab\x1e+\x83\xa9\x12\x03\xc3\x89\xabo\x18\xfcz\xff3\xf8\xb9[3\xfc\xfd\xfb\x97\x19\x18\x0f\xf2\x87\x1fl\xfb\xbf\xef\xd2\xd6\xff@6\x0bX\x83<\xf3Ecm\x19\x06\x06\x9f\xca\x13\x0c\x07\xee\x8a0\xf8\x1822\x18+\x00\x0d\xfa\xfd\x9ba\xfb\x95\x95\x0f\xbe\xfe\x7f\xcf\xf0\x8b\xf7\x03(\xe9\xbc\x04[\xf7\xed\xd3\xeb\xe9Nu/M\xec\xad,\x18\x14\x80\xc9BS\x8a\x81\x81\xfb\xcf=\xb9k\xdf\xbf\x1b\xad\xdb\xbcv+\xa3\xcc7I\x90\xba{\xcb~,\x07k\xf0\xf4\xf4d|\xfat\xce\x7f!.\xbdLm%\xa3\xb3\x8fo\x1c\x98\xf8\xf4\xd3'\xcb\x17o\xdf3\xccn]\xc5\x1a\x1c\x1c\xbc\xf9\xe3\xc7\x8fww\xef\xde\x9d\x83\x12\x0f\x17\x1e2\xb0jK3\xb0\xb3\xb20|\x01\xf1\xfd\xfd\xfc\x80\x09\x9d\x91\xe1\xe9\xd3\xa7\xfcg\xce\x9c\xf9\x04\xf74\x0c\x18\xc83\xfc\x86)\x06\x81\x8d\x9b61\x02\x93\x01\x83\xb4\x140U\x22\x87\x12>\x00\xd2\x04L\xa2\x0c~\xfe~\xffQb\x1a\x18d\xe8j9\xa1i\x89\x1f\xe4: ~\x05\x12\x04\x08 \x9ci\xc9\xae\xf21#;\x0b#\x03\x0f\x17\x0b\x9b\x98\x00\xfb_y\x09\xce\x7fU\x81\x1c\xff\x18H\x04\x18\x16\xcc\x99;\xef\xa2\xa0\x00\xbf\x1e3\x1b\xef\xf1\x17\xdf\x05\x17\xdd\xff\xaerVL\x90\x95A\x9a\xe9\x9a\xf1\xbf\xcf\x0f\xe2\xbe}\xfdd\xf9\xef\xdf\xbfKiii\xfa$Y\xd0\xd5;\xc1HZB\xf4\xec\x93\x7f\xfaA/\xff\xca?\x12\xe6ce\x90\x14bg\x90\x10`\x04b\x06\x06\x11\x1e\xa0\xff\x81\x98\x87\x83\x81a\xe7\x91kr\xff>?\x5c\xf7\xea\xd5+\xc3\xf8\xf8\xf8\x0bD\xe5\x1e>n\x8e\xb3?\xd8\x952\xf9\x054\x1fi\x0823\x88\xf310\x88\x03C\x93\x0f\x98\xce\x85\xb8\x19\x18>~\xfd\xcd\x90\xddw\x91\xe1\xc1G!\x065U\xadG\xe6\x82\xdf2\x19\xff<;\x0fr$4N\x80\xaa\x18$\x80\x98\x1d\x88\xdf\x02\xf1k\xa0\xe3\xff\xc1-`aaaP\xd369+&\xc0\x0cv-(\x03}\xf9\xf1\x87a\xf1\xd6\xab\x0c\x0b\x0f\xfc`P\xd64gPQ1a\xd0\x05\x8a\xf3\x03\x8d\x12\xe5\xd1?\xfb\xfa\xfd\x05\xb0\xc1\xc0\xa2\xfa\xd5\x81\xa7\xeb\xb8\x90]\xfe\xef\x07#Cqqq5\xb2\x05\x7f\xbf?\xde?QY\xcd5\xb7g\xd5}\x86\x1d'_1H\xaaX\x00KL}\x86 `}\x22\x02\xf4\x9107\xc4W\xa0\xa0\xbayf\xfb\xc4\x1f?~\xfc\x05e\xbd\xf3\x9c\xdb\xd8.\x9c\x05\x96\xac\x9d\xa7\xe0\x16x\x96\x9b1\xbcy\xc3\xec\x8b\x12\xc9\x8b\x16/=\xce\xc5\xc9n\xf1\xf8\xafn\xd0\x0f\x0e\xf5G\x02@\x03Ex\x81\x06\x03\x0d\x04\xc5\x83 \xd0\x8d\x7f\xbf\xbfp8}\xf2x\xcf\xa3'O\x18\xccLL~ZYYq\xd9\xdb\xdb7\xc8\xf8\xb3\x95\xfc\xe3\xf8\xc5\x093\x8b\xed\xa4\xc2\xa9\x85\x0b\x17\x06`M\xa6\xfdS\x17\xe92\xfe~w\x89\x9d\x9d\x9d\x01\x84A\xc1\x07\x02\xa0\xd2\x8d\x89\x89Is\xe5\xaaUA\x1b7lh\xcd\xc9\xc9ex\xf1\xf2\x05\xc3\xb3\xa7O\x7f\x02\xa5y\x81\xd9\xf17\xc1dJ\x0a\x00V_^\xd2RR[EDE\x19\xde\xbc~\xc3\xf0\xf4\xd9\xd3s@as\xa0E\x7f\xa8b\x01\x92Ea\xd2\xd2R+DDD\x19\xdf\xbc~\x0d\xb4\xe8\xd9K\xa0%\x12T\xb3\x00\xc9\xa2,\xa0\x8f\xa6\x02}\xf4r\xee\xdc\xb9\xd4\xb7\x00\x1b\x00\x08@{\xb5\xc6\xc4QE\xe1\xc3,\xeccf\xb7\xb0;\xbb\xec\xab;\xdbX\xac\x0f\xa8\x88\xb5\x94bR\xd4\xd0TE\x88&\xb6?\x88J\xb1\x89\x80i\x13K\xa3\xf8\xe0GU*ih\xc4\xa6h\xd2\xd0\x185\xb1\x09\xd1\xc4b\xc5\x88\xa5\x09\x0fA[%\x8aFL\x09)\xcc\xa0\xcbc\xc3.\xb0\xec\xc2,\xbb\x8b\xe7\xcc\xd0\xc6F\xd2\x87\xa17\xb9\xb9\xb3\xb3\x99\xf3\xdds\xee\xbd\xdf\xf7\xdd\xdb\x0ep]:}\xec\xb0/\xe5\xd9F\xbfaM3 \x16-\xdd\xd0\xde\xc2\x1a\xb4\xbb\x89\x92Q\xd9\x14\xb1\x92e\x19\xe4\xe8\xd2\xe7\x87\xaa\x0f\xee\xf9\xdf\x00\xef\x9dhv9x\xeeo\x93\xc9\x04\xa1\xf8\xba\xa6\xd1yW[\x5cg\x9f^\xcf\x05x\xc3\xe2\x9fE\x8bs\x93\xfb\x09\x0c\xcf\x85\xbb\xbc\xbc\xdcwK\x00\x8d\x8d\x8dZ\x9b\xcd&\xebL\x8e3\xad\x97\xb3\xeb\xecf\x1d\x10\xa3:,zp\x99UF%\x8a\xf8\xfd\xc7oj\xe7\xe7CO\x05\xe7\xe3\xfa\xca}\xa5\xf2M{\x114a\x11\x1dk\xfe\xe9\xfc\xf8\x03uB\xba\x16\x1c\xbc\x1el\xa9\xc9\xe0A\x8d\xb2!\x0f\x999\x95\x8bl;\x1f\xaf\xeb\xed\xe9r3\x8c?\xfc\xef\xef\xaf\x9bA}\xc3\xfb\x1e\xa7-M\xfa%V\x92\x9bna\x13\xd6\xd4\x14\xf0\xf0\x1a\xb0\x99T\x92\xa3\xe0\xc4\xae\xc1\xf9\x18\xbcp\xa4\x0fB\xc9\x1b\x98\x8a\xfb.\x5c\x9c\x0e\xceY+_\xdc7}\xc3\x0c\xb4L\xe2\x04\xf1\x8d\x877&\xec\xa4^X\x12\x9a5\xbf\x222\xfa\x14\x80\xcf\xbe\x1b\x81\x8f\xda\xfd\xa0\xb1\xe6\xc3\xfdBr\x22\x1e\xef\x83Xt\xe1$~\xfe\x0cn\x06\xda\x8dhI\x80\xc7Ne\x9b\xc0\x89\x87\xaf\x02h4\xcc#Hb\x90\xe1\xd4*\xb5\xa6\x99S\xe0T\xdc\xa0S\xb3Kp\xace\x04:\x07\x02\x90\xb3%\x0f\x9ci\xea\xfb\xc4l\x02P:w\x92_\xaa\xaf\xaf?\x93\xfb\xc4\xe6\x22Cz\x12\xc4\x16\x01\xc6\xfaf\x22\xf8\xfe^\x04\x11\x15\x00\x0aNM\xb0\xa2\xa5\x5c\xa7\x060a\x1f\x1c\x0dA\xf5\xf1\x0b\x10\xb3\xe4C\xde\xb6M\xe0@-HeU\x0a_Fg\x82\x00\xcb\xd5\xd5\xd5\xafn\xdbsw\x11\xa3\x8fB\x94,\x81\x16\xc0\xfd\xb0\x81E\x11\x1aD\x90tF\xcd \xb9\x93@\xd6\xe3\x82R\x10\x0a\xfe\xce\xc7\x7f@\xd5\x87\x13\xc0\x09\x85\xf0\xd0=\xac\xf2\x9f\xd3\x0cXFT\x18\xabj\x85\xf1\x8c\x9c\xcb\xca\xca:$'\x85aA\x8e@\xed\xde\xa3\xcaH\xdd\x92\x9dL\x0a\xe7P2\xe0\x8c\xc6\x03\x1a\x8d\xa6\xc4\xc2\xc6\x99\xcb\xe3\xd1\xc4\xb1\x96a\x18\x9c`!;s\xa3R.e\x07\x19\xd5\xddD\x19\xa6\x19\xe2\xcc\x10\x9e\x07\xd4\x87\x8a\xce\xce\xce6\xbb5\x9ewE\xcd^y\xee-E\xcd\x96c@\x94\xadS2\xd8[\xf6\xbc\x84C\xbc\xbb\xe7\xfb\x0fJ\x0f\xf7\x80\x18\xdb\x0c\xdbs6\x82;M\x0d\xe86\xab\xdd\x85\x9d\xd6`\xf0\xe7\xf6\xd3x\xb2\x13555\x01\x9f\xcf\xd7\x8fF\x11\xf2+\xefR\x04\x89F\xfa={i\xc9O\xe2\x7f\x95\x8bX\xa3\xd9\xe4\x9f\x9a\xd8zpW\xacv{\x86Z*7\x96\x83\xec*\x95\x84JCe\xba\xf4[\xf7\xd9`0\x90\xf1\xe0\xd6\x5c\xa6\xaa\xaa\xaa\xac\xa3\xa3\xe3\x88\xdc\xc1_\x0c\x85B\x90S&\x00\x8da\xbf\xbc0\xf4\xed\xe4)r\x16\xd7PEC\xd3\xa7\xde4\x9d\xfb0v\xe8\xb6\xdb\x96\x7f\x04h\xce\xcac\xa3\xa8\xa3\xf0\xdb\x99\xdd\x99\xdd\x99\xd9\x9d-\xb5t\xdb\x02)\xa0\x85\xa2\x09\x88\xc2\x1f\x8261\xa8\xa5\x15D\x9bz+wD(5\x91\x18\xe2\x85\x09 Z\xb0Eh!\x04\xc2\x11\x10\x0f\xa0\xa6\x04\xb9B\x08\x94\x84C<\x10\x8b-\xb5P\x14,\xdb\x83\xb2Wwg\xbb\xed\xee\xfa\xde\x1cP\x10\x03\x98\xd6t7/\xbf\xc9\xce\xce\xee\xf7\xcd\xef\x1d\xdf{\xd3\xe3\x7f\xd0\xd3/\xf3\xdd^\xb0\xb8ds_\xc9\x12\xceaM\xd1<\x86e\xfa\xc7c\xd1d\x88\xc7\x5c\xd1X\xbc\x11\xcbO3\xd6\xa0\x8b\xd1\xb8\xa9\xdcd\x91\xf6\xbcS8\xbd\xb9\xa7\x09\xdcv\x07>*\xfeZJ\xb1\x87\x8al<;\x93\xe38\x0bI\x032\xc3\xdf\x10\xb0Q\xdb\xd4j`\xac$\xcf\xc8\xf0|\x07*\x82\xb5v\x87s\xfe\xe4\xd7_\x09\xfeo\x04JKK\x93l\x82xB\x14l\x83P\x91\xe1^\x09?x\xda\xc5]\x87/\x0d\xde\xc7q|L\xc0JhC\x13\xadf\x90l\xacfVVm\xa2I@\xf1l\x94i\xac\xab\xccV\xda\xaeN\xe8\xech\x1fE\xc4\xf0\xbf\xeacq\xe6\x91\xa9S^k\xeaQ\x02e\xabV\xaf\x94Da\xae$I\x103\xcb\x15?\xb7\x0c*\xbb\x12\xe9\xe3\xe5,\x04\x98\xd1\x01\x9b\x81Hh\xc0M\xaa\xcc\x11\xb0\xfd\x16\xd1\x04N\x13jtLkkk\xb3\xf3l\xcd\x99\x02%\x14\x9aD\xbb\xa2\x84#e/\xbf\xf4\xc2\xdc\x1e!\xb0\xfc\xf3\x15\x15\x0e\xbb\xf4\x0c\x81o3\xb9\x16\xfc\xe2\x1f\xb1G\xbb\xdb,\x02dAD\xe0\x0e\x81\x05\xbbU\x03\x88\x85\x0e$^\x1b_\xd8\x10,g\xd1@\x93\x91\x97E:bPU\xef\x83\xe5\xdb\xeb\xe1\xc9aJ\x8eKl[H\xa3S,~\x15\x93'O~\xb6[\x09,[VQ%c\xd4\xd8\xb1c\x17\xd6\xd6\xd6\xeeC\x8c3\xf1T\xd3\x8d\x13`\x86=\x8b'\x86\xd2q\x02\xebNt%\x0eh1\xc0\x92\xcb\xd0\xdd&\xbf\xfe\xa3\xb9\x13\xf6\x9eh\x84?\x1aC\xf0S\xf5ePX\x178\x92G\xc0\xc0\x04M\xe1\x1b1`\xed\xb2cV\xfd7X\xc5\x9dxE\x03OV}\xf2\xf0\x8e{\x8a\x8b\x8bw\xde\x9f\x95>\xca\x9c\x84\xa9\x97\xdea\x0d\xcf\x07\xd3\x8a\xb4\xda\xb3a\xbe\xee\xf0\xe8\xbe\x0f\x01\xfb\xe0\x80\x8c\xdcb\xaex\xe7\xbcy\xf3&\xdc\xe4B\xe6U\xb8\x03\xa5\xe4F\xa1\xd6\xfa\x5cW\xe6\x80M\x04@\x9d'\xbbC\xe0\x09t\xc0\xf6\x83\xe7\xe1\xc0O\xad\xc0I\x08\xda\x95\x09iC3\xd4\xc9\x1e\xb9\x98z\xa7\xbb\x10\xa6\xbb/\xe9\xae\xc3\x99\xb5\xd8\xb9\xf8\xdb\xef\xb9\x04^\xaf\x17KH\x83\xa4\xa7\xa7?\x18\x11\x03\x10\x09k\xc3\xd8E\xb3Vh\xb32\x96W\xd7w\xa7,Q\xd7\x0f\xd7\xbc\xa5\x81\x11\x19\xa0k\xe8\xda\x1b\x82\xb8\xc5\x0f\xa6\xbd\x15\x9b+\x04A\x98H\x81\x9c\x9c\xecZ\xd0nI\xdb\xb3q\xef\x9fp\xc9\xcb\x83/\x18\x05\xce\x9e\x06\xae\xe4$\xb5+\xa1\xdd\xa1\xd4\xc9\xe9\xc1\xacf\x22=\x85\x1a\xe0\x0d\x22\xf4\xd9\xf9\xeac9M\xee\xbf\x16\xaa\x9dI0\xb8\xb3\xb0\xb0p\x12=^\xc9\xcb\xcb+se\xb3c\x189j'\x1c\x03\xfb\x0e\xd6&\xc3\xef\x96\xab\xeb\x8cO\xf2\xd4\xf5B\xf3y\xadx\xfa\xd8@\xe3\xbe\xe8\xd1\xf2\xf2\xf2\x82[\xd6\x81m\xdb\xb6\xad\xc1f\xe3\x0d\xf5iQ\xc4\x5c\xb1\xaf\xae_Y\xbb\xf8\x80\x97\x9a@\x87\x1e\x0fF\x06\x227\xe9\x1a\xdc\xc6y\x83\x1c}\xeei\xb9\x98\xf2\xf3\x8f'\x8a\xcc,3\x8cR\xa8\x891E\x8f\x1f;\xbe\x18e\xe6g\xd8\x11\xc5\x87\x0f\x1f>!33s\x96u\x8c\x7ft\xdc\xd6q-\x02\x06\xb9\xee\xd5v\xbf\xf1\xdc\xf5\xb4\xa9X\x94\xf0Q\xc7\xc9\x9a\x9a\x9a5\xd89\xed\xfa\xd7J\xbcz\xdd\x17.\x9b%\xfa=\xcb\x98\x06\xa8\xbd9c\xfd\xa1\xd3\xdcg\x97\xdf\x9e\x85\x95\x98\x8d\x19\xb9_\xd0\xc1\xabEL'\xc1\xa9\x04\xb1\xbb\xad>\x95\x1d\x0a\xb6\xcd\xf4z\xbd\xfd\x15E\x01\x9f?\x00\xbb\x0f\x1c\x84\xa1\x19\xf7\xc1\x8b\xf9y\xd0\xda\xd2\xe2\xc1\xbb\xf8\xf1\xa1C\x87\xd6\xd7\xd5\xd5\xc9YYY\xd3SRR\x1ec\xeem\xcb\xe8\x14CR\x8coW\xc90\xed\xbcb\x0e\x0am\xb1s\xd2\xefn\xb7\xfbHee\xe5z\xfc\xb8\xe1\x96m\xdf\xcd\xaf\xa5k\xf7;\xac\x91\x0bK!\x16\x99\x81\xb1\xc1\xd2\xae\x90\x162\xdaA\xc3\x88\xa41\x84\xa0\xdf$=D\x16\x89D\xa2\x98\x02\x0f\xa2,\x19=c\xfa4\xe7\xe9\xaa3\xf0\xcd\xf6o\xa1\xa6\xf6,XL1\xb8\xd2\xd4\x8cmK\x90&\xac\xefQ\x7f\xaaktI7^\x87A\x1da\x9bn\x0a\x8d\xd9\xff\x93\x9c\xfe\xf6$\x98.\xff\xb25\xad3\xec\xcb\x8dG;&\xb2\xa8F\x11\xae\x0b\x81'\xa1\xb5\xa0\xb9\xf1k\x97\x90\xe0.\xcc\xe1\xdf\xe5\xe7\xe77t\xbd>''\xf7\xb9\xf1\xe3\xc7\xaf\xac\xaa:\x9d\xaa(a\x13\x8aC\x22\x88;\xe3\x07\x9f\xd7KqA\xbe2\x1b]\xeb@\xaf\xed\x07\xb0!\xa4@}\x1e\x9b\xc3\xb7e\xa7\xaf1[\xadV>\x1c\x0e{\xf1\xd8\x83vU7\xaa\x05\x81\xdbN%\xbaiwb\xba\xd1\xf3F\xa5\xbbw\xeao@:<|3(U\x02\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\xed\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04\xa2IDATx\xdat\x8c\xb1\x0d\x001\x08\x03\x0d\xa1\xcd4\xd9\xbfe\x94T\xac@x\x99\xb4yK\x87\x91lY\xaa\x0a/)~d<\x11QsN\x88\x08\xb8\xb0\xf7\xbe\x81\xaab\x8c\xd1d&\xcc\xecN\xb1u\xce\x81\xbb\xb7\x93\x0e\xf8\xb0\xb9\xd6j'\x9f\x00\x9c\x8e\xb1\x0d\x000\x08\xc3\xa0\xea\x00\xff\xdf\xc35\x1c\x01\x03\x12U\x90x\xa0\x9e\xb2$1\xffYED\xe3\x18`FU\xf9\xac\x95\x99\x91\xbbO\x06\xf3\x91\x99\x0dEPU$\x22|\xb7\xbe\x120\x04O\x00\xe1t\x15^G\x81\xc0\xbd{\xf7\xfe\xf3\xf1\xf1\x81\xed\x86\xd9\x0f3\x19\x84EDD\x18Qlx\xfb\xf6\xed\x7fnnn\xb8\x06X8\xc34\xbc|\xf9\x92A^^\x9e\x11n\x14H\x01\x08\x83\x14\x83\xdc\x0e\xd3\x08\xd3\x0c\xb3\x95\x05\xddj\x98\xe6\xdf\xbf\x7f3 \xc7\x13(40<\x0d\xf2\x07\xb2m 9\x98\xbc\x92\x92\x12\xd8\x0f\x00\x018%\x83\x14\x80a\x10\x08\x86\xfe\xffA\xf9B\xde\x92\x93\x1e\xbcZWj\xb0\xad\x14\xd2@H\x08\xbb\xc4q\xdd\xee\xd2\xd16\xd7\xb6aA\xcf95w%\x03\x13\xd1\x9bAD4\xc4\xd9\x80m\xb3\xe2\xda\xdb\x0f!\xc4\x18f\x032)K\x0aC\x0e\x09\xe5\x84\xf9\x13\x9a\x99[\xef\xbd\xa6\x8e:\xc1\x809\xb7\x84u\x8c\xa1\x96\xac\xdf\xf1v-\xd7\xfd\x87\x8e\x16V\x86\x12\x1a\xe3\x9b\xe1\x9f\x06\xcb\xc1\xcfS\x00\xe6\xaa\xd8\x86\x81\x10\x06ZQD\x81X\x82\x1d\xe8\x18\x81\xd5i\xd8\x81\x0d\xa8\xe8\xf2\x8eb\xcbo\x8c^)^\xfa\x0e,\xc1\xe1\xbb\xf3\xf1\xb7\x97n\xb7\xd2\xe3\x00\xde\xba\x80\x1e\xf7\xde\x9f\x8cE\x5c[\x16\x91\xdc\xe3\xb9c\xc6y\x0eL\x00r\xaa\x05 A\xa4\xa0r-\x03\xd0\xa4\x88\x13Z\x00\xe0\xfc\x1c\x9d\x9dr\x0d\x9do=\xe4\x12@\xd3\x81k\xe7\x1c\xf4\xde\xb9\xbb\xd6\x1a\x94R\x96\xae,*_\xbb\xf1\xd3T\xe0\x1f\x87\xff\xd0\x9c\x13b\x8c\x5c\xd76\xd7\xfb\x05\x80\x92[^@\x89\x9es\x86Z+\x84\x10\xb8\xa6\x81\xe8\xfcVd=\xd2Rd\xa4'\xa5\xc4\x99f\x89|\x090\xc6\xf8\xda\x8d\xd3G\xe9\xb1\xa3\xf1g\xf1U\xcf\xbb\xa3\xe2#\x00\xfbU\x8fC!\x08\x83\x8dy\xbb\xa3\x03!qu\xc1cpd\x8f\xe0\x09\x98%\x9e\xe4Y\x12^*\xf4\x07\x07\xdf\xe4@4Z\xfa\xb5\xd0\xf6k\xdfZ\xf4\x02AA\xb6[\xbb\xddLZ\x93\xcc\xf4\xed\x7f\xf0\xf79\xff\x06p\x03\xf8\xf1\xab\x88\x0c\xe2\xa1I\xab(e\xb8\xf2T\x07K(\x90\xcabv\x14J\x86\xa3\xce\xae\xeb\xfa\xa3\x9e\x05WM\x81\xdf\x02\xe2\xae\x00\x8c\x07aN\x19H\xf3\xb2'Vx\xe5\x8a\xf4>\x1c\x869\xa05fo!\xadl\xb7\x00Xe\xa4W\xf6k\x9a\xd5\xa5w\xc03\xde\xab\xdc\xad\xa2\x84\xab\xe9\xa9\xcd\xc7G\xea\xeaG\xae\xf1ru\xa0\xfdo\xdb\xb6\xd3\xa6\xa5\xf2\xb7\xae+\xd1\x16k.\x8f\xca\x9c\x02\x10\xdd6\x10\xf3P\x11O\xd3t(W\xe2\xfb<\xcfts\xad\xd42\xf8\xf2\x0aD8\x1bo\x83\x98@\x91\x84\x97\xa5w\x97e!f\x04Fd\x19\xfc\xd5DV\x96%\x81\x18\xc7q\xf7l\x18\x06zVU\xd5\xf73\xb1\xc6\xc4\xd0\xee\xba\x8eX3\x18\x5czAa|\xd34\xf4\xcc\xfb\xfdK\x01\x1ceG-v\xf3\x1bR\x00\xb6\x0b\xce\xb8\xa0\xef\xe3\x13}\x1a\x89\xb3\x00y\xa0\x8a\x88\xa7\xb50\xa7\x85J\x9c\xd1\xc2`l%d\xd3\xbe\xefCFz\x80\xb2\xf2\x80\x05B\xc6q~\xb5mK\xfd\xd8\xf38G\xb3\xb2\xae\xd5wy\x05d\xa2\x89\x02\xc6x\x80\xb0\x98s\x04\x80<\x1c\xc8\xaaF\xa5ag\x80\xe4\x06\x83\xe8\x1cEn\x04\x8a\x029\x032g>\x97\xd4\xf3\x92:\xa2\xbbF\x92Q\xc4\xf0H)}\xab\x12\xbfp=\x01\xc0\x18?~\xda\xb0\x86\x8b\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\xe2\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04\x97IDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91;\xc1\xe7\xbb\x87\x8b\x1e\x80?\xd05$\x95(i\xbdtC\xea\x85\x1e\xaa\xa9\xfa\x02\x80\xe0)%J\xd1\x0c\xc0\x1a\xdcn\x05:8\xcb^\xde\xebnf\xdf\x0d=\xc1u\x15V\xc6]\x00\xde\xb2\xb2\xff\x0a\xc0\x02\x84J\x07\x09B\xc3\xad\xebz\xfa\x13}\x1fM\x00\x0f,\xe7\x1c\xe6y>\x9f\xb7m\x0b\xcb\xb24+r\x01\xd8\xc8\x94\xfdm\xbf\x9a\xf7\xd1\x0d \x010\xc1p\x84\xe8R.\x90\x12B\xee'\xda\x96\x16\xcc\xa2\xd8h\xefr_\xbe\xfb\xee\xf2\xf1?\xf8\xfb\x9c\x7f;p;\xf0\xe3\xe3!\x99dS\x13\xa6(]\xba\xe2\xba\x0eT\xa3\xc0\xed,\x9ef!c8d\xe0,\xcb\xbe\xba\xb2P\xab\x1a\xe2\xa7\x1ca#\x00\xc6\x83V4\x19\x08[e\x9f\xde\xa4z\x13X\x91m\xae\xb0`\xf0\x0d\xd0G\xa7!\x84\xc9v\xca\x01LyHd?\xd6\xb3\xba\xb4\x078\xe3}%\x03U\x00\xf9~\xb3\xc5\x8b=\x9f\x92b\xe2\x08pF\xbb\xd7m\xdb^p\x03\x0cGQ\xa4!\x08P\xe4d\xb5\xef\xd9e\x07\xa4\xb01\xf7\xeb\xba\xeakUU\xaf\x8d\x0f\x0e\xf5}\xaf\xd24Uu]\x93\xd214\x02\xa7\xf2\x00\xb6BX\xa4\xe0\x5c\xa8m[5\xcf\xb3\xae\x8a\xb8\xf7\xdf\x9e\x07\xae\x0e0z\x18\x06]uA\x14\xbe\x9e\xc8(\xec\xba\xf7\x062\xe6\x98\x05hp\x9a&\x05\xb5RY\x96(}\x86\xb0\x93\x18B\xbe\xec\x88q\xb7=\x176n\x92$\xba\xb6\xcd\xf3\x5cu]\xa7\x8e\xe3P\xe38z\xabD\xca!\xce)\xd6\x01J\x02\xf8\xa4\x00\xd6\xe3.\x8aB-\xcb\xa2\xf6}'\xdf\x0b\x8d\x8a\xb85\xe1\xe3j\x97\xc7a\x98\x8e\x82k\x0c\xd0h\xd34\x1aR\x10\x1d.\x13K#\xf0\x90@HR%\x9b\x11\xc71\xbarp\x00 \x81\xa3\xfd\xcc=\x1c8\xbd\x89}<-\x99/\x9d\xc39\xf36\x16\x0au$\xc4\xc93\xdfc\x8bz[RK\x1a\x91\x92d$1\x5c\x22\xa5\xef\xae\xc4/\x8c'\x7f\x08B\xb3\xa3\xd0\xea\xb7\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\xd6\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04\x8bIDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02\x88\x91\xedE5r\x8fCi+Ej^\xc9\xe5.N|\xb6\xf3\xb5\x96n\x97\xd2\xdf\x01\x1c\xba \x1a\x17]\xb0\xb0\xc0\xb5>@/\xb8\x97\xb9\xa7\xc7\xa7\x0fL\x00\xc4\x8c\x05\xc0 \xbcP>\xe7\x004)B\x03\x03H\xad\x942\x03\x91\xaf\xf5\x8b\x5c\x02h:0\x1cct9\xe7\xe5\xa1\x9a\xba\x8f\xbc\xc9\xd2e\x0b\xa6\x94\x5c\xad\xd5\xf5\xde\xdf\xea\xbbYs\x07Hn\x0bD\xee\x85\x10\xdc\x18c\xe9E?j[\x00mi|\xb2\xf7~f\x99\xd0$\xbf\x8d\x9f\x00Zk\xa7\xdc0\xa4\xf7\xb1\xa3\xf1%\xf1u\x9fwG\xc5C\x00\xf6\xab\x18\x87A\x18\x06\x22\xc4\x0bX\x92\x7f\xb0!\xe5\xdd\x90\xd7d\x809oh=\xb8J\xcd\xd9q+\xb5\x13C\xa4H8\xbe3\x89\xcf\xf6\xadE7\xc0\x0f\xe4\x9aZ*\xee\xa4-\xcdi\xa5\x82\x17\xe5P+\xd5\x17\x00r\x1eB\x80\x12\x8d\x00\xb4\xc2mF \x9d#\xf6\xfc]f3\xb2\x9b<\xcee\x14\x1ac\x17\x80\xb54\xf6\x16\xc0h\x19\xb0\xe3m\xdb\xde\x80j\xad*\xb0\xfb\x99J\xe3\x9c\xb3\xbb\x8a}\x94\x07|x]\xd7\xa1\x94\xa26\x01_\x03\xf0\xbf\xa6\xb1\xe98\x0e\xf7\xf3\xec\x02\xa0Z\x9bR\x1a\xf6}\xef\xd6a\x15\x00\x1d\x94\x8c\x97e\xa1\xb9\x0e\x16}\x044\xf6\xd2\xbf\xad\xb5\xb4\x9f\xe7y8\xcf\xd3\xb4U+\x1aIE\x8c\xd1\x95\xc9\x88\xd0shx\x8d\xb1fW\xe1\x91\x01t\xd9(\x82Ic\xa6e+\x02\xb0.\xfa\xf2\x8a(L\xc4\x90\xef@\xee[[\x92\xebnW\xc1#6\x12@+\x82v\x04\xffk\xdb\xf2\x10\xa0=+F\x95\x10\x06\xa2a\xb1\xb4\x13\x1b[\x1b\x8f\xa0\x9d\x95\x97\x16k\x11\xb1\xb6\xf5\x0c\x9e\xe0\xf3\x02\x81!?\x93\x19#\xbb\x7f?\x18\x90uw%\x997cf\xde\xbc\xbc}\x81\x7f_\xf3\x1f\x00\x0f\x80/\x1f\x99\xe6!\x9a\x9a8F\xe9\xa7+Iu\x88\x09\x05\xbe\xb2\x98\x0c\xc0\x19\x0e\x9e]\x96\xe5G=\x8b^\xd5\xad\x1f\x03\x22F\x00\xc6\xa3av\x15\x88\xf3\xb2\x86\xe6\xc5x\x8e\xef}8\x0cs\x9c\xe7\x99\x1e\x01\x8e\xb6\xc7\x00\xc4h\xa4D\xfb9\xcd\xea\xd6\x1e\x90\x8c\x97\x98{\x8c\x94P5\xdd\xdd\xd3\xe75\xbc\xfa\x95j|(:8D\xc0\x19\x00\x15\xe0\xe8'\xfe\x03\xa1\x09%\x00\xae\xcd\xbc\x0d@\xfb\xda\xe0Z\x96\xc5\x8c\xe3h\x8e\xe3\xf8%\xe8\xe1;\x8c\xdf\xf7]m\xf0\xed\x08hz6\x7f\x81\xb6m\xadX>\xcf\xb3\xb8\x87\xaez\xfbc\x85\xac\xeb:S\x14\x85\xed\xb0h\xf6\xfa\x9aJ,ub\xf8\x0d\xda0\x80L\xd3d%r\x8e\x83Ks\xdd\x02\x10\xaa\x8e\x5c\xee\x0eu(\xa8!\xc30X\x00\xeb\xba\xb2]b\x0c\x90\x04\xea\xa5\xf14G\x01bF8\x108R\xee\xfb\x1e\xa7\xbbf\xdb6\x16\xb0\x06PR\x1d\xa0\x93\xfa\xb9\xda\xcf\xe3u]\xdb3\xb3\x90\x01M\xd3\x98\xaa\xaal&\x92\xa2y%\x02\x99\xe6\x15\xd2t\xc9\x18\x00\xe0D\x86PF\xc9\xf3\xdc^W\x00\xf8\x87\x03Il\x94N\xac\x01\xa2\xdd\x90\xd2f\xd6\xcc\x91\xa5f -\x90+ S\xe6\x13\x9bzJ\xa95B\xa4\xa6\x18i\x0cw\x1a\x8a\xd4\x13<\xaa\xc4_\x8f\x1f\x06\xc6\x18\xae\x04\x8f\xa7\xff\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x08\xbe\x00\x00\x01\x00\x01\x00 \x00\x00\x01\x00\x08\x00\xa8\x08\x00\x00\x16\x00\x00\x00(\x00\x00\x00 \x00\x00\x00@\x00\x00\x00\x01\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x0f\x0f\x00\x13\x13\x13\x00\x14\x14\x14\x00\x15\x15\x15\x00\x16\x16\x16\x00\x17\x17\x17\x00\x19\x19\x19\x00\x1b\x1b\x1b\x00\x1c\x1c\x1c\x00\x1e\x1e\x1e\x00\x1f\x1f\x1f\x00 \x00!!!\x00\x22\x22\x22\x00#\x22#\x00$$$\x00%%%\x00'''\x00)))\x00+++\x00,,,\x00---\x00...\x00///\x00000\x00111\x00333\x00555\x00666\x00888\x00999\x00:::\x00;;;\x00<<<\x00>>>\x00???\x00AAA\x00BBB\x00CCC\x00DDD\x00FFF\x00GGG\x00HHH\x00III\x00JJJ\x00KKK\x00LLL\x00MMM\x00NNN\x00PPP\x00QQQ\x00RRR\x00SSS\x00TTT\x00VVV\x00WWW\x00YYY\x00ZZZ\x00[[[\x00\x5c\x5c\x5c\x00]]]\x00^^^\x00___\x00```\x00aaa\x00bbb\x00ccc\x00ddd\x00eee\x00fff\x00hhh\x00iii\x00jjj\x00kkk\x00lll\x00nnn\x00ooo\x00ppp\x00qqq\x00rrr\x00sss\x00uuu\x00vvv\x00www\x00xxx\x00yyy\x00yzy\x00zzz\x00{{{\x00|||\x00}}}\x00\x7f\x7f\x7f\x00\x81\x81\x81\x00\x82\x82\x82\x00\x85\x85\x85\x00\x86\x86\x86\x00\x87\x87\x87\x00\x88\x88\x88\x00\x89\x89\x89\x00\x8a\x8a\x8a\x00\xaeh\xf1\x00\x8c\x8c\x8c\x00\x8d\x8d\x8d\x00\x8e\x8e\x8e\x00\x8f\x8f\x8f\x00\x90\x90\x90\x00\x92\x92\x92\x00\x93\x93\x93\x00\x94\x94\x94\x00\x95\x95\x95\x00\xb2{\xe6\x00\x96\x96\x96\x00\x97\x97\x97\x00\x98\x98\x98\x00\x99\x99\x99\x00\x9a\x9a\x9a\x00\x9b\x9b\x9b\x00\xba~\xf3\x00\xbd|\xfa\x00\x9c\x9c\x9c\x00\x9d\x9d\x9d\x00\x9e\x9e\x9e\x00\x9f\x9f\x9f\x00\xa0\xa0\xa0\x00\xa1\xa1\xa1\x00\xa2\xa2\xa2\x00\xa3\xa3\xa3\x00\xa4\xa4\xa4\x00\xa5\xa5\xa5\x00\xa6\xa6\xa6\x00\xa7\xa7\xa7\x00\xa8\xa8\xa8\x00\xa9\xa9\xa9\x00\xab\xab\xab\x00\xac\xac\xac\x00\xad\xad\xad\x00\xae\xae\xae\x00\xaf\xaf\xaf\x00\xaf\xb0\xaf\x00\xb0\xb0\xb0\x00\xb1\xb1\xb1\x00\xb2\xb2\xb2\x00\xb3\xb3\xb3\x00\xb4\xb4\xb4\x00\xb5\xb5\xb5\x00\xcf\x9d\xfe\x00\xb6\xb6\xb6\x00\xb7\xb7\xb7\x00\xb8\xb8\xb8\x00\xb9\xb9\xb9\x00\xba\xba\xba\x00\xbb\xbb\xbb\x00\xca\xad\xe7\x00\xbc\xbc\xbc\x00\xbc\xbc\xbd\x00\xd5\xa6\xff\x00\xbd\xbd\xbd\x00\xbe\xbe\xbe\x00\xbe\xbf\xbd\x00\xbf\xbf\xbf\x00\xc8\xb8\xd7\x00\xc0\xc0\xc0\x00\xd2\xb1\xf1\x00\xc1\xc1\xc1\x00\xc2\xc2\xc2\x00\xc1\xc4\xbe\x00\xc1\xc4\xbf\x00\xc3\xc3\xc3\x00\xc2\xc5\xbe\x00\xc4\xc4\xc4\x00\xc5\xc5\xc5\x00\xc6\xc6\xc6\x00\xc7\xc7\xc7\x00\xc8\xc8\xc8\x00\xc9\xc9\xc9\x00\xd8\xbc\xf2\x00\xca\xca\xca\x00\xcb\xcb\xcb\x00\xcc\xcc\xcc\x00\xcd\xcd\xcd\x00\xce\xce\xce\x00\xdb\xc3\xf1\x00\xcf\xcf\xcf\x00\xd0\xd0\xd0\x00\xd1\xd1\xd1\x00\xd2\xd2\xd2\x00\xd3\xd3\xd3\x00\xd2\xd4\xd0\x00\xd4\xd4\xd4\x00\xe1\xc8\xf8\x00\xd5\xd5\xd5\x00\xd6\xd6\xd6\x00\xd7\xd7\xd7\x00\xd8\xd8\xd8\x00\xd9\xd9\xd9\x00\xda\xda\xda\x00\xdb\xdb\xdb\x00\xe5\xd3\xf5\x00\xdc\xdc\xdc\x00\xdd\xdd\xdd\x00\xde\xde\xde\x00\xe9\xd4\xfd\x00\xdf\xdf\xdf\x00\xdf\xdf\xe0\x00\xe0\xe0\xe0\x00\xe1\xe1\xe1\x00\xe2\xe2\xe2\x00\xe3\xe3\xe3\x00\xe4\xe4\xe4\x00\xe5\xe5\xe5\x00\xe6\xe6\xe6\x00\xe7\xe7\xe7\x00\xec\xe4\xf3\x00\xe8\xe8\xe8\x00\xe9\xe9\xe9\x00\xea\xea\xea\x00\xeb\xeb\xeb\x00\xec\xec\xec\x00\xee\xee\xee\x00\xef\xef\xef\x00\xf0\xef\xf1\x00\xf0\xf0\xf0\x00\xf1\xf1\xf1\x00\xf2\xf2\xf2\x00\xf3\xf3\xf3\x00\xf4\xf4\xf4\x00\xf5\xf5\xf5\x00\xf9\xf2\xff\x00\xf6\xf5\xf7\x00\xf5\xf6\xf4\x00\xf6\xf6\xf6\x00\xf7\xf7\xf7\x00\xf6\xf8\xf4\x00\xf8\xf8\xf8\x00\xf9\xf9\xf9\x00\xfa\xfa\xfa\x00\xfb\xfb\xfb\x00\xfb\xfb\xfc\x00\xfc\xfc\xfc\x00\xfe\xfc\xff\x00\xfd\xfd\xfd\x00\xfe\xfe\xfe\x00\xfe\xfe\xff\x00\xff\xfe\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xe7\xc1\xcf\xc3\xf4\xf4\xc2\xb6\xeb\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xe7\xc0\xd0\xc3\xf4\xd1\x97\xbe\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xa7m\xdf\xf4\xf4\xf4\xf4\xe6\xbf\xd2\xc3\xf4\x8f\x1a1\xc8\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xebW\x15p\xe7\xf4\xf4\xf4\xf4\xe6\xbc\xd6\xc4\xf4\xe6\x8b;\x09k\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xd53+\xb4\xea\xf4\xf4\xf4\xf4\xf4\xf4\xc3\xe0\xcc\xf4\xf4\xbe\xa9\xa9\x17;\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xd22=\xd8\xf4\xf4\xf4\xf4\xf4\xecI(!6\x5c\xc8\xf4\xbe\xad\xea\xdb\x1a)\xe6\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xddJ4\xe1\xf4\xf4\xf4\xf4\xf4\xf4\xeelt`^&\x01(y\xa7\xeb\xea\xa3%<\xf4\xf4\xf4\xf4\xf4\xf4\xee{\x1b\xd2\xf4\xf4\xe7f\xd6\xf4\xf4\xf4\xf4\xe9\xba\xdf\xce\xe1h\x05?\xd9\xe6\xa7\xb4\x0aa\xe6\xf4\xf4\xf4\xf4\xc4\x1d\x8b\xf4\xf4\xde8\x22\xc6\xf4\xf4\xec\xdd\xce\xb1\xde\xae\xc6\xf4\xa9\x128\xca\x9f\xc1w\x08\xb9\xf4\xf4\xf4\xe9_/\xf4\xf4\xeaE3\xda\xf4\xf1\xc2xH9\x80\xe6\xa7\xb3\xe7\xbf\x93%6\x92\xbf\xb2>K\xf4\xf4\xf4\xdd(\xa9\xf4\xf4o+\xc6\xf4\xeb|$-p\x93\xa3\xe0\xa9\x9f\xcd\xb6\x93\xdb\x12N\xc2\xb8q\x17\xd7\xf4\xf4\xe7\x85\xf4\xf4\xc71}\xf4\xf4b\x1d\x8f\xf4\xf4\xec\xb9\xde\xa4\x92sm\x89\xddz\x03\xa1\xb8\x87(y\xf4\xf4\xf4\xf4\xf4\xf4r9\xd7\xf4\x87\x19\xb9\xf4\xf4\xf4\xe1\xb7\xe0\x9d\x90]\x16j\xc8\x95\x1aE\xba\x93@>\xe0\xf4\xf4\xf4\xf4\xe1M]\xf4\xf4\x84\x95\xf4\xf4\xf4\xf4\xe8\xbb\xe5\x9a\x88\x94(;\xbc\x90[\x02\xba\x97S\x1f\xa3\xf4\xf4\xf4\xf4\xc4C\x87\xf4\xf4\xf0\xf4\xf4\xf4\xf4\xe3\xc5\xa0\xd4\x9e\x88\x93a\x17\xaa\x8e\x85V\xb0\x9de\x1c}\xe6\xf1\xe7\xeb\xa9:\x9c\xee\xe0\xe0\xe6\xe6\xe7\xe4\xbdun\xaf\xa5\x88\x8c\x88\x06~\x8c\x8d\x97\xa9\xac}\x1eo\xcac\xbf\xd3\x8b3\x8f\xdaiU\xbe\xcd\xcd\xcb\x98dv\xa2\xa8\x88\x8b\x95\x04r\x8c\x8b\x8d\x9d\xad\x82\x1bo\xe9\x15\xb9\xf4\xb1>\xa1\xf4\x80>\xd5\xf0\xee\xed\xc9\x91\x9b\xb5\xa6\x88\x8d\x81\x0bw\x8c\x8b\x8f\x8d\xaa\x80\x19\x88\xec*\x86\xf4\xc7D\x83\xf4\xcd\x18\xca\xf4\xf4\xf4\xf2\xef\xf3\xdc\x9f\x88\x92R }\x8c\x83J\x90\xaas\x16\xb0\xeeXL\xf4\xe7NY\xf4\xf45^\xea\xf4\xf4\xf4\xf4\xf4\xde\xa1\x89\x8e\x17T\x89\x8dR\x0f\x95\x9f]*\xc4\xf1\x9c\x11\xf4\xf4}6\xce\xf4\xcc\x0c\x96\xec\xf4\xf4\xf4\xf4\xde\xa1\x90? \xcf\xbc\x8f\x17O\x99\x8d7Q\xca\xf4\xde\x1a\x95\xf4\xcd5s\xf4\xf4\x97\x0dx\xd5\xe2\xee\xf4\xde\xa3\x8bPx\xe6\xf4b\x00\xc4\x9fk\x10\x85\xce\xf4\xeai-\xf4\xf4|+\xb2\xf4\xf4\xb7\x1d.b\x93\xf4\xdf\xa3\x87\x9d\x9f\xea\xd5\x0eN\xcf\x9d'8\xb0\xdb\xf4\xf4\xcf!\x7f\xf4\xf1M7\xbe\xf1\xf4\xf4\x8eMT\xf4\xdf\xa3\x86\xa1\xab\xc10)\x8e\xd1\x80\x13\xb6\xc1\xf4\xf4\xf4\xf0\x82\x1f\xbc\xf4\xdfF1\xd1\xf4\xf4\xf4\xf4\xf4\xf4\xdf\xa4\xa3\xbal&@\x8a\x88\x93\x1a\x82\xf4\xd7\xf4\xf4\xf4\xf4\xe2U-\xc7\xf4\xe2g\xd1\xf4\xf4\xf4\xf4\xf4\xf4\xde\x8dlG\x1bZ\xe7\x96\xaa\x89T\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xd9I,\xb7\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xcaB)Hj\xe6\xf4\x95\xd9\xec\xe7\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xd9Z\x1dr\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xd6q\x8c\xd1\xb3\xe2\xf4\x96\xe6\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xe6\x87&$\x83\xf4\xf4\xf4\xf4\xf4\xf4\xe0\xa7\xc7\xdf\xb1\xe1\xf4\xc7\xee\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf0\xd3w%\x079k\x94\xae\xc3\xe7\xa1\xc2\xdf\xb3\xe1\xf4\xee\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xec\xdf\xb7h>#\x14A\xf4\xa3\xc1\xf0\xd8\xec\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf0\xee\xec\xeb\xf0\xe1\xa7\xc3\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x91\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x05FIDATx\xdatL\xc9\x0d\xc00\x083\x84o\x06\x88\xc4\xfe\xabd\x13\xc4\x83\x15\x08\x15\x8a\xfak\xfd\xb0,_TU\xf8\x02\xe3\x07\xd2\x14\x115\xe7\x04\x11\xa1\x1f\xcc\xec.\x98\x19\xee\x8e\xbd7\xc6\x18\x10\x91\xbb\xe8\xd6Z\x0b\xaa\x8a\xcc\xc49\xe7\x06-^\xa3K\xad\x1f\x01\xc4H\x9e\xab\xbe\x7f\xff\xfe\x1fd)\x08\x80\x8c\xe1\xe4\xe4dd\x81\xb9\x0a\xe4\x12\x98C\xe0F\x818\x17.\x5c`\xd8\xbau+\x5c\x02l\xf9\xd7\xaf_\xff\x83t\xc1\x5c\xc8\xcd\xcd\xcd\x08\x10\x80\x0f2V\x01\x18\x06\x81h\x02\x9d\x9d\xdc\x05\xff\xff\x9b\xb2;\xf8\x07BZ\x03'i\xa0=\x10\x02\xe6|\xa7\x9f\xa9~C\xa5\xc6\x18\x93\x88V\x0eL\xc5\xe4,f\xee\x85N\xb9\xfb|\x90e\xc0\x9da0\xb3&\x22\xbd\x08\xf9!\xeb4`IP\xaf\x13\x8d7\x04JD\xb4W$\xec\xb1\xd3\xb2\x87\xbe\xaa\xae\x1dn\x018%c\x14\x80A\x18\x8aJ'7O\xe1\xfd\x8f\xe0\xa27q\xd2\xc9E\x04\x87\xd4\x84F\xd2\xeab\x03A\x90\xff1/\xfe\xe3-]\xea\xb0\x8e\x0d\x13:\xe7\x0crC\x12\xb8\x94\xb22\xd4Z\x81\xc5\xd2\x80\xad\xb5&\xed\xeb\x05\x16b\x0c\xa5\x01\xe3\xb8e`\x03vJ\x89F\x0b!L\xf3\xc2\xc0\xe5\x9cS\xc6\x18\x0c\xbd\xb2\xd6\xae\xd4<'2\xb4\xd6 \xc6\x08\xe3W\xc1{\x0f\xbdw\xba{\x8at\xff\xa1y\x85;\xc3\x96\x01\xe3+\xe1\xbf\x86\xf1\x0ft\xde\x020W\xc5(\x10\x83@p\xab\x04\xf2\x81\xb4>\xc0>`\x11\x82\xe0\x8fm,\x04\xdf\xe0\x07\xac\xd2\x07\xf2\x81\xcb\x0a+{j\xb8\xbb\x22p\xc5\xc2\xc6D';;;\xfe\xac\xa5\xc7\xa5\xf4w\x00\x8d.P\xe3\xd34\xbd\x09\x8b\xb8\xeeI\x84s\x8f\xfb\xae\x19/s\xd0\x05 \x9b\xe9\x01p\x10\xdeP\x9es\x03\xecRD\x1f\xd0\x81\xde\xfb\xe2k\x18!\x84\x92\x1f\xc7\x91\xad\x8a\x83\x7f\x04\xa8\xe90\xc6\xc0\xbe\xefy\xcd9\x07R\xcar`\x8c\x11\x86a\xb8\xdd\xdb\xa5\x88J\xe6\xdaN)\xc1<\xcf\xb0\xae+^L`\xad\x05!\x04l\xdb\xd6\x0cJ\xfd\xdcT@\x8eM\xbcb(\xa5\xb2)\x8c\xe3\x98\xdf/\xcb\x92\xc7\x95.V\xde\x03\xee\xf8\xdd\x0a\xea\x91\xa6J\xb4\xd6\xc5\xcb\xd0}0\xea\x9f\xf9\x0a\xe0<\xcf,\xb7\xe2>\x8c\xd3ZEu~I\xbc\xed\xe7\xd3V\xf1\x12\x80\x1d+\xc6a\x10\x86\x81\x08\xf5\x11\x9121\x91\x0c\xf9\x7f\xbe\x90\x91\x85\x01>S.\x92\xa34\xd8&\xa8j'*E\x22\xc5\xf8\xec\x06\xdf\xd9}\xb8\xe8\x01\xf8\x01]\xa3\xa5\xa2N\xba]\x9c\x9a\xd7\x0b5TS\xf5\x09\x00\xce\xc19\x1cEs\x00\x92p\xab\x19\xb4\xce\xb9\xe8\xe9~[\xcd\x9c\xdd\xab\xc7y\x9b\x85\x14q\x17\x80\xb6\xa4\xe85\x80Q3\xc0\x1e\xe3\x12\x01\xa4\x94>\x00c\x8c\x22\x19^\xbe\xa6d\xbc\xef{\xd9\x1f\xbd\xdep\xf4~\xe5\x9e\xf7^t|\xab\x0e\xc8\xc1<\xcf\xc3\xba\xae\xf9\x1a2:M\xd3\xf7\x85\x86\x96\x16\xbf\xf7\xb6mY6!\x99\xf8,\xcb2\xf40\xb1\x08@\x0f[k\xb3&\x03\x00\xdf\x11 \xc0$\x1d\x16\xdf\x22I\xc0q\x0e\x10yr\x8c\xe8C\x08\xa7\x22\xe3\x80\xc6\xab\xf2\xc7\xc2T\xe9\x9c+{\x1c4&]\xceVU4P\x851\xa6\xab\x92\xb9\x80\x8e\xa1\xa1\x8c\xb1jW\xd1C\x03\x1c\x17q\x19\xa8\x8d\x97vx\xad\xd3\xee\xff\x1b\x90&\x17a\xddd\xd5\xd7\xb5-\xe8\xfa\xb2\xab\xa0\x11\x9b#@-\x83z\x04\xffk\xdb\xf2\x16\xa0=\xabgq\x10\x08\xa2r\xe8\x0f\xb0\xb0\xb1\x8dX(\xd8\xf9\xf7m\xacS\x8a\x96B\xdaD\xc1J\xbb\xe3\x0dL\xd8\xdb\xdb\x8f\xd1\x5c\xeer\x90\x85%\x82f}og\x9c\x997\xfb\xf4\x17\xfc\xfb\x9c\xff&\xf0&\xf0\xe2#\x94<\xa4\x86&[E\xa9\x87+_\xd7\xc1\xd5(\xd0;\x8b\x87\xa3\x10\x03G\x9d\x9d$\xc9\xaf\xee,\xb4*\x07~\x17\x11\xaf\x05\x00\x1e\x82\x993\x90m\x97M\xf5\xa6\xcd\x02.\x91\xcd\xbf\xd80\xac\xb1,\xcbq\x17\xb2\x95\xed.\x02\xae\xfa\xd4W\xf6\xdbzV\x0f}\x03>\xf0&\xc9\xe0\x12@\xa6{j\xf1\xa2>\xef*\xc5DQ\xc8\x07\x1as\x9egR\x0b&\x1d\xb4m\x1b\xf9\xb1\xde\xf9\xe39M\xd3\x97F\x9c\xc45w\x13\xf0\xb9\xcd8\x8e\xc10\x0c\xdf\xc0\xa1c\xd54M\xd0\xb6-\xf5\xcbu\x12}\xdf\xd3\x8c\xa2\xc8\x0a\xf8a\x0b\xf8,\x83Q\x14\x05I(\x80TIv]GMqL\x96Z|\x7f]W\x22^U\xd5\xae\xdd~J\x22\x83L\x83\xb2b\x01\x8a\x81\xc8\x813\xc1\xb2,\x83<\xcf\xe9Z\x8d& \x07\x91\x04-\xf3'\x99X\x0f\x7fP\xc8\x10R\x1cjqB\x08\xf0\xb8\x07q\x05\x81\xcb\xb2\x11dn\xb7\x1bY\xce\x16J\x7f\x8c\x80);\x9a^\x08\x90\xd8Q\xb8\xca\xe5r\xb9\xeb]\xfe?\xb4.\x80_\xafWz\x06\xe0mZK\xbf\xf6\x91\xfa\x90\xec\xb4\xad\x04P_p:\x9d\xe8\xa0\x08\xae\xc4\x9a\x99\xa5\x15\x8e\xd7\xb2,\x0b\xce\xe73\x91M\xd3\xd4\xb9\xd6\x1e\xab\x84Rw1\xc5j5\x8e\x03X]\xd7\xf4\x81\x22{\xeb`@@\xb5\x8c/\x13K-\x10J\x5cH\xa2\x921\xe28\xbe7\xf4M\x11\x85\x8f\xea\xf6\x10\xd0\x0f\x07\x0eU\xa3\xea\xc2\x12\x22\xd2\x0f\xd2\xe5\xfb\xd25\xc2\xa3\x11HJd\x0f\xc9#\xebyE\xbdZRK\xfa\xae\x92d$\x01.)\xa5\xdf]\x89W\x18\x9f\xa4\x95d\xdf\xae\xac\xa9\xa2\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x0f\xde\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x04\x93IDATx\xdatL1\x0e\x001\x08B\xeb\xea\xff\x7f\xe8\xe4\x17\xd4\x0bv\xed\x91 $\x8023xA\xf1\x03\xe3\xc9\xccqw\x88\x08\xf8!\x22n\xa0\xaa8\xe7,\xab\x0afv\x03\xb6\xba{\x17Tr\x03\x1a6\xa9,\xd1\x7f\x02p:\x06'\x00\xc0 \x10\xab\xa5\x0f\xdd\x7f?\xd7\xd0\x87`I\xc1\x05\x9a\xd7q\xa0\x17\xf9\xb3\x8a\x88f\x18xcf\xb2\xc7\xca\xdd\x9f\x0d\x19\xdeFf6%T\xd5RU9s>\x12\x98\xc1\x15@8]\x85\xd7Q \x00\xb4\xfb?\x1f\x1f\x1f\xd8n\x98\xfd0\x93AXDD\x84\x11\xc5\x86\xb7o\xdf\xfe\xe7\xe6\xe6\x86k\x80\x853L\xc3\xcb\x97/\x19\xe4\xe5\xe5\x19\xe1F\x81\x14\x800L\xc3\xb6m\xdbP4\xc3leA\xb7\x1a\xc6vww\x07\x87\x00\xcc\x16\x10\x1b\xc3\xd3 \x7f \xdb\x06\x92\x83\xc9+))\x81\xfd\x00\x10\x80s*H\x01\x18\x06ae?\xf0\xff\xcf\xf3\xe8\xc9\x8b\xd7N\x0b\x96\xb4+\x1b\xae \xbd\x18Lb,\xbbt\xb5\xe2+\x03\xa6h\x11\xe9\xe8\x0a\x06SU\x9f\x1a\xcc\xacg3\x02\xa2<+\xa3w\x99\x90\x8d\x11C\x04D\x9e\x8e\x94\x12\x80K\xca;\xf9\x14\xed1\x09\x8a\xef.%\xdf(\x22j\xcc\xbc,n2\xf9-\x1ao{\x07\x1cEG|Q\xfc\x0e\xf0=\x8c\xff\x16\x80\x99*F\x81\x18\x04\x82\x16\xa9,m}\x80o\xb0\xf0\xdd\xf66~\xc27\xd8\xf8\x81c%\x13\xd6\xcd\x0a\xb9\x83\xc0\x05$\x22\x0c\xa3\xb33\xf3\xb5\x97^\xb7\xd2\xdf\x11\x1c\xf2\x80\xcfs\xd9\x1f\xc7\xe1\xa6iR\xfb\xb0\x0a\xd0j\xe0\xfcP\xb0Q\xd3G\xe7\x07T\xfe\xe8\xd9\xb6\xad4\xfd\x94\x92[\x96\x05R\x05L]9\xday\xef\xbb*\x19\xf1\x11y\xc6c\xac\xa9*zh\x00qQ\xf3\x17Y \x96\xf0\xb5\xe2\xf5B\x01D\x16\xb2\x0e\x92\xfb\xfa,\xd1uSU\xf0\x88\x8d\x08\xd0\xf2\xa0\x1e\xc1\xff*[\xde\x02\xb4g-)\x0c\xc2PPJ7\x1e\xc0\xad\xe0\x09\xf4\x04zi\x0f\xe1-\xdcx\x02w\xe2\xa6L\xe0\x95\x10\xde\xcf\xd8/\x18(M\xdbh3\xcf\xe4\xbd\x99\xc9\xdb\xff\xe0\xefk\xfe\x05\xe0\x02\xf0\xe3\xed\xee\x19\x14\xa7&\x89Q\xa6\xe9\xcar\x1d4\xa3 u\x16\xb3\xb3\x10M\x1c\xf4\xaa\xaa\xaa\x8fF\x16Z\x95\x12\xbf\x06\xc4|\x02\x98<\x04s\xec\xf7qQ\xe6\xf8\xa6\xe6MH\x22\x9b\xde\x110\xdcc]\xd7\xfc%$\xd1v\x0d\x80F#-\xda/yV\xa7\xf6\x805yN2h\x02\x88\xfb-&/\xf1x\x0f\xaf\xbe\xe7N\xde\x02\x06\x15\x01\xcbv\xdf\xf7\xf0\x19\x92\x12\xc7\x19\x1a\xad\xe6\xbe;\x0d\xc0\xbbl\xa8\x0fCo\x9a\xa6\xa2\xef\xfb\xa7\xa7L,N\xda\x17i\xd4\x8f<\x81\xac: E\x08}\x18\xfc`\xe5\x88v\xbc\x87\xc8\xe0\xb5\xae\xffz!\x83\xaa\xc5\x01\xdb8\x8eA]YY\xe4#\x85L\x93\x84\x5c\xbfm\xdb\xa2\xeb\xba\xe7\xd2\x81\x1e\xc5q\x11\x96UY\x96\xa2\x92\xcba\xc67k\xa2\x9atI\xabh\xfc\x22U\x82\x09C\xef6M\x13\x9e\x08\xa7\x125@\x16(\x13\x80F\x018*\x80H\xa7 \xb6m+\x96e\x09\x99H\xba\xce\x03\xe8\x945\xc1\xe5\xea4\x8f\xa3\xe1\xccl\x9e\xe7@\x03\x90JQ\xc9\xb1\xa1\x87aPU\xae\xf4\xdd)\x00qd,\x95L\x0d\x87\x99u]\x8b\x9e\xa4\x97JP?=\x1c\xc8\xde\xc4\x5c\x9e\xf6\x8c\xf7\x8e\xb1\xc0\xbc,\x0b\x1d\x05r\x04d\xce\xfdLQ\x1fSj\x8f\x11\xe9)F\x9e\x89{\xa8\xf4\xe5J\xfcB{\x00&\x9b+\xe1}\x9eoR\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x1b\xa9\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x10^IDATx\xdab\xfc\xff\xff?\x036\xc0\x04\x22\xd6\xaf_\xff\x7f\xf2\x8e\xdf\xc6\xe7.\xdd\x0c\x84\xcb\x80t\xec\xb9\xfc\xdf\x18H\x1b\x17,\xfao\xbc\xe4\xc8\x7f\xc6c\xcf\xb7\xfe\x07\xeb\xb8q\xe9\xb0\x5c\xdf6\x06\x86\xf5\x14\x83\x82\x8e\xcb\xae\xdb'W\xde\xde\xbau\xab\x15\x0b''\x97q\xed*\x06\xc6\xbcH3\x86wW\x97\x9e:\xf8\x7f\x05\x03\x83\x0c\x837\xb2\xa5L\xe6\xe6\xe6\xb1\xc6\xc6\xc6\xb7@|\x0c\xcbMLL\xb2\x81T\x0d@\x00\xa1H\xcc\x9b7\xef?+++\xc3\x97/_\x18~\xfc\xfa\xafY\x98\x9fu\x03\xdd\xb5`\x0dS\xa6L\xf9\xf3D \xc1\x5c_\x89\x87A_\x9e\x81AQ\x94\x81a\xfb\xf1\xc7\x0c\xff?^\x9f\x1c\xec\xe7f\x85\xe1=NNN\xe6\x9a\x10\x1e\x06C\xe9\x8f\x0ci\x13\x1f0\xf4ne`\xf8\xc5*\xcb\xc0\xcb'\xb8\x88\x91\x91Qy\xc7\xfd\xa5\xffMm\x8c\xfe\x83h\xb8\x93f\xcc\x9c}\x8d\x9b\x87\xff\x83\x8aIP>\xeb\xd7k\xc6\x0f\xef\xdf\x9e~\xe2\xdc\x05\x86KgN\xfd}\xf9\xf2e\x8c\xbe\xbe~\xfe\xa2E\x8b\x82\xb0\x0692\x06\x86\x82+(\x0a\x80\xf4e\x948\xc2\x11\x1bB@\xac\x02\xc4\xb6\x0a\x0a\x0a=@Z\x0c \x80p\xc6\x1d.\xc0\x02c\xf4O\x99\xa3.\xcc\xc7v\xe3\xcf\x9f?\x0c\x9f>}b(((`\xc4\xa6\x01l\xc3\x8c\x99\xb3\x0e\xbd\xe3\xb4\xea\xff\xc6\xaa\xf0HO\x91\x87AO\x8e\x81\xe1\xca\xb15gBBB\x18\xb1\xda\xc0/(\xce\xf2\x9d[\xfd\x91\xad2+\x83\x9a$\x03\x83\x00\x17\x03C\xc2>I\x93/_\x16\xfcIHH`\xc1\x88\x87\xdf\x12^\xf9\xaez\xac\x0c\xa6J\x0c\x0c'\xae\xbea\xf0\xeb\xfd\xcf\xe0\xe7n\xcd\xf0\xf7\xef_f`<\xc8\x1f~\xb0\xed\xff\xbeK[\xff\x03\xd9,`\xdd\xf2\xcc\x17\x8d\xb5eLN\xbb\x96\x9c`\xd05\xb0`\xf01d`P\x93``\xb8\xff\xfc7\xc3\xf6++\x1f\x14\xc5\xd4\xc0\x92\xceK\xb0\x1f\xb6o\xdf\xfe\xbf\xeb\x98\x91\x89\xbd\x818\x83\x020YhJ10p\xff\xb9'\xb7{\xfb\xc6\x07\xd7\x7f\x9e\xd8\xaak\xad*\x09\xd2\xb0\xa9\xe9\xe4T\xb0\x0d\x9e\x9e\x9e\x8cO\x9f\xce\xf9/\xc4\xa5\x97\xa9\xaddt\xf6\xf1\x8d\x03\x13\x9f~\xfad\xf9\xe2\xed{\x86\xd9\xad\xabX\x83\x83\x837\x7f\xfc\xf8\xf1\xee\xee\xdd\xbbsP\xe2\xe1\xc2C\x06Vmi\x06vV\x16\x86/ \xbe\xbf\x9f\x1f0\xa132<}\xfa\x94\xff\xcc\x993\x9f\xe0\x9e\x86\x01\x03y\x86\xdf0\xc5 \xb0q\xd3&F`2`\x90\x96\x02\xa6J\xe4P\xc2\x07@\x9a\x80I\x94\xc1\xcf\xdf\xef?JL\x03\x83\x0c]-'4-\xf1\x83\x5c\x07\xc4\xaf@\x82\x00\x01\x843-\xd9U>fdgad\xe0\xe1ba\x13\x13`\xff+/\xc1\xf9\xaf*\x90\xe3\x1f\x03\x89\x00\xc3\x829s\xe7]\x14\x14\xe0\xd7cf\xe3=\xfe\xe2\xbb\xe0\xa2\xfb\xdfU\xce\x8a\x09\xb22H3]3\xfe\xf7\xf9A\xdc\xb7\xaf\x9f,\xff\xfd\xfbw)--M\x9f$\x0b\xbaz'\x18IK\x88\x9e}\xf2O?\xe8\xe5_\xf9G\xc2|\xac\x0c\x92B\xec\x0c\x12\x02\x8c@\xcc\xc0 \xc2\x03\xf4?\x10\xf3p00\xec\x06\x06q`h\xf2\x01\xd3\xb9\x107\x03\xc3\xc7\xaf\xbf\x19\xb2\xfb.2<\xf8(\xc4\xa0\xa6\xaa\xf5\xc8\x5c\xf0[&\xe3\x9fg\xe7A\x8e\x84\xc6\x09P\x15\x030\xbd2\xb0\x03\xf1[ ~\x0dt\xfc?\xb8\x05,\xc0T\xae\xa6mrVL\x80\x19\xecZP\x06\xfa\xf2\xe3\x0f\xc3\xe2\xadW\x19\x16\x1e\xf8\xc1\xa0\xaci\xce\xa0\xa2b\xc2\xa0\x0b\x14\xe7\x07\x1a%\xca\xa3\x7f\xf6\xf5\xfb\x0b`\x83\x81E\xf5\xab\x03O\xd7q!\xbb\xfc\xdf\x0fF\x86\xe2\xe2\xe2jd\x0b\xfe~\x7f\xbc\x7f\xa2\xb2\x9akn\xcf\xaa\xfb\x0c;N\xbeb\x90T\xb1\x00\x96\x98\xfa\x0cA\xc0\xfaD\x04\xe8#an\x88\xaf@Au\xf3\xcc\xf6\x89?~\xfc\xf8\x0b\xcaz\xe79\xb7\xb1]8\xcb\xc0\xb0\xa2z\x0f\xdc\x82\x88V\x17\x867o\x98}Q\x22y\xd1\xe2\xa5\xc7\xb98\xd9-\x1e\xff\xd5\x0d\xfa\xc1\xa1\xfeH\x00h\xa0\x08/\xd0`\xa0\x81\xa0x\x10\x04\xba\xf1\xef\xf7\x17\x0e\xa7O\x1e\xefy\xf4\xe4\x09\x83\x99\x89\xc9O+++.{{\xfb\x06\x19\x7f\xb6\x92\x7f\x1c\xbf8af\xb1\x9dT8\xb5p\xe1\xc2\x00\xac\xc9\xb4\x7f\xea\x22]\xc6\xdf\xef.\xb1\xb3\xb33\x800\x0b\xa4\x90`\x00\x95nLLL\x9a+W\xad\x0a\xda\xb8aCkNN.\xc3\x8b\x97/\x18\x9e=}\xfa\x13(\xcd\x0b\xcc\x8e\xbf\x09&SR\x00\xb0\xfa\xf2\x92\x96\x92\xda*\x22*\xca\xf0\xe6\xf5\x1b\x86\xa7\xcf\x9e\x9e\x03\x0a\x9b\x03-\xfaC\x15\x0b\x90,\x0a\x93\x96\x96Z!\x22\x22\xca\xf8\xe6\xf5k\xa0E\xcf^\x02-\x91\xa0\x9a\x05H\x16e\x01}4\x15\xe8\xa3\x97s\xe7\xce\xa5\xbe\x05\xd8\x00@\x00\xda\xab5\xa6\xad2\x0c\xbf\x9cCO\xdbs\xdaA{zg=]\xd0-Fp8\xe7\x80\xf0c\xa8\x01o\x08\xd1\xc4\xf1c1!\xb8\xc4\x82n\x89+\xc9\xe2t&\xc61\x89a\x8a\xcb\xf8\xb3\xb0\x18/Y2\xc4\xc4\xe1d:\xc7\x0c\xa0eNS\xb5:1A\xb2r\x0e\xc85PF/\xd0\xbb\xef\xd7\xd6\xe2\x22\xd9\xc5t_r\xd2\xe6$\xe7}\xde\xf7\xfd\xde\xefy\x9e\xef\x8e\x03\xdc\x90N\x1f\x7fcJ\xf6\x5c\xc7\xbc2\xab\x15\x10\x16\xdd\xbd\xe9|7\xabdv\x11JFe\x83H$\x02\xa1P\x08B\xe1HO\x8bc\x7f\xfd\xff\x06x\xf7x\x97\xc5\xc4s\x7f\xa9\xd5j\xf0\xc56t\x8e\xfb-}1\xb9qa#\xb7\xc8+W\xff\xa8Y]\x9e\xddK\xc0\xf0\x5c\x14466N\xdd\x16@GG\x07\xa3\xd7\xebCr\xb5\xe9L\xef\xd5\x92V\xa3F\x0e\x84QMZ\x05X4)F%\x14\xf1\xdb\xf7\xe7\x0e\xf9\xfd\xbe\xa7\xbd\xfe\x98\xa2i\xcf\xee\xd0-{\x114aA9\xab\xf9\xf1\xe2\xf4\x03\xad\x82\x81\x01\x13\xaf\x00}^.XQ\xa3\xf4\xc8C\x1a.\xc5E\xfa\xea'Z\x9d\xdf\x0e\x16P\xd4|\xe0\xdf\xdf\xdf\xb0\x82\xb6\xf6\xf7\xacf}\xbe\xf4s\xb4\xae\xd4\xa0e\xe3\xba<\x19Xy\x1a\xf4\xea\x14\xc9\x91\xe0\x84]\xbd\xfe(<\x7fd\x18|\xb9\x9b(\xfb\xd6\xcb?,x\x97uM/\xecY\xb8i\x05\x0c\x15?N\xf8\xc6\xca\xab\xe2F\xa2^\xd8\x12\x925\x9f\x16\x19\x85\x0c\xe0\xd4\xd7\x1ex\xff\xfc<\xd0\xba\x0a\xb8_\xc8\x8d\xc7b\xc3\x10\x0d\xaf\x9c\xc0\xcf\x9f\xc5a \xd3\x88\x96\x04x|H\xdbf0\xf1@\x06\x80\xa6\xa9\x87\x91\xc4\xe0n3\x93\xec5\xc9\x9c\x04\xce\xc3\x01\x9d\xbb\x16\x81\xa3\xdd\x1e\x18p/\xc2\xb6\xed\xe5`\xceO\xbd\x8f_\x8b\x03Jg5\xf1KmmmgJ\x9f\xbc\xafFi\xc8\x81\xe8*\xc0\xc4\xf0R\x10\xdf\xdf\x8b b\x12\x80\x04'K\xd0\xa1\xa5\xdc\x90\x0a\xa0\xc6gd\xdc\x07\x8ec\x97!\xaa\xad\x80\xf2\xb2-`B-\xc8cS\x14\x9e@g\x82\x00\x09\x87\xc3q\xa0\xac\xfe\x9e\x1aJ\x11\x860\xb1\x04\x0c@\xc1CJ\x16Eh\x04A\x0c\xe9\x0ar\x07\x10\xa4n\xa36%\x8f\xb94\xc0\xe1\x0f~\x87\xfe\x11\x06tB\x15\x14[q\x08\x18\xc8\xb4\x8d$\xe1\xf1$\xc8\x19\xb9P\x5c\x5c\xdc\x12\xca\x09\xc0\xeb\xf5\xefd\xfa~\xf8\xe3\x16\xd0\x96\xc8\x88\xc2\x99\x92\x00\x9cJ\xb5\x8f\xa6\xe9:-\x1b\xa3\xaeN\x87\xe3G\xbb\xc7`d\x86\x85\x92\xa2\xbb\x92\xedJN\x90*\x05@\x82\xe7+c\xd4(\x9e\x07\xd4\x07\xfb\xc0\xc0@\x9fQ\x17+\x9f]^;\x16\xdf\x5c\xf9\x12\x12Q \x94-\xcf\x9c\x83\x9e\x9e\x9e(\xaf3\xfct\xe0T\xa8\x99\xdf\xfc(l\xb5\xa6\x14\x8c\xa8\x9a!\x9d9\x99(\x02\xe8r\x9e;=99Qh\xb7\xdb\xe9\xea\xea\xea\xce\xc2\x06\xc5K\xc5\xb65\x17sEtCb\x92\x9d\xee:\xf8\xc9\xb6\xcc\x1c\xb3*\x8dz~n&\xb8\xff1\xf5\xa1?)hU+\xd6\x02f\xaa\xc0\xdf_]Cg\xbd\xdeE\xf3\x83;J\xa1\xb9\xb9\xb9\xa1\xbf\xbf\xffHCA\xc3\x8eKe\xdf\x95f\x08n\x95Y\x99\xfc\xcaw\x928\x8b\xeb\xa8\xa2\xbd\xf3#[\xbe<4N\xeeSr\xb5\xb1Sc.\xea3\x9a-\x0bLt\x8e_\x9c\xf5\xd4,y\x17\xf7\xfa|>(\xdc\xbc\x05\xde|\xebmx\xf9E;\xbcz\xf0\x95\x06\x97\xcbu\xa1\xaa\xaa\xea5\x8b\xc5\xb2=\x10\x08,\x8d\x8d\x8d}\xe8v\xbb?\xc5\xd8\xd1\xff\x90]\xfb\x17@S\xa3\xc7>\x933t-\xd1c\x02\x86\xfb\x93\xbc\xdb \x0f\xf5:\x9d\xce]\xb5\xb5u!-\xaf\xcd9\xd9\xd5\x05\xa2$B0\x10\xdc\x87\x0a\xd6y[zpi\x14(\xdc\xd4\x1c\xb4)JN\x01\x11*\x07\xae\xe3\x9d\xca\xca\x9daA\x10d\xc4wI\x92\x04\x98y\x13\x82\x9c\xc8\xaa\xe0T\xee\xac\x94\x04\x9b\xd5J@DQ\x8a\x07\x83\x81g\x10\xe4\xf3[\x16\x9c\x9b\xad\xc1\xa1AA\x12\xa5\xa47\xb5\xd9\x04\x8a\xe3\xd8^\xd4\xe5G\xb2\x06@V \x18\xac\x90D\xf1\x17r\xf3\x10\x04\x1bp\x1cw\x11A\x9e\xca\x1a\x00\xb6d\x05A*Eib\x924\x1b\xf7\x05X\x8e=\x9b5\x804\xc82\xf6_\x98\x10%R\x08\xd8\xac\xb6\xa2u7y\x9dk\xd1zK\x96\xbe.\xfd\xf3(\x08\xdb\xa4\xa9Ze\xb6\x98\xf9\xe9\xa9\xe9!\xfc?\x85\xb1}w\xdc\xb6\xfc-@wV\x1e\x1bE\x19\xc5\xdf\xce\xec\xce\xec\xce\xcc\xeel)\xa5\xdb\x16H\x01)\x14I\xb8\x84?\x00mbPK+\x876\xf5V\xee\xa8\x1c\xfdCb4\x9a@D4\x1c\x16\x94\x16B \x08\xe1P9S\x82\x5c!\x04J\xc2!\xc8e\xb1\xa5\x14\xcae\xd9\x1e\x94\xbd\xba;\xdbmw\xd7\xf7\xe6\xc0\x82\xd5\x80\x8a6\xee\xe6\xe5\x9b\xec\xec\xec\xfe~\xf3\xbd\xf7}\xbf\xdf\x9bG\xfe\x07\x8f\xfa\xf5\xd0\x04\xe6/Y\xdfE\xb2\x84sXS4\x8fa\x99n\xf1X4\x19\xe21W4\x16\xaf\xc5\xed\xa7\x1e\xf7\xa0\x1b\xd1\xb8i\xbb\xc9\x22\xedy\xbf`J\xfd\x7fN`n\xe1wR\x8a=\xb4\xd0\xc6\xb3\xd38\x8e\xb3\x904\xa00\xf2\x0d\x01\xab\xcb \xe9?\xb2'\xc6H\xf2\x8c\x02\xcf\xb7\xa0\x22Xew8?\x98\xf0\xd6\xeb\xc1\x7f\x8d@QQQ\x92M\x10O\x88\x82\xad'*2\xdc\xa5\x85S\x9efq\xd7\xe1\x9b\xbd\xf6q\x1c\x1f\x13x\x06l\x18\xa2\xd5\x0c\x92\x8d\xd5\xc2\xca\xaa&\x9a\x04\x14\xcfF\x99\xda\xaa\xd2l\xa5\xe9\xce\x98\xd6\x96\xe6\xa1D\x0c\xff\xab:\x16g\x86O\x9a\xf8f\xdd#%P\xbc|\xc52I\x14fI\x92\x041\xb3\x5cr\xa6\xa1g\xf1\xedH'/g!\xc0\x8c\x0e\xd8\x0cDB\x03nRe\x8e\x80\xf6[\xc4\x108M\xa8\xd11\x8d\x8d\x8d\xf5\xce\x8b\x15\x17f*\xa1\xd0x\x9a\x15%\x1c)~\xed\xd5\x97g=\x12\x02K\xbf\xfc\xaa\xc4a\x97\xc6\x11\xf8&\x93k\xce9\xff\xc0=\xda\xddf\x11 \x0b\x22\x02w\x08,\x90\x1e \x80\xb8\xd1\x81\xc4k\xed\x0b\x1b\x82\xe5,\x1ah\x0a\xca\xb2HK\x0c\xca\xaa}\xb0tk5<\xdbO\xc9q\x89M\xf3\xa8u\x8a\x9b_\xc9\x84\x09\x13^\xf8G\x09,^\x5c8C\x92\x84b\x02\x1f\xe7\x93\x8b+[\x87\xaf\xb3\x0b\x16\xb0\x0bf\x95\x00\x81&\xa0\x0e\x9b6\xf2fM\xebQ\x10\x19\x22@\xfa\x13\xcb\x02\x0e\x9e\xbd\x03\x17\xaa\xbdp\xbe\xea\x0e\x94]\x0f\x83\x9c\xd2\x1f\xba$:\xe1\xf9\xd4\xc3\x13\xb9h\xfd\xccP(\x04\xa1\x90\xf2\xc6\xf4\xe9\xefn\xfa;\x04\xeeq\x0ef33\x83\xb4\x09\xa9\xe0\x90\xad\xf7\xee\x1e\x92\x88\xea\xd7\xa4\x02'\x15,\xb6\x01K\xc1#`\x0b\xab\x8d\x01\x94\xd3?\xe1\x9d.\xbb\xdc\x08\x1b\xf7W\x83'\xe2\x00\xde\x91\x06\x9d\x93\x9e\x80'Gh3E\xb3\x12`z\xeevF\xdc3\xa9&b\xb1\xe8\xc7E\xfb\xe1\x9bY\xcf\x81\xda\xce$\xed\x8f*zRZZ\xda\xf8\x84\x84\x84^\xb2,;\x08\x97\xcf\xe7\xf3{<\x9e+555%K\x96,Y\xab\xf7\x88Z\x7fG\x00\xc1g\x12xZaRS\xbb6J\x82I\x05L\xf9\xcd\xebiA\xa3M\x1f\xe3\xfa*\xb4\xf0\xdb\xabp\xe0l\x00\xa2\xc0C\xabI\x80\x84\xae\xa3`\xb0S\xd3\xb3T\x0f4SV\x83\xbc%\xa51\x1e\xd4\x9a\xd3xm\xe6\xa6\x05\xf9LA\xf6\xb6.\xe8-V\xf7\xe9\xd3';q\xb0\x85e\xef3\xcc\xc9\x90D\x06\xaeST\xc9\x18:r\xe4\xc8y\x95\x95\x95\xfb\x10\xe34\xbbR~,\xa7\xce\xfd\xcb<2\x16\xb8\x0a\xed,((\x18O\x8fW\xf2\xf2\xf2\x8a]\xd9\xec\x08F\x8e\xda\x09\xc7\xfe\x05\xa7\xdb%\xf0\xdc\x87C\xb4\xcd\xd3\xc7\x06j\xf7E\x8fn\xdf\xbe}f\xbb\xfb\xc0\x96-[V\xa2\xd9x[}Z\x141\x97\xec\xab\xeaZ\xdc,\xf6\xf7\x92}r\xe8\xf5`\xac@\x94&m\x8b\xdb8o\x90\xa3\xcf=\x0d7R\xce\xfcxb\xa1\x99e\xfa\xd1\x12jbL\xd1\xe3\xc7\x8e\xcfG\x99\xf9\x05:\xa2\xf8\x80\x01\x03\xc6dff\xbec\x1d\xe1\x1f\x16\xb7\xb5\xfci\xcb\xc8\xa4X\x94\xf0Q\xc7\xc9\x8a\x8a\x8a\x95\xe8\x9cv\xfd\xe1N\xbcb\xf5F\x97\xcd\x12\xfd\x81eL\xddUo\xceXO\xb5\x9a;\xed\xf2\xdb\xb3p'fc\xc6\xda/\xe8\xe0\xd5ML'\xc1\xa9\x04\xd1\xdd\x96\x9f\xcd\x0e\x05\x9b\xa6y\xbd\xden\x8a\xa2\x80\xcf\x1f\x80\xdd\x07\x0eB\xdf\x8c\xde\xf0J~\x1e464x\xf0.~v\xe8\xd0\xa15UUUrVV\xd6\x94\x94\x94\x94\xa7\x98\xc7\x9a2Z\xc5\x90\x14\xe3\x9bU2L3\xaf\x98\x83BS\xec\xb2t\xc9\xedv\x1f)--]\x83\x1f\xd7\xb4k\xfb\xee\x7f-Z\xb5\xdfa\x8d\x5c]\x04\xb1\xc8T\xac\x0d\x96f\x85\xb4\x90a\x07\x8d \x92F\x13\x82~\x93\xf4\x10E$\x12\x89\xe2\x12x\x10e\xc9\xb0\xa9S&;\xcf\x97]\x80\xcd[w@E\xe5E\xb0\x98bp\xbb\xae\x1emK\x90:\xac\x1f\x91?\xd55\xba\xa4\x07\xaf\xc3 G\xd8\xa4\x87Bm\xf6\xbf\xa4Fw\x9c\x04\xd3\xads\x9b\xd2Z\xc3\xbe\xdcx\xb4e,\x8bj\x14\xe1\xba\x10x\x12F\x03\x86\x1b\xbfv\x13\x09\xee\xc25\xfc\xfb\xfc\xfc\xfc\x9a\xb6\xd7\xe7\xe4\xe4\xbe8z\xf4\xe8eee\xe7S\x15%lBqH\x04qf\xfc\xe0\xf3z\xa9..\xe3\xd7\xa6cj\x1d\xe8\xb0~\x00\x0d!\x15\xeaKh\x0e\xdf\x93\x9dr?\xd9.\x03\xc7s\xd0\x8cD\xfc\xbf\x11\x09\xe0w&#\x91m\x1d\xd6\xd0 \x11j\x1fMA\x22\xf3\x90\x88\xd3 rwF<^J-\xf5\x118\x12Y\xd5\xa1\x1d\x19\x92\x99$\x08\xe2\xd7\xce\x04\x19d\xbb\x03\x89\xf0*\x11\xbf\xdf\x07\x1e\x8f\x0f5S\xb0\x96\xbc\x14=\x92A2\x81\x0ek)\x91\xc8X$\xb2\xd6\xe9\x94;a\x0d\x81Q#~\x1f\x12\xf1\xf9\xe2\xa1`\xb0\x8c\x1e\x1c\x22\x89\xa2\x0e\xed\x89\x91\xc83\x82(,\xc3\xcc\xea{\x97H\xb3\x9aZu>\x9fgTi\xe9\x91\x0b\x1d\xde\xd4#\x09Z\x93G\x8b\x82\xf8\xa9\x9c \xa7\xca\x0e\xf9\xe9\x0d\x1b6\x94?T\x0d<`\x9f\xe5A\x1e\xd8\xb0\xba\xf2\xb5\xb4\x19\x8d0\xce\xd1\x9f\x09z\x8fF\xd0\xfb5f\xab\xd5\xca\x87\xc3a/\x1e{0\xee\xe8A{A\xe0\x7f\xd1V\xf9\x15N\x0d\x19H\xb5\x03\xcb\x8b\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x10\xaa\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x01 \x05\xc9\x11\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x0a\xe1iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \xc2\xa9 2009 by Oliver Twardowski\x0a \x0a \x0a \x0a \x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0aI\xaa4\xe0\x00\x00\x05_IDATx\xdab\xfc\xff\xff?\x036\xc0\xc4\x80\x03`H\x18\xe4\xf1\x81\x8d`\x81\x09\xd8\xf5\x08\xff\xff\xf4\xe87\x03\x9f\x1c+\x98\xcf\x88\xcb\x0e\xb8\x0e\xe32\xfe\xff\x7f\x7f@\x14]\x98\xf4\x89\x11 \x80\x18)s\x15\xccEp;\xecz\x84\x80.\xfa\x83\xa9\xe3\xfb\xbb\xbfpK\xadZ\x05!\xba@\x96\x83\xb0Q)\xef\x7f\xfd\x5c\xde\xff0>@\x00\xe1t\x15\xd1AE\x08\xb0\xa0\x0b8N\x16\x81X\xc9\x08F`\x86\x82\x84\x08\xc3\xbc\x90\xeb\x8c(n\x05a\x8b&\x01\x14\xf7\xc2\xb0]\xaf\x10\x5c\x0c\xc5\x86\xef\xaf\xff2\xb0\xf131\x98\xd5\x0a\xfcg\x04\xda\xf3\x1fj\x0b\xb2/Q<\xcd\xc8\xc8\xc8\x0e\xa4\xe4\x818\x18\x88O\x00\xf1] \xfe\x04\xc4\xdf\x80\xea~\x81\xd4\x00\x04\x10\xedCi\x805\xa4l\xd0\xc6\xf0 \x8a\xa7\x1d\xa7\x88\x82\x02\x93\x01\x1e\xa4P\xa9w\xb7\x7f\x81\x13\x1a\x86\x0d\x7f\x7f\xfccx\x7f\xeb\x17X\x01\x88\xfe\xf3\xf3?\xc3\xbe\xdc7\x8c8\x93\xc6\x9f\xef\xff\xe0&\xc1\x80}\x9f\xf0\x7f\x9c~\xf8\xf6\xfa/\x86?\xfe\x00m\xc5i\x83\xba\x96\x10\x8390Y\x80\x8d\xfc\xcf\x00O\x17\xc8\xb6\xa2h\xb8y\xed\x1d\x033\x07#\x03,\x1b\xc3r\x15\x03\x9e\xb4\xc4\x0d\xa4\xf4\x80\xd8\x04\x88\x8f\x02\xf1#P\x9a\x04\xaa\xf9\x0aS\x03\x10@$\xa7%\x9a\xa7\x8c\xe1c\x01r!\x89\x0f\x10\x8c\x03|\x06\x09k\xb2\x83\xca(\x86\xb77~a\xa4\x06\x82\x16\x80\x0c\x16Pb\x03&\xbb\xff\x0c\xa8:\x19Q\x13%\x14|\xb8\xf7\x1b\xab%,\xb8\x5c\xc7#\xc9\x02\xce\x05_\x9e\xfd\xc1\xaa\x11\xe63\x90\x1c\x88-\xa8\xc2\x06\x16CW\x8b\xd3\x82?\xdf\xff\x83]\x8f\x0b \x1b\xc4#\xc3\xc2\xf0\x1b-K\x92\x14\x07\x1az\x22\x0c\x0f\x9f\xbdg\xf8\xfe\xe6/V5\x1c\xc2\xcc\xe0\xe0\xfa\xf1\xf6\x1f\xfe\x9c\x89\x0cL*\xf9\xff3\xc2\xc3\x1c\x16\xda\xb0:\x89\x11\x1a\x0b\xff\xe1\xb1\xf1\xfb\xeb\x7f\xacAITN\x06\xf9\x82\x11\xd8f8\xdf\x8b=\xa5P\x94L)\x05\x00\x014Z\x16\x8dZ0j\x01\x05\x16\x80\xdaM\xc4T:dW8\xc2\x1a\xec\xe0\xf6\x96\xa2\xb8\x18\xc3\xd9\x03\x8f\xc9\xabp\x04\xd5\xd8 \x0d7p\x13\x05\xa9z\xf9\x0f-\x02\x19\x11U\x8f\x82\x84(\xa2%NL\x10\xf1+\xb12\xfc\x07vC\xfe\xfd\xfd\xcf\xf0\xef\x0f2\x06\x89A\xf1\xef\xff\x105@|\xee\xd0S\xe2\xfa\x090\xd7\x83\xba\x5c \xdf}z\xfc\x1b\xabF^\xa0<\xc8\xb9\x9f\x1e\xfd\x82\xd7jD[\x00\x02 \xd7)H\x8b0,)\xbd\xc5\x88\xcb\x11\xb0pw\x9c$\xfc\xdf\xc0F\x9a\xb4T\xf4\x17h\xc1\xbf_\xff\x08V\x99 \x8b\xfe\x82\x83\xed\x1fi\x16\x80\xc2\xfb\xef\xdf\x7fD%\xd9\xff\xd0\xf8!\xc9\x02\x90\xa6\x9bW\xdf\xc0\x83\x03_\x9a\xff\xfb\x07\x12\xd9$\xc5\xc1\xa9\xb6\x8f\xe0 \x88\x98\xa3\xf4\x1fT\xa9\xffx\xfb\x17k\x9e\x00\xc9\xfd\xfb\x8d;\x88\xf0\xe6\x035-\x11\x86\x07O\xdf\xc1\x13;#J\xe5\xcf\x88\xe0\x03\x89_\x9f\xfe\x11_\xe9\x83\x0cg\xe1fDjA\xfc\xc7h\xd31\xa0uB\xcf\xb4\x7fd$\xb9\xa8\x00Y\xc4\xcc\x0e\x0d\xe7\x9f\x0cD5\xc2\xc8*\x8b\x98X\x19\xc0\xe1\x8c\xcf \xa2{\xfcH\xdd#N %\x08\xc4\x86@\xac\x0f\xec\xd8/\x00\x8a\xf9\x01\xd9\xa0\xa4\xf5\x1e\x88\xdf\x82Z\x98P\x0c\xeaz\xff\x01:\xf6\x0f\xdd\x9b-\x00\x01\xda\xb5\x9a\x95\x06b \x9c\xa8U(\x05Q\x16\x11\x17\xa1\x1e\xfc\xa1\x0aB\xef\xe2\xcb\xf8\x0e>\x96\x8f\xe0A\x8f\xeaE\x0f\x1e<\xb4hA\xa1\x94j\x11\xb5\xdb\xce:\x93MLv\xb7\xd8M\xeaV\x94\x0d\x0c\xcd\x12\x9a\xe4K6\x99\xf9\xbe\xd9\xdc\x07\xf8\xf3\x1e\xb3\x00P\x00(\x00\x14\x00\xdc\x1cA^\xc5\x8c\x18\x5c<\xd7\xaf\x03\xa0\xe2\xd5\x16D\x00qt\xb2+\xbc\xd0\xe5\xe9\xbd3\x18gG\xa6V\xb2~\xb8\x8e\x9d\x80\xd4|gd\x04\x03\xfa\xed\xe4\xc0\x92bP\xe3\xa9m\xe8)\xa4\x17sVE\xfe\xc2\xb0\x1f\x15\xfdg\x05d\x0d \xd2\xb4JB^\x13\x83\x1b\x9a\x1a\xe7\xba\xaf\x10\x11qn\x86gZ\xf6\xe1*`\xa3\x04\x03\xe7:|\x0b5\x7f\xea\xde\xf53\x81\xb0\x02 \xe8N\xb5\xc4R\xbd\x1aA\xa4\xaeF\xa8\xd4\xa4\xa26\x15\xc4\x86\xb1\xffR\xfbs#p:\x17\xd6g \x0c\xa2\xe1\x05m]\xf5\xd8\xf5\xc5\xa3\xd5\xa0\xb4\x08\x15\x7fNL\xba\xf70H1$\xb2\xfa\x81\xcf\xae\xceZ\x99\xfa\xb5\x06@\x14L\xa9\x800t;\xc4\xe7\xc7\x1d\x9e\x04EL\x99\xb4_\xda\x12\xc8\xc8\xd6\xdcv`\x00\x0c\x84\xc4\x09V\x03\x8d\xbb:\x896\xaa\x06\xa2\xfb\xb992\xc1\xf0\xa4\x860\x0c\xc0J1\xf9~a\xa8o\x90|9\xc73\xf0\xf1\x02\xa9\x95T\x13\xae\xed\xaf0WvAJ\x82:\xd5\x10d\xdf\xd9\x89\xfd\xc0\xf6\x9e\xc7\x9aH\x9d\x8d\x5c\x17\xab\xfaK\xb2F~\x01\xc6\xbe\x00\x8dV;\x96\xdd\xd8\xf0\x97\xd9\xedM;\x9fkts\xc7\x93\x83v\x8c\x8b1\xcd\xf7Y,G\x9c&\xeb<\xe6\x13\xf4\xc5J\xcf\xfd\x1e\xe4\xeb\xc8\xe8w\xb6\xcc\xbf\xb2\xad\xe9L\xb6tJ\xcay\xa9\x94z\x98h70Q\xfe\xc6\xc5\x0fL\xcc\x89\x85v1\xcf\xb5\x0a\x89\x15\xe8\x87?\x12\xa8M%\x98\x1bu\x98\xa7Y\xacw@~\xda0#m\x11\xad\x82VF#\x1df\x0bmM.L\x17\xad\x89\xd6\xa3\xcb\x0b\xed\x1d\xed\x0d\xedU\xd6!aCi\xe2y\x94F\xf3/e\x95O\x08\xef\x09\x03\x8d\x1a\xcd\xa5\x00\x00\x00\x00IEND\xaeB`\x82\x00\x00\x02\xf9PyLoT - the Python picking and Localisation Tool\x0a\x0a

PyLoT is a program which is capable of picking seismic phases,\x0aexporting these as numerous standard phase format and localize the corresponding\x0aseismic event with external software as, e.g.:

\x0a
    \x0a
  • NonLinLoc
  • \x0a
  • HypoInvers
  • \x0a
  • HypoSat
  • \x0a
  • whatever you want ...
  • \x0a
\x0a

Read more on the\x0aPyLoT WikiPage.

\x0a

Bug reports are very much appreciated and can also be delivered on our\x0aPyLoT TracPage after\x0asuccessful registration.

\x0a\x0a" +qt_resource_name = "\x00\x04\x00\x06\xec0\x00h\x00e\x00l\x00p\x00\x05\x00o\xa6S\x00i\x00c\x00o\x00n\x00s\x00\x06\x07\xa7(\x98\x00s\x00p\x00l\x00a\x00s\x00h\x00\x0a\x08\x94\x19\x07\x00s\x00p\x00l\x00a\x00s\x00h\x00.\x00p\x00n\x00g\x00\x09\x0fG\xb4\xc7\x00k\x00e\x00y\x00_\x00T\x00.\x00p\x00n\x00g\x00\x09\x0fC\xb4\xc7\x00k\x00e\x00y\x00_\x00P\x00.\x00p\x00n\x00g\x00\x09\x03g\xa7G\x00p\x00y\x00l\x00o\x00t\x00.\x00p\x00n\x00g\x00\x0a\x0c\xad\x0f\x07\x00d\x00e\x00l\x00e\x00t\x00e\x00.\x00p\x00n\x00g\x00\x09\x0fH\xb4\xc7\x00k\x00e\x00y\x00_\x00U\x00.\x00p\x00n\x00g\x00\x0b\x08T\x9f\x87\x00c\x00o\x00m\x00p\x00a\x00r\x00e\x00.\x00p\x00n\x00g\x00\x09\x0fD\xb4\xc7\x00k\x00e\x00y\x00_\x00Q\x00.\x00p\x00n\x00g\x00\x0a\x0a\xc8\xf7'\x00f\x00i\x00l\x00t\x00e\x00r\x00.\x00p\x00n\x00g\x00\x0a\x08\xaa1 everything +AUTOPHASES_AIC_HOS4_ARH #phasefile# %name of autoPILOT output phase file +AUTOLOC_AIC_HOS4_ARH #locfile# %name of autoPILOT output location file +AUTOFOCMEC_AIC_HOS4_ARH.in #focmecin# %name of focmec input file containing polarities +HYPOSAT #locrt# %location routine used ("HYPOINVERSE" or "HYPOSAT") +6 #pmin# %minimum required P picks for location +4 #p0min# %minimum required P picks for location if at least + %3 excellent P picks are found +2 #smin# %minimum required S picks for location +/home/ludger/bin/run_HYPOSAT4autoPILOT.csh #cshellp# %path and name of c-shell script to run location routine +7.6 8.5 #blon# %longitude bounding for location map +49 49.4 #blat# %lattitude bounding for location map +#parameters for moment magnitude estimation# +5000 #vp# %average P-wave velocity +2800 #vs# %average S-wave velocity +2200 #rho# %rock density [kg/m^3] +300 #Qp# %quality factor for P waves +100 #Qs# %quality factor for S waves +#common settings picker# +15 #pstart# %start time [s] for calculating CF for P-picking +40 #pstop# %end time [s] for calculating CF for P-picking +-1.0 #sstart# %start time [s] after or before(-) P-onset for calculating CF for S-picking +7 #sstop# %end time [s] after P-onset for calculating CF for S-picking +2 20 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz] +2 30 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz] +2 15 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz] +2 20 #bph2# %lower/upper corner freq. of second band pass filter z-comp. [Hz] +#special settings for calculating CF# +%!!Be careful when editing the following!! +#Z-component# +HOS #algoP# %choose algorithm for P-onset determination (HOS, ARZ, or AR3) +7 #tlta# %for HOS-/AR-AIC-picker, length of LTA window [s] +4 #hosorder# %for HOS-picker, order of Higher Order Statistics +2 #Parorder# %for AR-picker, order of AR process of Z-component +1.2 #tdet1z# %for AR-picker, length of AR determination window [s] for Z-component, 1st pick +0.4 #tpred1z# %for AR-picker, length of AR prediction window [s] for Z-component, 1st pick +0.6 #tdet2z# %for AR-picker, length of AR determination window [s] for Z-component, 2nd pick +0.2 #tpred2z# %for AR-picker, length of AR prediction window [s] for Z-component, 2nd pick +0.001 #addnoise# %add noise to seismogram for stable AR prediction +3 0.1 0.5 0.1 #tsnrz# %for HOS/AR, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +3 #pickwinP# %for initial AIC pick, length of P-pick window [s] +8 #Precalcwin# %for HOS/AR, window length [s] for recalculation of CF (relative to 1st pick) +0 #peps4aic# %for HOS/AR, artificial uplift of samples of AIC-function (P) +0.2 #aictsmooth# %for HOS/AR, take average of samples for smoothing of AIC-function [s] +0.1 #tsmoothP# %for HOS/AR, take average of samples for smoothing CF [s] +0.001 #ausP# %for HOS/AR, artificial uplift of samples (aus) of CF (P) +1.3 #nfacP# %for HOS/AR, noise factor for noise level determination (P) +#H-components# +ARH #algoS# %choose algorithm for S-onset determination (ARH or AR3) +0.8 #tdet1h# %for HOS/AR, length of AR-determination window [s], H-components, 1st pick +0.4 #tpred1h# %for HOS/AR, length of AR-prediction window [s], H-components, 1st pick +0.6 #tdet2h# %for HOS/AR, length of AR-determinaton window [s], H-components, 2nd pick +0.3 #tpred2h# %for HOS/AR, length of AR-prediction window [s], H-components, 2nd pick +4 #Sarorder# %for AR-picker, order of AR process of H-components +6 #Srecalcwin# %for AR-picker, window length [s] for recalculation of CF (2nd pick) (H) +3 #pickwinS# %for initial AIC pick, length of S-pick window [s] +2 0.2 1.5 0.5 #tsnrh# %for ARH/AR3, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +0.05 #aictsmoothS# %for AIC-picker, take average of samples for smoothing of AIC-function [s] +0.02 #tsmoothS# %for AR-picker, take average of samples for smoothing CF [s] (S) +0.2 #pepsS# %for AR-picker, artificial uplift of samples of CF (S) +0.4 #ausS# %for HOS/AR, artificial uplift of samples (aus) of CF (S) +1.5 #nfacS# %for AR-picker, noise factor for noise level determination (S) +%first-motion picker% +1 #minfmweight# %minimum required p weight for first-motion determination +2 #minFMSNR# %miniumum required SNR for first-motion determination +0.2 #fmpickwin# %pick window around P onset for calculating zero crossings +%quality assessment% +#inital AIC onset# +0.01 0.02 0.04 0.08 #timeerrorsP# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for P +0.04 0.08 0.16 0.32 #timeerrorsS# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for S +80 #minAICPslope# %below this slope [counts/s] the initial P pick is rejected +1.2 #minAICPSNR# %below this SNR the initial P pick is rejected +50 #minAICSslope# %below this slope [counts/s] the initial S pick is rejected +1.5 #minAICSSNR# %below this SNR the initial S pick is rejected +#check duration of signal using envelope function# +1.5 #prepickwin# %pre-signal window length [s] for noise level estimation +0.7 #minsiglength# %minimum required length of signal [s] +0.2 #sgap# %safety gap between noise and signal window [s] +2 #noisefactor# %noiselevel*noisefactor=threshold +60 #minpercent# %per cent of samples required higher than threshold +#check for spuriously picked S-onsets# +3.0 #zfac# %P-amplitude must exceed zfac times RMS-S amplitude +#jackknife-processing for P-picks# +3 #thresholdweight#%minimum required weight of picks +3 #dttolerance# %maximum allowed deviation of P picks from median [s] +4 #minstats# %minimum number of stations with reliable P picks +3 #Sdttolerance# %maximum allowed deviation from Wadati-diagram + diff --git a/inputs/autoPyLoT_local.in b/inputs/autoPyLoT_local.in new file mode 100644 index 00000000..075450de --- /dev/null +++ b/inputs/autoPyLoT_local.in @@ -0,0 +1,99 @@ +%This is a parameter input file for autoPyLoT. +%All main and special settings regarding data handling +%and picking are to be set here! +%Parameters are optimized for local data sets! +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#main settings# +/DATA/Insheim #rootpath# %project path +EVENT_DATA/LOCAL #datapath# %data path +2016.08_Insheim #database# %name of data base +e0007.224.16 #eventID# %event ID for single event processing +/DATA/Insheim/STAT_INFO #invdir# %full path to inventory or dataless-seed file +PILOT #datastructure#%choose data structure +0 #iplot# %flag for plotting: 0 none, 1 partly, >1 everything +True #apverbose# %choose 'True' or 'False' for terminal output +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#NLLoc settings# +/home/ludger/NLLOC #nllocbin# %path to NLLoc executable +/home/ludger/NLLOC/Insheim #nllocroot# %root of NLLoc-processing directory +AUTOPHASES.obs #phasefile# %name of autoPyLoT-output phase file for NLLoc + %(in nllocroot/obs) +Insheim_min1d032016_auto.in #ctrfile# %name of autoPyLoT-output control file for NLLoc + %(in nllocroot/run) +ttime #ttpatter# %pattern of NLLoc ttimes from grid + %(in nllocroot/times) +AUTOLOC_nlloc #outpatter# %pattern of NLLoc-output file + %(returns 'eventID_outpatter') +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#parameters for seismic moment estimation# +3530 #vp# %average P-wave velocity +2500 #rho# %average rock density [kg/m^3] +300 0.8 #Qp# %quality factor for P waves ([Qp, ap], Qp*f^a) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +AUTOFOCMEC_AIC_HOS4_ARH.in #focmecin# %name of focmec input file containing derived polarities +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#common settings picker# +15.0 #pstart# %start time [s] for calculating CF for P-picking +60.0 #pstop# %end time [s] for calculating CF for P-picking +-1.0 #sstart# %start time [s] relative to P-onset for calculating CF for S-picking +10.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking +2 20 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz] +2 30 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz] +2 15 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz] +2 20 #bph2# %lower/upper corner freq. of second band pass filter z-comp. [Hz] +#special settings for calculating CF# +%!!Edit the following only if you know what you are doing!!% +#Z-component# +HOS #algoP# %choose algorithm for P-onset determination (HOS, ARZ, or AR3) +7.0 #tlta# %for HOS-/AR-AIC-picker, length of LTA window [s] +4 #hosorder# %for HOS-picker, order of Higher Order Statistics +2 #Parorder# %for AR-picker, order of AR process of Z-component +1.2 #tdet1z# %for AR-picker, length of AR determination window [s] for Z-component, 1st pick +0.4 #tpred1z# %for AR-picker, length of AR prediction window [s] for Z-component, 1st pick +0.6 #tdet2z# %for AR-picker, length of AR determination window [s] for Z-component, 2nd pick +0.2 #tpred2z# %for AR-picker, length of AR prediction window [s] for Z-component, 2nd pick +0.001 #addnoise# %add noise to seismogram for stable AR prediction +3 0.1 0.5 0.5 #tsnrz# %for HOS/AR, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +3.0 #pickwinP# %for initial AIC pick, length of P-pick window [s] +6.0 #Precalcwin# %for HOS/AR, window length [s] for recalculation of CF (relative to 1st pick) +0.2 #aictsmooth# %for HOS/AR, take average of samples for smoothing of AIC-function [s] +0.1 #tsmoothP# %for HOS/AR, take average of samples for smoothing CF [s] +0.001 #ausP# %for HOS/AR, artificial uplift of samples (aus) of CF (P) +1.3 #nfacP# %for HOS/AR, noise factor for noise level determination (P) +#H-components# +ARH #algoS# %choose algorithm for S-onset determination (ARH or AR3) +0.8 #tdet1h# %for HOS/AR, length of AR-determination window [s], H-components, 1st pick +0.4 #tpred1h# %for HOS/AR, length of AR-prediction window [s], H-components, 1st pick +0.6 #tdet2h# %for HOS/AR, length of AR-determinaton window [s], H-components, 2nd pick +0.3 #tpred2h# %for HOS/AR, length of AR-prediction window [s], H-components, 2nd pick +4 #Sarorder# %for AR-picker, order of AR process of H-components +5.0 #Srecalcwin# %for AR-picker, window length [s] for recalculation of CF (2nd pick) (H) +3.0 #pickwinS# %for initial AIC pick, length of S-pick window [s] +2 0.2 1.5 0.5 #tsnrh# %for ARH/AR3, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +0.5 #aictsmoothS# %for AIC-picker, take average of samples for smoothing of AIC-function [s] +0.7 #tsmoothS# %for AR-picker, take average of samples for smoothing CF [s] (S) +0.9 #ausS# %for HOS/AR, artificial uplift of samples (aus) of CF (S) +1.5 #nfacS# %for AR-picker, noise factor for noise level determination (S) +%first-motion picker% +1 #minfmweight# %minimum required P weight for first-motion determination +2 #minFMSNR# %miniumum required SNR for first-motion determination +0.2 #fmpickwin# %pick window around P onset for calculating zero crossings +%quality assessment% +#inital AIC onset# +0.01 0.02 0.04 0.08 #timeerrorsP# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for P +0.04 0.08 0.16 0.32 #timeerrorsS# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for S +4 #minAICPslope# %below this slope [counts/s] the initial P pick is rejected +1.2 #minAICPSNR# %below this SNR the initial P pick is rejected +2 #minAICSslope# %below this slope [counts/s] the initial S pick is rejected +1.5 #minAICSSNR# %below this SNR the initial S pick is rejected +#check duration of signal using envelope function# +3 #minsiglength# %minimum required length of signal [s] +1.0 #noisefactor# %noiselevel*noisefactor=threshold +40 #minpercent# %required percentage of samples higher than threshold +#check for spuriously picked S-onsets# +2.0 #zfac# %P-amplitude must exceed at least zfac times RMS-S amplitude +#check statistics of P onsets# +2.5 #mdttolerance# %maximum allowed deviation of P picks from median [s] +#wadati check# +1.0 #wdttolerance# %maximum allowed deviation from Wadati-diagram + diff --git a/inputs/autoPyLoT_regional.in b/inputs/autoPyLoT_regional.in new file mode 100644 index 00000000..14972000 --- /dev/null +++ b/inputs/autoPyLoT_regional.in @@ -0,0 +1,100 @@ +%This is a parameter input file for autoPyLoT. +%All main and special settings regarding data handling +%and picking are to be set here! +%Parameters are optimized for regional data sets! +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#main settings# +/DATA/Egelados #rootpath# %project path +EVENT_DATA/LOCAL #datapath# %data path +2006.01_Nisyros #database# %name of data base +e1412.008.06 #eventID# %event ID for single event processing +/DATA/Egelados/STAT_INFO #invdir# %full path to inventory or dataless-seed file +PILOT #datastructure# %choose data structure +0 #iplot# %flag for plotting: 0 none, 1, partly, >1 everything +True #apverbose# %choose 'True' or 'False' for terminal output +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#NLLoc settings# +/home/ludger/NLLOC #nllocbin# %path to NLLoc executable +/home/ludger/NLLOC/Insheim #nllocroot# %root of NLLoc-processing directory +AUTOPHASES.obs #phasefile# %name of autoPyLoT-output phase file for NLLoc + %(in nllocroot/obs) +Insheim_min1d2015_auto.in #ctrfile# %name of autoPyLoT-output control file for NLLoc + %(in nllocroot/run) +ttime #ttpatter# %pattern of NLLoc ttimes from grid + %(in nllocroot/times) +AUTOLOC_nlloc #outpatter# %pattern of NLLoc-output file + %(returns 'eventID_outpatter') +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#parameters for seismic moment estimation# +3530 #vp# %average P-wave velocity +2700 #rho# %average rock density [kg/m^3] +1000f**0.8 #Qp# %quality factor for P waves (Qp*f^a) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +AUTOFOCMEC_AIC_HOS4_ARH.in #focmecin# %name of focmec input file containing derived polarities +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#common settings picker# +20 #pstart# %start time [s] for calculating CF for P-picking +100 #pstop# %end time [s] for calculating CF for P-picking +1.0 #sstart# %start time [s] after or before(-) P-onset for calculating CF for S-picking +100 #sstop# %end time [s] after P-onset for calculating CF for S-picking +3 10 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz] +3 12 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz] +3 8 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz] +3 6 #bph2# %lower/upper corner freq. of second band pass filter H-comp. [Hz] +#special settings for calculating CF# +%!!Be careful when editing the following!! +#Z-component# +HOS #algoP# %choose algorithm for P-onset determination (HOS, ARZ, or AR3) +7 #tlta# %for HOS-/AR-AIC-picker, length of LTA window [s] +4 #hosorder# %for HOS-picker, order of Higher Order Statistics +2 #Parorder# %for AR-picker, order of AR process of Z-component +1.2 #tdet1z# %for AR-picker, length of AR determination window [s] for Z-component, 1st pick +0.4 #tpred1z# %for AR-picker, length of AR prediction window [s] for Z-component, 1st pick +0.6 #tdet2z# %for AR-picker, length of AR determination window [s] for Z-component, 2nd pick +0.2 #tpred2z# %for AR-picker, length of AR prediction window [s] for Z-component, 2nd pick +0.001 #addnoise# %add noise to seismogram for stable AR prediction +5 0.2 3.0 1.5 #tsnrz# %for HOS/AR, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +3 #pickwinP# %for initial AIC and refined pick, length of P-pick window [s] +8 #Precalcwin# %for HOS/AR, window length [s] for recalculation of CF (relative to 1st pick) +1.0 #aictsmooth# %for HOS/AR, take average of samples for smoothing of AIC-function [s] +0.3 #tsmoothP# %for HOS/AR, take average of samples for smoothing CF [s] +0.3 #ausP# %for HOS/AR, artificial uplift of samples (aus) of CF (P) +1.3 #nfacP# %for HOS/AR, noise factor for noise level determination (P) +#H-components# +ARH #algoS# %choose algorithm for S-onset determination (ARH or AR3) +0.8 #tdet1h# %for HOS/AR, length of AR-determination window [s], H-components, 1st pick +0.4 #tpred1h# %for HOS/AR, length of AR-prediction window [s], H-components, 1st pick +0.6 #tdet2h# %for HOS/AR, length of AR-determinaton window [s], H-components, 2nd pick +0.3 #tpred2h# %for HOS/AR, length of AR-prediction window [s], H-components, 2nd pick +4 #Sarorder# %for AR-picker, order of AR process of H-components +10 #Srecalcwin# %for AR-picker, window length [s] for recalculation of CF (2nd pick) (H) +25 #pickwinS# %for initial AIC and refined pick, length of S-pick window [s] +5 0.2 3.0 3.0 #tsnrh# %for ARH/AR3, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +3.5 #aictsmoothS# %for AIC-picker, take average of samples for smoothing of AIC-function [s] +1.0 #tsmoothS# %for AR-picker, take average of samples for smoothing CF [s] (S) +0.2 #ausS# %for HOS/AR, artificial uplift of samples (aus) of CF (S) +1.5 #nfacS# %for AR-picker, noise factor for noise level determination (S) +%first-motion picker% +1 #minfmweight# %minimum required p weight for first-motion determination +2 #minFMSNR# %miniumum required SNR for first-motion determination +6.0 #fmpickwin# %pick window around P onset for calculating zero crossings +%quality assessment% +#inital AIC onset# +0.04 0.08 0.16 0.32 #timeerrorsP# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for P +0.04 0.08 0.16 0.32 #timeerrorsS# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for S +3 #minAICPslope# %below this slope [counts/s] the initial P pick is rejected +1.2 #minAICPSNR# %below this SNR the initial P pick is rejected +5 #minAICSslope# %below this slope [counts/s] the initial S pick is rejected +2.5 #minAICSSNR# %below this SNR the initial S pick is rejected +#check duration of signal using envelope function# +30 #minsiglength# %minimum required length of signal [s] +2.5 #noisefactor# %noiselevel*noisefactor=threshold +60 #minpercent# %required percentage of samples higher than threshold +#check for spuriously picked S-onsets# +0.5 #zfac# %P-amplitude must exceed at least zfac times RMS-S amplitude +#check statistics of P onsets# +45 #mdttolerance# %maximum allowed deviation of P picks from median [s] +#wadati check# +3.0 #wdttolerance# %maximum allowed deviation from Wadati-diagram + diff --git a/inputs/filter.in b/inputs/filter.in new file mode 100644 index 00000000..8303b88a --- /dev/null +++ b/inputs/filter.in @@ -0,0 +1,2 @@ +P bandpass 4 2.0 20.0 +S bandpass 4 2.0 15.0 \ No newline at end of file diff --git a/inputs/pylot.in b/inputs/pylot.in new file mode 100644 index 00000000..611277b2 --- /dev/null +++ b/inputs/pylot.in @@ -0,0 +1,98 @@ +%This is a example parameter input file for PyLoT. +%All main and special settings regarding data handling +%and picking are to be set here! +%Parameters shown here are optimized for local data sets! +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#main settings# +/data/Geothermie/Insheim #rootpath# %project path +EVENT_DATA/LOCAL #datapath# %data path +2013.02_Insheim #database# %name of data base +e0019.048.13 #eventID# %event ID for single event processing +/data/Geothermie/Insheim/STAT_INFO #invdir# %full path to inventory or dataless-seed file +PILOT #datastructure# %choose data structure +0 #iplot# %flag for plotting: 0 none, 1 partly, >1 everything +True #apverbose# %choose 'True' or 'False' for terminal output +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#NLLoc settings# +/progs/bin #nllocbin# %path to NLLoc executable +/data/Geothermie/Insheim/LOCALISATION/NLLoc #nllocroot# %root of NLLoc-processing directory +AUTOPHASES.obs #phasefile# %name of autoPyLoT-output phase file for NLLoc + %(in nllocroot/obs) +Insheim_min1d2015.in #ctrfile# %name of PyLoT-output control file for NLLoc + %(in nllocroot/run) +ttime #ttpatter# %pattern of NLLoc ttimes from grid + %(in nllocroot/times) +AUTOLOC_nlloc #outpatter# %pattern of NLLoc-output file + %(returns 'eventID_outpatter') +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#parameters for seismic moment estimation# +3530 #vp# %average P-wave velocity +2500 #rho# %average rock density [kg/m^3] +300 0.8 #Qp# %quality factor for P waves (Qp*f^a); list(Qp, a) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +AUTOFOCMEC_AIC_HOS4_ARH.in #focmecin# %name of focmec input file containing derived polarities +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#common settings picker# +15.0 #pstart# %start time [s] for calculating CF for P-picking +60.0 #pstop# %end time [s] for calculating CF for P-picking +-1.0 #sstart# %start time [s] relative to P-onset for calculating CF for S-picking +10.0 #sstop# %end time [s] after P-onset for calculating CF for S-picking +2 20 #bpz1# %lower/upper corner freq. of first band pass filter Z-comp. [Hz] +2 30 #bpz2# %lower/upper corner freq. of second band pass filter Z-comp. [Hz] +2 15 #bph1# %lower/upper corner freq. of first band pass filter H-comp. [Hz] +2 20 #bph2# %lower/upper corner freq. of second band pass filter z-comp. [Hz] +#special settings for calculating CF# +%!!Edit the following only if you know what you are doing!!% +#Z-component# +HOS #algoP# %choose algorithm for P-onset determination (HOS, ARZ, or AR3) +7.0 #tlta# %for HOS-/AR-AIC-picker, length of LTA window [s] +4 #hosorder# %for HOS-picker, order of Higher Order Statistics +2 #Parorder# %for AR-picker, order of AR process of Z-component +1.2 #tdet1z# %for AR-picker, length of AR determination window [s] for Z-component, 1st pick +0.4 #tpred1z# %for AR-picker, length of AR prediction window [s] for Z-component, 1st pick +0.6 #tdet2z# %for AR-picker, length of AR determination window [s] for Z-component, 2nd pick +0.2 #tpred2z# %for AR-picker, length of AR prediction window [s] for Z-component, 2nd pick +0.001 #addnoise# %add noise to seismogram for stable AR prediction +3 0.1 0.5 0.5 #tsnrz# %for HOS/AR, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +3.0 #pickwinP# %for initial AIC pick, length of P-pick window [s] +6.0 #Precalcwin# %for HOS/AR, window length [s] for recalculation of CF (relative to 1st pick) +0.2 #aictsmooth# %for HOS/AR, take average of samples for smoothing of AIC-function [s] +0.1 #tsmoothP# %for HOS/AR, take average of samples for smoothing CF [s] +0.001 #ausP# %for HOS/AR, artificial uplift of samples (aus) of CF (P) +1.3 #nfacP# %for HOS/AR, noise factor for noise level determination (P) +#H-components# +ARH #algoS# %choose algorithm for S-onset determination (ARH or AR3) +0.8 #tdet1h# %for HOS/AR, length of AR-determination window [s], H-components, 1st pick +0.4 #tpred1h# %for HOS/AR, length of AR-prediction window [s], H-components, 1st pick +0.6 #tdet2h# %for HOS/AR, length of AR-determinaton window [s], H-components, 2nd pick +0.3 #tpred2h# %for HOS/AR, length of AR-prediction window [s], H-components, 2nd pick +4 #Sarorder# %for AR-picker, order of AR process of H-components +5.0 #Srecalcwin# %for AR-picker, window length [s] for recalculation of CF (2nd pick) (H) +3.0 #pickwinS# %for initial AIC pick, length of S-pick window [s] +2 0.2 1.5 0.5 #tsnrh# %for ARH/AR3, window lengths for SNR-and slope estimation [tnoise,tsafetey,tsignal,tslope] [s] +0.5 #aictsmoothS# %for AIC-picker, take average of samples for smoothing of AIC-function [s] +0.7 #tsmoothS# %for AR-picker, take average of samples for smoothing CF [s] (S) +0.9 #ausS# %for HOS/AR, artificial uplift of samples (aus) of CF (S) +1.5 #nfacS# %for AR-picker, noise factor for noise level determination (S) +%first-motion picker% +1 #minfmweight# %minimum required P weight for first-motion determination +2 #minFMSNR# %miniumum required SNR for first-motion determination +0.2 #fmpickwin# %pick window around P onset for calculating zero crossings +%quality assessment% +#inital AIC onset# +0.01 0.02 0.04 0.08 #timeerrorsP# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for P +0.04 0.08 0.16 0.32 #timeerrorsS# %discrete time errors [s] corresponding to picking weights [0 1 2 3] for S +4 #minAICPslope# %below this slope [counts/s] the initial P pick is rejected +1.2 #minAICPSNR# %below this SNR the initial P pick is rejected +2 #minAICSslope# %below this slope [counts/s] the initial S pick is rejected +1.5 #minAICSSNR# %below this SNR the initial S pick is rejected +#check duration of signal using envelope function# +3 #minsiglength# %minimum required length of signal [s] +1.0 #noisefactor# %noiselevel*noisefactor=threshold +40 #minpercent# %required percentage of samples higher than threshold +#check for spuriously picked S-onsets# +2.0 #zfac# %P-amplitude must exceed at least zfac times RMS-S amplitude +#check statistics of P onsets# +2.5 #mdttolerance# %maximum allowed deviation of P picks from median [s] +#wadati check# +1.0 #wdttolerance# %maximum allowed deviation from Wadati-diagram diff --git a/inputs/richter_scaling.data b/inputs/richter_scaling.data new file mode 100644 index 00000000..74909881 --- /dev/null +++ b/inputs/richter_scaling.data @@ -0,0 +1,53 @@ + 0 1.4 + 10 1.5 + 20 1.7 + 25 1.9 + 30 2.1 + 35 2.3 + 40 2.4 + 45 2.5 + 50 2.6 + 60 2.8 + 70 2.8 + 75 2.9 + 85 2.9 + 90 3.0 +100 3.0 +110 3.1 +120 3.1 +130 3.2 +140 3.2 +150 3.3 +160 3.3 +170 3.4 +180 3.4 +190 3.5 +200 3.5 +210 3.6 +230 3.7 +240 3.7 +250 3.8 +260 3.8 +270 3.9 +280 3.9 +290 4.0 +300 4.0 +310 4.1 +320 4.2 +330 4.2 +340 4.2 +350 4.3 +360 4.3 +370 4.3 +380 4.4 +390 4.4 +400 4.5 +430 4.6 +470 4.7 +510 4.8 +560 4.9 +600 5.1 +700 5.2 +800 5.4 +900 5.5 +1000 5.7 \ No newline at end of file diff --git a/makePyLoT.py b/makePyLoT.py new file mode 100644 index 00000000..1e87f1fb --- /dev/null +++ b/makePyLoT.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python +# encoding: utf-8 +from __future__ import print_function + +""" +makePyLoT -- build and install PyLoT + +makePyLoT is a python make file in order to establish the folder structure and +meet requisites + +It defines +:class CLIError: +:method main: + +:author: Sebastian Wehling-Benatelli + +:copyright: 2014 MAGS2 EP3 Working Group. All rights reserved. + +:license: GNU Lesser General Public License, Version 3 + (http://www.gnu.org/copyleft/lesser.html) + +:contact: sebastian.wehling@rub.de + +updated: Updated +""" + +import glob +import os +import sys +import shutil +import copy + +from argparse import ArgumentParser +from argparse import RawDescriptionHelpFormatter + +__all__ = [] +__version__ = 0.1 +__date__ = '2014-11-26' +__updated__ = '2016-04-28' + +DEBUG = 0 +TESTRUN = 0 +PROFILE = 0 + + +class CLIError(Exception): + """Generic exception to raise and log different fatal errors.""" + + def __init__(self, msg): + super(CLIError).__init__(type(self)) + self.msg = "E: %s" % msg + + def __str__(self): + return self.msg + + def __unicode__(self): + return self.msg + + +def main(argv=None): # IGNORE:C0111 + '''Command line options.''' + + if argv is None: + argv = sys.argv + else: + sys.argv.extend(argv) + + program_name = os.path.basename(sys.argv[0]) + program_version = "v%s" % __version__ + program_build_date = str(__updated__) + program_version_message = 'makePyLoT %s (%s)' % ( + program_version, program_build_date) + program_shortdesc = __import__('__main__').__doc__.split("\n")[1] + program_license = '''{0:s} + + Created by Sebastian Wehling-Benatelli on {1:s}. + Copyright 2014 MAGS2 EP3 Working Group. All rights reserved. + + GNU Lesser General Public License, Version 3 + (http://www.gnu.org/copyleft/lesser.html) + + Distributed on an "AS IS" basis without warranties + or conditions of any kind, either express or implied. + + USAGE + '''.format(program_shortdesc, str(__date__)) + + try: + # Setup argument parser + parser = ArgumentParser(description=program_license, + formatter_class=RawDescriptionHelpFormatter) + parser.add_argument("-b", "--build", dest="build", action="store_true", + help="build PyLoT") + parser.add_argument("-v", "--verbose", dest="verbose", action="count", + help="set verbosity level") + parser.add_argument("-i", "--install", dest="install", + action="store_true", + help="install PyLoT on the system") + parser.add_argument("-d", "--directory", dest="directory", + help="installation directory", metavar="RE") + parser.add_argument('-V', '--version', action='version', + version=program_version_message) + + # Process arguments + args = parser.parse_args() + + verbose = args.verbose + build = args.build + install = args.install + directory = args.directory + + if verbose > 0: + print("Verbose mode on") + if install and not directory: + raise CLIError("""Trying to install without appropriate + destination; please specify an installation + directory!""") + if build and install: + print("Building and installing PyLoT ...\n") + buildPyLoT(verbose) + installPyLoT(verbose) + elif build and not install: + print("Building PyLoT without installing! Please wait ...\n") + buildPyLoT(verbose) + cleanUp() + return 0 + except KeyboardInterrupt: + cleanUp(1) + return 0 + except Exception as e: + if DEBUG or TESTRUN: + raise e + indent = len(program_name) * " " + sys.stderr.write(program_name + ": " + repr(e) + "\n") + sys.stderr.write(indent + " for help use --help") + return 2 + + +def buildPyLoT(verbosity=None): + system = sys.platform + if verbosity > 1: + msg = ("... on system: {0}\n" + "\n" + " Current working directory: {1}\n" + ).format(system, os.getcwd()) + print(msg) + if system.startswith(('win', 'microsoft')): + raise CLIError( + "building on Windows system not tested yet; implementation pending") + elif system == 'darwin': + # create a symbolic link to the desired python interpreter in order to + # display the right application name + for path in os.getenv('PATH').split(':'): + found = glob.glob(os.path.join(path, 'python')) + if found: + os.symlink(found, './PyLoT') + break + + +def installPyLoT(verbosity=None): + files_to_copy = {'autoPyLoT_local.in':['~', '.pylot'], + 'autoPyLoT_regional.in':['~', '.pylot'], + 'filter.in':['~', '.pylot']} + if verbosity > 0: + print ('starting installation of PyLoT ...') + if verbosity > 1: + print ('copying input files into destination folder ...') + ans = input('please specify scope of interest ' + '([0]=local, 1=regional) :') or 0 + if not isinstance(ans, int): + ans = int(ans) + ans = 'local' if ans is 0 else 'regional' + link_dest = [] + for file, destination in files_to_copy.items(): + link_file = ans in file + if link_file: + link_dest = copy.deepcopy(destination) + link_dest.append('autoPyLoT.in') + link_dest = os.path.join(*link_dest) + destination.append(file) + destination = os.path.join(*destination) + srcfile = os.path.join('input', file) + assert not os.path.isabs(srcfile), 'source files seem to be ' \ + 'corrupted ...' + if verbosity > 1: + print ('copying file {file} to folder {dest}'.format(file=file, dest=destination)) + shutil.copyfile(srcfile, destination) + if link_file: + if verbosity: + print('linking input file for autoPyLoT ...') + os.symlink(destination, link_dest) + + + + +def cleanUp(verbosity=None): + if verbosity >= 1: + print('cleaning up build files...') + if sys.platform == 'darwin': + os.remove('./PyLoT') + + +if __name__ == "__main__": + if DEBUG: + sys.argv.append("-h") + sys.argv.append("-v") + if TESTRUN: + import doctest + + doctest.testmod() + if PROFILE: + import cProfile + import pstats + + profile_filename = 'makePyLoT_profile.txt' + cProfile.run('main()', profile_filename) + statsfile = open("profile_stats.txt", "wb") + p = pstats.Stats(profile_filename, stream=statsfile) + stats = p.strip_dirs().sort_stats('cumulative') + stats.print_stats() + statsfile.close() + sys.exit(0) + sys.exit(main()) diff --git a/pylot/PyLoT.ico b/pylot/PyLoT.ico new file mode 100644 index 00000000..81bd4cf5 Binary files /dev/null and b/pylot/PyLoT.ico differ diff --git a/pylot/RELEASE-VERSION b/pylot/RELEASE-VERSION new file mode 100644 index 00000000..be128064 --- /dev/null +++ b/pylot/RELEASE-VERSION @@ -0,0 +1 @@ +0.1a diff --git a/pylot/__init__.py b/pylot/__init__.py new file mode 100755 index 00000000..e0981d72 --- /dev/null +++ b/pylot/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# -------------------------------------------------------- +# Purpose: Convience imports for PyLoT +# +''' +================================================ +PyLoT - the Python picking and Localization Tool +================================================ + +This python library contains a graphical user interfaces for picking +seismic phases. This software needs ObsPy (http://github.com/obspy/obspy/wiki) +and the Qt4 libraries to be installed first. + +PILOT has been developed in Mathworks' MatLab. In order to distribute +PILOT without facing portability problems, it has been decided to re- +develop the software package in Python. The great work of the ObsPy +group allows easy handling of a bunch of seismic data and PyLoT will +benefit a lot compared to the former MatLab version. + +The development of PyLoT is part of the joint research project MAGS2. + +:copyright: + The PyLoT Development Team +:license: + GNU Lesser General Public License, Version 3 + (http://www.gnu.org/copyleft/lesser.html) +''' diff --git a/pylot/core/__init__.py b/pylot/core/__init__.py new file mode 100755 index 00000000..40a96afc --- /dev/null +++ b/pylot/core/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pylot/core/analysis/__init__.py b/pylot/core/analysis/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/pylot/core/analysis/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pylot/core/analysis/magnitude.py b/pylot/core/analysis/magnitude.py new file mode 100644 index 00000000..5a057b6f --- /dev/null +++ b/pylot/core/analysis/magnitude.py @@ -0,0 +1,694 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created autumn/winter 2015. + +:author: Ludger Küperkoch / MAGS2 EP3 working group +""" +import os + +import matplotlib.pyplot as plt +import numpy as np +import obspy.core.event as ope +from obspy.geodetics import degrees2kilometers +from scipy import integrate, signal +from scipy.optimize import curve_fit + +from pylot.core.pick.utils import getsignalwin, crossings_nonzero_all, \ + select_for_phase +from pylot.core.util.utils import common_range, fit_curve + + +def richter_magnitude_scaling(delta): + relation = np.loadtxt(os.path.join(os.path.expanduser('~'), + '.pylot', 'richter_scaling.data')) + # prepare spline interpolation to calculate return value + func, params = fit_curve(relation[:, 0], relation[:, 1]) + return func(delta, params) + + +class Magnitude(object): + """ + Base class object for Magnitude calculation within PyLoT. + """ + + def __init__(self, stream, event, verbosity=False, iplot=0): + self._type = "M" + self._plot_flag = iplot + self._verbosity = verbosity + self._event = event + self._stream = stream + self._magnitudes = dict() + + def __str__(self): + print( + 'number of stations used: {0}\n'.format(len(self.magnitudes.values()))) + print('\tstation\tmagnitude') + for s, m in self.magnitudes.items(): print('\t{0}\t{1}'.format(s, m)) + + def __nonzero__(self): + return bool(self.magnitudes) + + @property + def type(self): + return self._type + + @property + def plot_flag(self): + return self._plot_flag + + @plot_flag.setter + def plot_flag(self, value): + self._plot_flag = value + + @property + def verbose(self): + return self._verbosity + + @verbose.setter + def verbose(self, value): + if not isinstance(value, bool): + print('WARNING: only boolean values accepted...\n') + value = bool(value) + self._verbosity = value + + @property + def stream(self): + return self._stream + + @stream.setter + def stream(self, value): + self._stream = value + + @property + def event(self): + return self._event + + @property + def origin_id(self): + return self._event.origins[0].resource_id + + @property + def arrivals(self): + return self._event.origins[0].arrivals + + @property + def magnitudes(self): + return self._magnitudes + + @magnitudes.setter + def magnitudes(self, value): + """ + takes a tuple and saves the key value pair to private + attribute _magnitudes + :param value: station, magnitude value pair + :type value: tuple or list + :return: + """ + station, magnitude = value + self._magnitudes[station] = magnitude + + def calc(self): + pass + + def updated_event(self): + self.event.magnitudes.append(self.net_magnitude()) + return self.event + + def net_magnitude(self): + if self: + # TODO if an average Magnitude instead of the median is calculated + # StationMagnitudeContributions should be added to the returned + # Magnitude object + # mag_error => weights (magnitude error estimate from peak_to_peak, calcsourcespec?) + # weights => StationMagnitdeContribution + mag = ope.Magnitude( + mag=np.median([M.mag for M in self.magnitudes.values()]), + magnitude_type=self.type, + origin_id=self.origin_id, + station_count=len(self.magnitudes), + azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap) + return mag + return None + + +class RichterMagnitude(Magnitude): + """ + Method to derive peak-to-peak amplitude as seen on a Wood-Anderson- + seismograph. Has to be derived from instrument corrected traces! + """ + + # poles, zeros and sensitivity of WA seismograph + # (see Uhrhammer & Collins, 1990, BSSA, pp. 702-716) + _paz = { + 'poles': [5.6089 - 5.4978j, -5.6089 - 5.4978j], + 'zeros': [0j, 0j], + 'gain': 2080, + 'sensitivity': 1 + } + + _amplitudes = dict() + + def __init__(self, stream, event, calc_win, verbosity=False, iplot=0): + super(RichterMagnitude, self).__init__(stream, event, verbosity, iplot) + + self._calc_win = calc_win + self._type = 'ML' + self.calc() + + @property + def calc_win(self): + return self._calc_win + + @calc_win.setter + def calc_win(self, value): + self._calc_win = value + + @property + def amplitudes(self): + return self._amplitudes + + @amplitudes.setter + def amplitudes(self, value): + station, a0 = value + self._amplitudes[station] = a0 + + def peak_to_peak(self, st, t0): + + # simulate Wood-Anderson response + st.simulate(paz_remove=None, paz_simulate=self._paz) + + # trim waveform to common range + stime, etime = common_range(st) + st.trim(stime, etime) + + # get time delta from waveform data + dt = st[0].stats.delta + + power = [np.power(tr.data, 2) for tr in st if tr.stats.channel[-1] not + in 'Z3'] + if len(power) != 2: + raise ValueError('Wood-Anderson amplitude defintion only valid for ' + 'two horizontals: {0} given'.format(len(power))) + power_sum = power[0] + power[1] + # + sqH = np.sqrt(power_sum) + + # get time array + th = np.arange(0, len(sqH) * dt, dt) + # get maximum peak within pick window + iwin = getsignalwin(th, t0 - stime, self.calc_win) + wapp = np.max(sqH[iwin]) + if self.verbose: + print("Determined Wood-Anderson peak-to-peak amplitude: {0} " + "mm".format(wapp)) + + # check for plot flag (for debugging only) + if self.plot_flag > 1: + st.plot() + f = plt.figure(2) + plt.plot(th, sqH) + plt.plot(th[iwin], sqH[iwin], 'g') + plt.plot([t0, t0], [0, max(sqH)], 'r', linewidth=2) + plt.title( + 'Station %s, RMS Horizontal Traces, WA-peak-to-peak=%4.1f mm' \ + % (st[0].stats.station, wapp)) + plt.xlabel('Time [s]') + plt.ylabel('Displacement [mm]') + plt.show() + raw_input() + plt.close(f) + + return wapp + + def calc(self): + for a in self.arrivals: + if a.phase not in 'sS': + continue + pick = a.pick_id.get_referred_object() + station = pick.waveform_id.station_code + wf = select_for_phase(self.stream.select( + station=station), a.phase) + if not wf: + if self.verbose: + print( + 'WARNING: no waveform data found for station {0}'.format( + station)) + continue + delta = degrees2kilometers(a.distance) + onset = pick.time + a0 = self.peak_to_peak(wf, onset) + amplitude = ope.Amplitude(generic_amplitude=a0 * 1e-3) + amplitude.unit = 'm' + amplitude.category = 'point' + amplitude.waveform_id = pick.waveform_id + amplitude.magnitude_hint = self.type + amplitude.pick_id = pick.resource_id + amplitude.type = 'AML' + self.event.amplitudes.append(amplitude) + self.amplitudes = (station, amplitude) + # using standard Gutenberg-Richter relation + # TODO make the ML calculation more flexible by allowing + # use of custom relation functions + magnitude = ope.StationMagnitude( + mag=np.log10(a0) + richter_magnitude_scaling(delta)) + magnitude.origin_id = self.origin_id + magnitude.waveform_id = pick.waveform_id + magnitude.amplitude_id = amplitude.resource_id + magnitude.station_magnitude_type = self.type + self.event.station_magnitudes.append(magnitude) + self.magnitudes = (station, magnitude) + + +class MomentMagnitude(Magnitude): + ''' + Method to calculate seismic moment Mo and moment magnitude Mw. + Requires results of class calcsourcespec for calculating plateau w0 + and corner frequency fc of source spectrum, respectively. Uses + subfunction calcMoMw.py. Returns modified dictionary of picks including + Dc-value, corner frequency fc, seismic moment Mo and + corresponding moment magntiude Mw. + ''' + + _props = dict() + + def __init__(self, stream, event, vp, Qp, density, verbosity=False, + iplot=False): + super(MomentMagnitude, self).__init__(stream, event, verbosity, iplot) + + self._vp = vp + self._Qp = Qp + self._density = density + self._type = 'Mw' + self.calc() + + @property + def p_velocity(self): + return self._vp + + @property + def p_attenuation(self): + return self._Qp + + @property + def rock_density(self): + return self._density + + @property + def moment_props(self): + return self._props + + @moment_props.setter + def moment_props(self, value): + station, props = value + self._props[station] = props + + @property + def seismic_moment(self): + return self._m0 + + @seismic_moment.setter + def seismic_moment(self, value): + self._m0 = value + + def calc(self): + for a in self.arrivals: + if a.phase not in 'pP': + continue + pick = a.pick_id.get_referred_object() + station = pick.waveform_id.station_code + wf = select_for_phase(self.stream.select( + station=station), a.phase) + if not wf: + continue + onset = pick.time + distance = degrees2kilometers(a.distance) + azimuth = a.azimuth + incidence = a.takeoff_angle + w0, fc = calcsourcespec(wf, onset, self.p_velocity, distance, + azimuth, + incidence, self.p_attenuation, + self.plot_flag, self.verbose) + if w0 is None or fc is None: + if self.verbose: + print("WARNING: insufficient frequency information") + continue + wf = select_for_phase(wf, "P") + m0, mw = calcMoMw(wf, w0, self.rock_density, self.p_velocity, + distance, self.verbose) + self.moment_props = (station, dict(w0=w0, fc=fc, Mo=m0)) + magnitude = ope.StationMagnitude(mag=mw) + magnitude.origin_id = self.origin_id + magnitude.waveform_id = pick.waveform_id + magnitude.station_magnitude_type = self.type + self.event.station_magnitudes.append(magnitude) + self.magnitudes = (station, magnitude) + + +def calcMoMw(wfstream, w0, rho, vp, delta, verbosity=False): + ''' + Subfunction of run_calcMoMw to calculate individual + seismic moments and corresponding moment magnitudes. + + :param: wfstream + :type: `~obspy.core.stream.Stream` + + :param: w0, height of plateau of source spectrum + :type: float + + :param: rho, rock density [kg/m³] + :type: integer + + :param: delta, hypocentral distance [km] + :type: integer + + :param: inv, name/path of inventory or dataless-SEED file + :type: string + ''' + + tr = wfstream[0] + delta = delta * 1000 # hypocentral distance in [m] + + if verbosity: + print( + "calcMoMw: Calculating seismic moment Mo and moment magnitude Mw for station {0} ...".format( + tr.stats.station)) + + # additional common parameters for calculating Mo + rP = 2 / np.sqrt( + 15) # average radiation pattern of P waves (Aki & Richards, 1980) + freesurf = 2.0 # free surface correction, assuming vertical incidence + + Mo = w0 * 4 * np.pi * rho * np.power(vp, 3) * delta / (rP * freesurf) + + # Mw = np.log10(Mo * 1e07) * 2 / 3 - 10.7 # after Hanks & Kanamori (1979), defined for [dyn*cm]! + Mw = np.log10(Mo) * 2 / 3 - 6.7 # for metric units + + if verbosity: + print( + "calcMoMw: Calculated seismic moment Mo = {0} Nm => Mw = {1:3.1f} ".format( + Mo, Mw)) + + return Mo, Mw + + +def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence, + qp, iplot=0, verbosity=False): + ''' + Subfunction to calculate the source spectrum and to derive from that the plateau + (usually called omega0) and the corner frequency assuming Aki's omega-square + source model. Has to be derived from instrument corrected displacement traces, + thus restitution and integration necessary! Integrated traces are rotated + into ray-coordinate system ZNE => LQT using Obspy's rotate modul! + + :param: wfstream (corrected for instrument) + :type: `~obspy.core.stream.Stream` + + :param: onset, P-phase onset time + :type: float + + :param: vp, Vp-wave velocity + :type: float + + :param: delta, hypocentral distance [km] + :type: integer + + :param: azimuth + :type: integer + + :param: incidence + :type: integer + + :param: Qp, quality factor for P-waves + :type: integer + + :param: iplot, show results (iplot>1) or not (iplot<1) + :type: integer + ''' + if verbosity: + print ("Calculating source spectrum ....") + + # get Q value + Q, A = qp + + dist = delta * 1000 # hypocentral distance in [m] + + fc = None + w0 = None + + zdat = select_for_phase(wfstream, "P") + + dt = zdat[0].stats.delta + + freq = zdat[0].stats.sampling_rate + + # trim traces to common range (for rotation) + trstart, trend = common_range(wfstream) + wfstream.trim(trstart, trend) + + # rotate into LQT (ray-coordindate-) system using Obspy's rotate + # L: P-wave direction + # Q: SV-wave direction + # T: SH-wave direction + LQT = wfstream.rotate('ZNE->LQT', azimuth, incidence) + ldat = LQT.select(component="L") + if len(ldat) == 0: + # if horizontal channels are 2 and 3 + # no azimuth information is available and thus no + # rotation is possible! + if verbosity: + print("calcsourcespec: Azimuth information is missing, " + "no rotation of components possible!") + ldat = LQT.select(component="Z") + + # integrate to displacement + # unrotated vertical component (for comparison) + inttrz = signal.detrend(integrate.cumtrapz(zdat[0].data, None, dt)) + + # rotated component Z => L + Ldat = signal.detrend(integrate.cumtrapz(ldat[0].data, None, dt)) + + # get window after P pulse for + # calculating source spectrum + rel_onset = onset - trstart + impickP = int(rel_onset * freq) + wfzc = Ldat[impickP: len(Ldat) - 1] + # get time array + t = np.arange(0, len(inttrz) * dt, dt) + # calculate spectrum using only first cycles of + # waveform after P onset! + zc = crossings_nonzero_all(wfzc) + if np.size(zc) == 0 or len(zc) <= 3: + if verbosity: + print ("calcsourcespec: Something is wrong with the waveform, " + "no zero crossings derived!\n") + print ("No calculation of source spectrum possible!") + plotflag = 0 + else: + plotflag = 1 + index = min([3, len(zc) - 1]) + calcwin = (zc[index] - zc[0]) * dt + iwin = getsignalwin(t, rel_onset, calcwin) + xdat = Ldat[iwin] + + # fft + fny = freq / 2 + l = len(xdat) / freq + # number of fft bins after Bath + n = freq * l + # find next power of 2 of data length + m = pow(2, np.ceil(np.log(len(xdat)) / np.log(2))) + N = int(np.power(m, 2)) + y = dt * np.fft.fft(xdat, N) + Y = abs(y[: N / 2]) + L = (N - 1) / freq + f = np.arange(0, fny, 1 / L) + + # remove zero-frequency and frequencies above + # corner frequency of seismometer (assumed + # to be 100 Hz) + fi = np.where((f >= 1) & (f < 100)) + F = f[fi] + YY = Y[fi] + + # correction for attenuation + wa = 2 * np.pi * F # angular frequency + D = np.exp((wa * dist) / (2 * vp * Q * F ** A)) + YYcor = YY.real * D + + # get plateau (DC value) and corner frequency + # initial guess of plateau + w0in = np.mean(YYcor[0:100]) + # initial guess of corner frequency + # where spectral level reached 50% of flat level + iin = np.where(YYcor >= 0.5 * w0in) + Fcin = F[iin[0][np.size(iin) - 1]] + + # use of implicit scipy otimization function + fit = synthsourcespec(F, w0in, Fcin) + [optspecfit, _] = curve_fit(synthsourcespec, F, YYcor, [w0in, Fcin]) + w01 = optspecfit[0] + fc1 = optspecfit[1] + if verbosity: + print ("calcsourcespec: Determined w0-value: %e m/Hz, \n" + "Determined corner frequency: %f Hz" % (w01, fc1)) + + # use of conventional fitting + [w02, fc2] = fitSourceModel(F, YYcor, Fcin, iplot, verbosity) + + # get w0 and fc as median of both + # source spectrum fits + w0 = np.median([w01, w02]) + fc = np.median([fc1, fc2]) + if verbosity: + print("calcsourcespec: Using w0-value = %e m/Hz and fc = %f Hz" % ( + w0, fc)) + + if iplot > 1: + f1 = plt.figure() + tLdat = np.arange(0, len(Ldat) * dt, dt) + plt.subplot(2, 1, 1) + # show displacement in mm + p1, = plt.plot(t, np.multiply(inttrz, 1000), 'k') + p2, = plt.plot(tLdat, np.multiply(Ldat, 1000)) + plt.legend([p1, p2], ['Displacement', 'Rotated Displacement']) + if plotflag == 1: + plt.plot(t[iwin], np.multiply(xdat, 1000), 'g') + plt.title('Seismogram and P Pulse, Station %s-%s' \ + % (zdat[0].stats.station, zdat[0].stats.channel)) + else: + plt.title('Seismogram, Station %s-%s' \ + % (zdat[0].stats.station, zdat[0].stats.channel)) + plt.xlabel('Time since %s' % zdat[0].stats.starttime) + plt.ylabel('Displacement [mm]') + + if plotflag == 1: + plt.subplot(2, 1, 2) + p1, = plt.loglog(f, Y.real, 'k') + p2, = plt.loglog(F, YY.real) + p3, = plt.loglog(F, YYcor, 'r') + p4, = plt.loglog(F, fit, 'g') + plt.loglog([fc, fc], [w0 / 100, w0], 'g') + plt.legend([p1, p2, p3, p4], ['Raw Spectrum', \ + 'Used Raw Spectrum', \ + 'Q-Corrected Spectrum', \ + 'Fit to Spectrum']) + plt.title('Source Spectrum from P Pulse, w0=%e m/Hz, fc=%6.2f Hz' \ + % (w0, fc)) + plt.xlabel('Frequency [Hz]') + plt.ylabel('Amplitude [m/Hz]') + plt.grid() + plt.show() + raw_input() + plt.close(f1) + + return w0, fc + + +def synthsourcespec(f, omega0, fcorner): + ''' + Calculates synthetic source spectrum from given plateau and corner + frequency assuming Akis omega-square model. + + :param: f, frequencies + :type: array + + :param: omega0, DC-value (plateau) of source spectrum + :type: float + + :param: fcorner, corner frequency of source spectrum + :type: float + ''' + + # ssp = omega0 / (pow(2, (1 + f / fcorner))) + ssp = omega0 / (1 + pow(2, (f / fcorner))) + + return ssp + + +def fitSourceModel(f, S, fc0, iplot, verbosity=False): + ''' + Calculates synthetic source spectrum by varying corner frequency fc. + Returns best approximated plateau omega0 and corner frequency, i.e. with least + common standard deviations. + + :param: f, frequencies + :type: array + + :param: S, observed source spectrum + :type: array + + :param: fc0, initial corner frequency + :type: float + ''' + + w0 = [] + stdw0 = [] + fc = [] + stdfc = [] + STD = [] + + # get window around initial corner frequency for trials + fcstopl = fc0 - max(1, len(f) / 10) + il = np.argmin(abs(f - fcstopl)) + fcstopl = f[il] + fcstopr = fc0 + min(len(f), len(f) / 10) + ir = np.argmin(abs(f - fcstopr)) + fcstopr = f[ir] + iF = np.where((f >= fcstopl) & (f <= fcstopr)) + + # vary corner frequency around initial point + for i in range(il, ir): + FC = f[i] + indexdc = np.where((f > 0) & (f <= FC)) + dc = np.mean(S[indexdc]) + stddc = np.std(dc - S[indexdc]) + w0.append(dc) + stdw0.append(stddc) + fc.append(FC) + # slope + indexfc = np.where((f >= FC) & (f <= fcstopr)) + yi = dc / (1 + (f[indexfc] / FC) ** 2) + stdFC = np.std(yi - S[indexfc]) + stdfc.append(stdFC) + STD.append(stddc + stdFC) + + # get best found w0 anf fc from minimum + if len(STD) > 0: + fc = fc[np.argmin(STD)] + w0 = w0[np.argmin(STD)] + elif len(STD) == 0: + fc = fc0 + w0 = max(S) + if verbosity: + print( + "fitSourceModel: best fc: {0} Hz, best w0: {1} m/Hz".format(fc, w0)) + + if iplot > 1: + plt.figure(iplot) + plt.loglog(f, S, 'k') + plt.loglog([f[0], fc], [w0, w0], 'g') + plt.loglog([fc, fc], [w0 / 100, w0], 'g') + plt.title('Calculated Source Spectrum, Omega0=%e m/Hz, fc=%6.2f Hz' \ + % (w0, fc)) + plt.xlabel('Frequency [Hz]') + plt.ylabel('Amplitude [m/Hz]') + plt.grid() + plt.figure(iplot + 1) + plt.subplot(311) + plt.plot(f[il:ir], STD, '*') + plt.title('Common Standard Deviations') + plt.xticks([]) + plt.subplot(312) + plt.plot(f[il:ir], stdw0, '*') + plt.title('Standard Deviations of w0-Values') + plt.xticks([]) + plt.subplot(313) + plt.plot(f[il:ir], stdfc, '*') + plt.title('Standard Deviations of Corner Frequencies') + plt.xlabel('Corner Frequencies [Hz]') + plt.show() + raw_input() + plt.close() + + return w0, fc diff --git a/pylot/core/io/__init__.py b/pylot/core/io/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/pylot/core/io/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pylot/core/io/data.py b/pylot/core/io/data.py new file mode 100644 index 00000000..c3ca8b85 --- /dev/null +++ b/pylot/core/io/data.py @@ -0,0 +1,601 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import copy +import os +from obspy import read_events +from obspy.core import read, Stream, UTCDateTime +from obspy.core.event import Event + +from pylot.core.io.phases import readPILOTEvent, picks_from_picksdict, \ + picksdict_from_pilot, merge_picks +from pylot.core.util.errors import FormatError, OverwriteError +from pylot.core.util.utils import fnConstructor, full_range + + +class Data(object): + """ + Data container with attributes wfdata holding ~obspy.core.stream. + + :type parent: PySide.QtGui.QWidget object, optional + :param parent: A PySide.QtGui.QWidget object utilized when + called by a GUI to display a PySide.QtGui.QMessageBox instead of printing + to standard out. + :type evtdata: ~obspy.core.event.Event object, optional + :param evtdata ~obspy.core.event.Event object containing all derived or + loaded event. Container object holding, e.g. phase arrivals, etc. + """ + + def __init__(self, parent=None, evtdata=None): + self._parent = parent + if self.getParent(): + self.comp = parent.getComponent() + else: + self.comp = 'Z' + self.wfdata = Stream() + self._new = False + if isinstance(evtdata, Event): + pass + elif isinstance(evtdata, dict): + evt = readPILOTEvent(**evtdata) + evtdata = evt + elif isinstance(evtdata, basestring): + try: + cat = read_events(evtdata) + if len(cat) is not 1: + raise ValueError('ambiguous event information for file: ' + '{file}'.format(file=evtdata)) + evtdata = cat[0] + except TypeError as e: + if 'Unknown format for file' in e.message: + if 'PHASES' in evtdata: + picks = picksdict_from_pilot(evtdata) + evtdata = Event() + evtdata.picks = picks_from_picksdict(picks) + elif 'LOC' in evtdata: + raise NotImplementedError('PILOT location information ' + 'read support not yet ' + 'implemeted.') + else: + raise e + else: + raise e + else: # create an empty Event object + self.setNew() + evtdata = Event() + evtdata.picks = [] + self.evtdata = evtdata + self.wforiginal = None + self.cuttimes = None + self.dirty = False + + def __str__(self): + return str(self.wfdata) + + def __add__(self, other): + assert isinstance(other, Data), "operands must be of same type 'Data'" + if other.isNew() and not self.isNew(): + picks_to_add = other.get_evt_data().picks + old_picks = self.get_evt_data().picks + for pick in picks_to_add: + if pick not in old_picks: + old_picks.append(pick) + elif not other.isNew() and self.isNew(): + new = other + self + self.evtdata = new.get_evt_data() + elif self.isNew() and other.isNew(): + pass + elif self.get_evt_data().get('id') == other.get_evt_data().get('id'): + other.setNew() + return self + other + else: + raise ValueError("both Data objects have differing " + "unique Event identifiers") + return self + + def getPicksStr(self): + picks_str = '' + for pick in self.get_evt_data().picks: + picks_str += str(pick) + '\n' + return picks_str + + def getParent(self): + """ + + + :return: + """ + return self._parent + + def isNew(self): + """ + + + :return: + """ + return self._new + + def setNew(self): + self._new = True + + def getCutTimes(self): + """ + + + :return: + """ + if self.cuttimes is None: + self.updateCutTimes() + return self.cuttimes + + def updateCutTimes(self): + """ + + + """ + self.cuttimes = full_range(self.getWFData()) + + def getEventFileName(self): + """ + + + :return: + """ + ID = self.getID() + # handle forbidden filenames especially on windows systems + return fnConstructor(str(ID)) + + def exportEvent(self, fnout, fnext='.xml'): + + """ + + :param fnout: + :param fnext: + :raise KeyError: + """ + from pylot.core.util.defaults import OUTPUTFORMATS + + try: + evtformat = OUTPUTFORMATS[fnext] + except KeyError as e: + errmsg = '{0}; selected file extension {1} not ' \ + 'supported'.format(e, fnext) + raise FormatError(errmsg) + + # try exporting event via ObsPy + try: + self.get_evt_data().write(fnout + fnext, format=evtformat) + except KeyError as e: + raise KeyError('''{0} export format + not implemented: {1}'''.format(evtformat, e)) + + def getComp(self): + """ + + + :return: + """ + return self.comp + + def getID(self): + """ + + + :return: + """ + try: + return self.evtdata.get('resource_id').id + except: + return None + + def filterWFData(self, kwargs): + """ + + :param kwargs: + """ + self.getWFData().filter(**kwargs) + self.dirty = True + + def setWFData(self, fnames): + """ + + :param fnames: + """ + self.wfdata = Stream() + self.wforiginal = None + if fnames is not None: + self.appendWFData(fnames) + else: + return False + self.wforiginal = self.getWFData().copy() + self.dirty = False + return True + + def appendWFData(self, fnames): + """ + + :param fnames: + """ + assert isinstance(fnames, list), "input parameter 'fnames' is " \ + "supposed to be of type 'list' " \ + "but is actually" \ + " {0}".format(type(fnames)) + if self.dirty: + self.resetWFData() + + warnmsg = '' + for fname in fnames: + try: + self.wfdata += read(fname) + except TypeError: + try: + self.wfdata += read(fname, format='GSE2') + except Exception as e: + warnmsg += '{0}\n{1}\n'.format(fname, e) + if warnmsg: + warnmsg = 'WARNING: unable to read\n' + warnmsg + print(warnmsg) + + def getWFData(self): + """ + + + :return: + """ + return self.wfdata + + def getOriginalWFData(self): + """ + + + :return: + """ + return self.wforiginal + + def resetWFData(self): + """ + + + """ + self.wfdata = self.getOriginalWFData().copy() + self.dirty = False + + def resetPicks(self): + """ + + + """ + self.get_evt_data().picks = [] + + def get_evt_data(self): + """ + + + :return: + """ + return self.evtdata + + def setEvtData(self, event): + self.evtdata = event + + def applyEVTData(self, data, type='pick', authority_id='rub'): + + """ + + :param data: + :param type: + :param authority_id: + :raise OverwriteError: + """ + + def applyPicks(picks): + """ + Creates ObsPy pick objects and append it to the picks list from the + PyLoT dictionary contain all picks. + :param picks: + :raise OverwriteError: raises an OverwriteError if the picks list is + not empty. The GUI will then ask for a decision. + """ + + #firstonset = find_firstonset(picks) + if self.get_evt_data().picks: + raise OverwriteError('Actual picks would be overwritten!') + else: + picks = picks_from_picksdict(picks) + self.get_evt_data().picks = picks + # if 'smi:local' in self.getID() and firstonset: + # fonset_str = firstonset.strftime('%Y_%m_%d_%H_%M_%S') + # ID = ResourceIdentifier('event/' + fonset_str) + # ID.convertIDToQuakeMLURI(authority_id=authority_id) + # self.get_evt_data().resource_id = ID + + + def applyEvent(event): + """ + takes an `obspy.core.event.Event` object and applies all new + information on the event to the actual data + :param event: + """ + if not self.isNew(): + self.setEvtData(event) + else: + # prevent overwriting original pick information + picks = copy.deepcopy(self.get_evt_data().picks) + event = merge_picks(event, picks) + # apply event information from location + self.get_evt_data().update(event) + + applydata = {'pick': applyPicks, + 'event': applyEvent} + + applydata[type](data) + + +class GenericDataStructure(object): + """ + GenericDataBase type holds all information about the current data- + base working on. + """ + + def __init__(self, **kwargs): + + self.allowedFields = [] + self.expandFields = ['root'] + self.dsFields = {} + + self.modifyFields(**kwargs) + + def modifyFields(self, **kwargs): + + """ + + :param kwargs: + """ + assert isinstance(kwargs, dict), 'dictionary type object expected' + + if not self.extraAllowed(): + kwargs = self.updateNotAllowed(kwargs) + + for key, value in kwargs.items(): + key = str(key).lower() + if value is not None: + if type(value) not in (str, int, float): + for n, val in enumerate(value): + value[n] = str(val) + else: + value = str(value) + try: + self.setFieldValue(key, value) + except KeyError as e: + errmsg = '' + errmsg += 'WARNING:\n' + errmsg += 'unable to set values for datastructure fields\n' + errmsg += '%s; desired value was: %s\n' % (e, value) + print(errmsg) + + def isField(self, key): + """ + + :param key: + :return: + """ + return key in self.getFields().keys() + + def getFieldValue(self, key): + """ + + :param key: + :return: + """ + if self.isField(key): + return self.getFields()[key] + else: + return + + def setFieldValue(self, key, value): + """ + + :param key: + :param value: + :raise KeyError: + """ + if not self.extraAllowed() and key not in self.getAllowed(): + raise KeyError + else: + if not self.isField(key): + print('creating new field "%s"' % key) + self.getFields()[key] = value + + def getFields(self): + """ + + + :return: + """ + return self.dsFields + + def getExpandFields(self): + """ + + + :return: + """ + return self.expandFields + + def setExpandFields(self, keys): + """ + + :param keys: + """ + expandFields = [] + for key in keys: + if self.isField(key): + expandFields.append(key) + self.expandFields = expandFields + + def getAllowed(self): + """ + + + :return: + """ + return self.allowedFields + + def extraAllowed(self): + """ + + + :return: + """ + return not self.allowedFields + + def updateNotAllowed(self, kwargs): + """ + + :param kwargs: + :return: + """ + for key in kwargs: + if key not in self.getAllowed(): + kwargs.__delitem__(key) + return kwargs + + def hasSuffix(self): + """ + + + :return: + """ + try: + self.getFieldValue('suffix') + except KeyError: + return False + else: + if self.getFieldValue('suffix'): + return True + return False + + def expandDataPath(self): + """ + + + :return: + """ + expandList = [] + for item in self.getExpandFields(): + expandList.append(self.getFieldValue(item)) + if self.hasSuffix(): + expandList.append('*%s' % self.getFieldValue('suffix')) + return os.path.join(*expandList) + + def getCatalogName(self): + """ + + + :return: + """ + return os.path.join(self.getFieldValue('root'), 'catalog.qml') + + +class PilotDataStructure(GenericDataStructure): + """ + Object containing the data access information for the old PILOT data + structure. + """ + + def __init__(self, **fields): + if not fields: + fields = {'database': '2006.01', + 'root': '/data/Egelados/EVENT_DATA/LOCAL'} + + GenericDataStructure.__init__(self, **fields) + + self.setExpandFields(['root', 'database']) + + +class SeiscompDataStructure(GenericDataStructure): + """ + Dictionary containing the data access information for an SDS data archive: + + :param str dataType: Desired data type. Default: ``'waveform'`` + :param sdate, edate: Either date string or an instance of + :class:`obspy.core.utcdatetime.UTCDateTime. Default: ``None`` + :type sdate, edate: str or UTCDateTime or None + """ + + def __init__(self, rootpath='/data/SDS', dataformat='MSEED', + filesuffix=None, **kwargs): + super(GenericDataStructure, self).__init__() + + edate = UTCDateTime() + halfyear = UTCDateTime('1970-07-01') + sdate = UTCDateTime(edate - halfyear) + del halfyear + + year = '' + if not edate.year == sdate.year: + nyears = edate.year - sdate.year + for yr in range(nyears): + year += '{0:04d},'.format(sdate.year + yr) + year = '{' + year[:-1] + '}' + else: + year = '{0:04d}'.format(sdate.year) + + # SDS fields' default values + # definitions from + # http://www.seiscomp3.org/wiki/doc/applications/slarchive/SDS + + self.dsFields = {'root': '/data/SDS', 'YEAR': year, 'NET': '??', + 'STA': '????', 'CHAN': 'HH?', 'TYPE': 'D', 'LOC': '', + 'DAY': '{0:03d}'.format(sdate.julday) + } + self.modifiyFields(**kwargs) + + def modifiyFields(self, **kwargs): + """ + + :param kwargs: + """ + if kwargs and isinstance(kwargs, dict): + for key, value in kwargs.iteritems(): + key = str(key) + if type(value) not in (str, int, float): + for n, val in enumerate(value): + value[n] = str(val) + else: + value = str(value) + try: + self.setFieldValue(key, value) + except KeyError as e: + errmsg = '' + errmsg += 'WARNING:\n' + errmsg += 'unable to set values for SDS fields\n' + errmsg += '%s; desired value was: %s\n' % (e, value) + print(errmsg) + + def setFieldValue(self, key, value): + """ + + :param key: + :param value: + """ + if self.isField(key): + self.getFields()[key] = value + else: + print('Warning: trying to set value of non-existent field ' + '{field}'.format(field=key)) + + def expandDataPath(self): + """ + + + :return: + """ + fullChan = '{0}.{1}'.format(self.getFields()['CHAN'], self.getType()) + dataPath = os.path.join(self.getFields()['SDSdir'], + self.getFields()['YEAR'], + self.getFields()['NET'], + self.getFields()['STA'], + fullChan, + '*{0}'.format(self.getFields()['DAY'])) + return dataPath diff --git a/pylot/core/io/inputs.py b/pylot/core/io/inputs.py new file mode 100644 index 00000000..a200e056 --- /dev/null +++ b/pylot/core/io/inputs.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pylot.core.util.errors import ParameterError + + +class AutoPickParameter(object): + ''' + AutoPickParameters is a parameter type object capable to read and/or write + parameter ASCII. + + :param fn str: Filename of the input file + + Parameters are given for example as follows: + ========== ========== ======================================= + Name Value Comment + ========== ========== ======================================= + phl S # phaselabel + ff1 0.1 # freqmin + ff2 0.5 # freqmax + tdet 6.875 # det-window_(s)_for_ar + tpred 2.5 # pred-window_(s)_for_ar + order 4 # order_of_ar + fnoise 0 # noise_level_for_ar + suppp 7 # envelopecoeff + tolt 300 # (s)time around arrival time + f1tpwt 4 # propfact_minfreq_secondtaper + pickwindow 9 # length_of_pick_window + w1 1 # length_of_smoothing_window + w2 0.37 # cf(i-1)*(1+peps)_for_local_min + w3 0.25 # cf(i-1)*(1+peps)_for_local_min + tslope 0.8;2 # slope_det_window_loc_glob + aerr 30;60 # adjusted_error_slope_fitting_loc_glob + tsn 20;5;20;10 # length_signal_window_S/N + proPh Sn # nextprominentphase + ========== ========== ======================================= + ''' + + def __init__(self, fnin=None, fnout=None, verbosity=0, **kwargs): + ''' + Initialize parameter object: + + io content of an ASCII file an form a type consistent dictionary + contain all parameters. + ''' + + self.__filename = fnin + parFileCont = {} + # io from parsed arguments alternatively + for key, val in kwargs.items(): + parFileCont[key] = val + + if self.__filename is not None: + inputFile = open(self.__filename, 'r') + else: + return + try: + lines = inputFile.readlines() + for line in lines: + parspl = line.split('\t')[:2] + parFileCont[parspl[0].strip()] = parspl[1] + except IndexError as e: + if verbosity > 0: + self._printParameterError(e) + inputFile.seek(0) + lines = inputFile.readlines() + for line in lines: + if not line.startswith(('#', '%', '\n', ' ')): + parspl = line.split('#')[:2] + parFileCont[parspl[1].strip()] = parspl[0].strip() + for key, value in parFileCont.items(): + try: + val = int(value) + except: + try: + val = float(value) + except: + if len(value.split(' ')) > 1: + vallist = value.strip().split(' ') + val = [] + for val0 in vallist: + val0 = float(val0) + val.append(val0) + else: + val = str(value.strip()) + parFileCont[key] = val + self.__parameter = parFileCont + + if fnout: + self.export2File(fnout) + + # Human-readable string representation of the object + def __str__(self): + string = '' + string += 'Automated picking parameter:\n\n' + if self.__parameter: + for key, value in self.iteritems(): + string += '%s:\t\t%s\n' % (key, value) + else: + string += 'Empty parameter dictionary.' + return string + + # String representation of the object + def __repr__(self): + return "AutoPickParameter('%s')" % self.__filename + + # Boolean test + def __nonzero__(self): + return self.__parameter + + def __getitem__(self, key): + return self.__parameter[key] + + def __setitem__(self, key, value): + self.__parameter[key] = value + + def __delitem__(self, key): + del self.__parameter[key] + + def __iter__(self): + return iter(self.__parameter) + + def __len__(self): + return len(self.__parameter.keys()) + + def iteritems(self): + for key, value in self.__parameter.items(): + yield key, value + + def hasParam(self, parameter): + if self.__parameter.has_key(parameter): + return True + return False + + def get(self, *args): + try: + for param in args: + try: + return self.__getitem__(param) + except KeyError as e: + self._printParameterError(e) + raise ParameterError(e) + except TypeError: + try: + return self.__getitem__(args) + except KeyError as e: + self._printParameterError(e) + raise ParameterError(e) + + def setParam(self, **kwargs): + for param, value in kwargs.items(): + self.__setitem__(param, value) + # print(self) + + @staticmethod + def _printParameterError(errmsg): + print('ParameterError:\n non-existent parameter %s' % errmsg) + + def export2File(self, fnout): + fid_out = open(fnout, 'w') + lines = [] + for key, value in self.iteritems(): + lines.append('{key}\t{value}'.format(key=key, value=value)) + fid_out.writelines(lines) + + +class FilterOptions(object): + ''' + FilterOptions is a parameter object type providing Butterworth filter + option parameter for PyLoT. Its easy to access properties helps to manage + file based as well as parameter manipulation within the GUI. + + :type filtertype: str, optional + :param filtertype: String containing the desired filtertype For information + about the supported filter types see _`Supported Filter` section . + + :type freq: list, optional + :param freq: list of float(s) describing the cutoff limits of the filter + + :type order: int, optional + :param order: Integer value describing the order of the desired Butterworth + filter. + + .. rubric:: _`Supported Filter` + + ``'bandpass'`` + Butterworth-Bandpass + + ``'bandstop'`` + Butterworth-Bandstop + + ``'lowpass'`` + Butterworth-Lowpass + + ``'highpass'`` + Butterworth-Highpass + ''' + + def __init__(self, filtertype='bandpass', freq=[2., 5.], order=3, + **kwargs): + self._order = order + self._filtertype = filtertype + self._freq = freq + + def __str__(self): + hrs = '''\n\tFilter parameter:\n + Type:\t\t{ftype}\n + Frequencies:\t{freq}\n + Order:\t\t{order}\n + '''.format(ftype=self.getFilterType(), + freq=self.getFreq(), + order=self.getOrder()) + return hrs + + def __nonzero__(self): + return bool(self.getFilterType()) + + def parseFilterOptions(self): + if self: + robject = {'type': self.getFilterType(), 'corners': self.getOrder()} + if len(self.getFreq()) > 1: + robject['freqmin'] = self.getFreq()[0] + robject['freqmax'] = self.getFreq()[1] + else: + robject['freq'] = self.getFreq() if type(self.getFreq()) is \ + float else self.getFreq()[0] + return robject + return None + + def getFreq(self): + return self.__getattribute__('_freq') + + def setFreq(self, freq): + self.__setattr__('_freq', freq) + + def getOrder(self): + return self.__getattribute__('_order') + + def setOrder(self, order): + self.__setattr__('_order', order) + + def getFilterType(self): + return self.__getattribute__('_filtertype') + + def setFilterType(self, filtertype): + self.__setattr__('_filtertype', filtertype) diff --git a/pylot/core/io/location.py b/pylot/core/io/location.py new file mode 100644 index 00000000..157321a8 --- /dev/null +++ b/pylot/core/io/location.py @@ -0,0 +1,220 @@ +from obspy import UTCDateTime +from obspy.core import event as ope + +from pylot.core.util.utils import getLogin, getHash + + +def create_amplitude(pickID, amp, unit, category, cinfo): + ''' + + :param pickID: + :param amp: + :param unit: + :param category: + :param cinfo: + :return: + ''' + amplitude = ope.Amplitude() + amplitude.creation_info = cinfo + amplitude.generic_amplitude = amp + amplitude.unit = ope.AmplitudeUnit(unit) + amplitude.type = ope.AmplitudeCategory(category) + amplitude.pick_id = pickID + return amplitude + + +def create_arrival(pickresID, cinfo, phase, azimuth=None, dist=None): + ''' + create_arrival - function to create an Obspy Arrival + + :param pickresID: Resource identifier of the created pick + :type pickresID: :class: `~obspy.core.event.ResourceIdentifier` object + :param cinfo: An ObsPy :class: `~obspy.core.event.CreationInfo` object + holding information on the creation of the returned object + :type cinfo: :class: `~obspy.core.event.CreationInfo` object + :param phase: name of the arrivals seismic phase + :type phase: str + :param azimuth: azimuth between source and receiver + :type azimuth: float or int, optional + :param dist: distance between source and receiver + :type dist: float or int, optional + :return: An ObsPy :class: `~obspy.core.event.Arrival` object + ''' + arrival = ope.Arrival() + arrival.creation_info = cinfo + arrival.pick_id = pickresID + arrival.phase = phase + if azimuth is not None: + arrival.azimuth = float(azimuth) if azimuth > -180 else azimuth + 360. + else: + arrival.azimuth = azimuth + arrival.distance = dist + return arrival + + +def create_creation_info(agency_id=None, creation_time=None, author=None): + ''' + + :param agency_id: + :param creation_time: + :param author: + :return: + ''' + if author is None: + author = getLogin() + if creation_time is None: + creation_time = UTCDateTime() + return ope.CreationInfo(agency_id=agency_id, author=author, + creation_time=creation_time) + + +def create_event(origintime, cinfo, originloc=None, etype='earthquake', + resID=None, authority_id=None): + ''' + create_event - funtion to create an ObsPy Event + + :param origintime: the events origintime + :type origintime: :class: `~obspy.core.utcdatetime.UTCDateTime` object + :param cinfo: An ObsPy :class: `~obspy.core.event.CreationInfo` object + holding information on the creation of the returned object + :type cinfo: :class: `~obspy.core.event.CreationInfo` object + :param originloc: tuple containing the location of the origin + (LAT, LON, DEP) affiliated with the event which is created + :type originloc: tuple, list + :param etype: Event type str object. converted via ObsPy to a valid event + type string. + :type etype: str + :param resID: Resource identifier of the created event + :type resID: :class: `~obspy.core.event.ResourceIdentifier` object, str + :param authority_id: name of the institution carrying out the processing + :type authority_id: str + :return: An ObsPy :class: `~obspy.core.event.Event` object + ''' + + if originloc is not None: + o = create_origin(origintime, cinfo, + originloc[0], originloc[1], originloc[2]) + else: + o = None + if not resID: + resID = create_resourceID(origintime, etype, authority_id) + elif isinstance(resID, str): + resID = create_resourceID(origintime, etype, authority_id, resID) + elif not isinstance(resID, ope.ResourceIdentifier): + raise TypeError("unsupported type(resID) for resource identifier " + "generation: %s" % type(resID)) + event = ope.Event(resource_id=resID) + event.creation_info = cinfo + event.event_type = etype + if o: + event.origins = [o] + return event + + +def create_magnitude(originID, cinfo): + ''' + create_magnitude - function to create an ObsPy Magnitude object + :param originID: + :type originID: + :param cinfo: + :type cinfo: + :return: + ''' + magnitude = ope.Magnitude() + magnitude.creation_info = cinfo + magnitude.origin_id = originID + return magnitude + + +def create_origin(origintime, cinfo, latitude, longitude, depth): + ''' + create_origin - function to create an ObsPy Origin + :param origintime: the origins time of occurence + :type origintime: :class: `~obspy.core.utcdatetime.UTCDateTime` object + :param cinfo: + :type cinfo: + :param latitude: latitude in decimal degree of the origins location + :type latitude: float + :param longitude: longitude in decimal degree of the origins location + :type longitude: float + :param depth: hypocentral depth of the origin + :type depth: float + :return: An ObsPy :class: `~obspy.core.event.Origin` object + ''' + + assert isinstance(origintime, UTCDateTime), "origintime has to be " \ + "a UTCDateTime object, but " \ + "actually is of type " \ + "'%s'" % type(origintime) + + origin = ope.Origin() + origin.time = origintime + origin.creation_info = cinfo + origin.latitude = latitude + origin.longitude = longitude + origin.depth = depth + return origin + + +def create_pick(origintime, picknum, picktime, eventnum, cinfo, phase, station, + wfseedstr, authority_id): + ''' + create_pick - function to create an ObsPy Pick + + :param origintime: + :type origintime: + :param picknum: number of the created pick + :type picknum: int + :param picktime: + :type picktime: + :param eventnum: human-readable event identifier + :type eventnum: str + :param cinfo: An ObsPy :class: `~obspy.core.event.CreationInfo` object + holding information on the creation of the returned object + :type cinfo: :class: `~obspy.core.event.CreationInfo` object + :param phase: name of the arrivals seismic phase + :type phase: str + :param station: name of the station at which the seismic phase has been + picked + :type station: str + :param wfseedstr: A SEED formatted string of the form + network.station.location.channel in order to set a referenced waveform + :type wfseedstr: str, SEED formatted + :param authority_id: name of the institution carrying out the processing + :type authority_id: str + :return: An ObsPy :class: `~obspy.core.event.Pick` object + ''' + pickID = eventnum + '_' + station.strip() + '/{0:03d}'.format(picknum) + pickresID = create_resourceID(origintime, 'pick', authority_id, pickID) + pick = ope.Pick() + pick.resource_id = pickresID + pick.time = picktime + pick.creation_info = cinfo + pick.phase_hint = phase + pick.waveform_id = ope.ResourceIdentifier(id=wfseedstr, prefix='file:/') + return pick + + +def create_resourceID(timetohash, restype, authority_id=None, hrstr=None): + ''' + + :param timetohash: + :type timetohash + :param restype: type of the resource, e.g. 'orig', 'earthquake' ... + :type restype: str + :param authority_id: name of the institution carrying out the processing + :type authority_id: str, optional + :param hrstr: + :type hrstr: + :return: + ''' + assert isinstance(timetohash, UTCDateTime), "'timetohash' is not an ObsPy" \ + "UTCDateTime object" + hid = getHash(timetohash) + if hrstr is None: + resID = ope.ResourceIdentifier(restype + '/' + hid[0:6]) + else: + resID = ope.ResourceIdentifier(restype + '/' + hrstr) + if authority_id is not None: + resID.convertIDToQuakeMLURI(authority_id=authority_id) + return resID \ No newline at end of file diff --git a/pylot/core/io/phases.py b/pylot/core/io/phases.py new file mode 100644 index 00000000..7871f4bb --- /dev/null +++ b/pylot/core/io/phases.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import glob +import obspy.core.event as ope +import os +import scipy.io as sio +import warnings +from obspy.core import UTCDateTime + +from pylot.core.io.inputs import AutoPickParameter +from pylot.core.io.location import create_arrival, create_event, \ + create_magnitude, create_origin, create_pick +from pylot.core.pick.utils import select_for_phase +from pylot.core.util.utils import getOwner, full_range, four_digits + + +def add_amplitudes(event, amplitudes): + amplitude_list = [] + for pick in event.picks: + try: + a0 = amplitudes[pick.waveform_id.station_code] + amplitude = ope.Amplitude(generic_amplitude=a0 * 1e-3) + amplitude.unit = 'm' + amplitude.category = 'point' + amplitude.waveform_id = pick.waveform_id + amplitude.magnitude_hint = 'ML' + amplitude.pick_id = pick.resource_id + amplitude.type = 'AML' + amplitude_list.append(amplitude) + except KeyError: + continue + event.amplitudes = amplitude_list + return event + +def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs): + """ + readPILOTEvent - function + + Reads Matlab PHASES and LOC files written by Matlab versions of PILOT and + converts the data into an ObsPy Event object which is returned to the + calling program. + + :rtype : ~obspy.core.event.Event + :param eventID: + :param authority: + :param kwargs: + :param phasfn: filename of the old PILOT Matlab PHASES file + :param locfn: filename of the old PILOT Matlab LOC file + :return event: event object containing event and phase information + """ + if phasfn is not None and os.path.isfile(phasfn): + phases = sio.loadmat(phasfn) + phasctime = UTCDateTime(os.path.getmtime(phasfn)) + phasauthor = getOwner(phasfn) + else: + phases = None + phasctime = None + phasauthor = None + if locfn is not None and os.path.isfile(locfn): + loc = sio.loadmat(locfn) + locctime = UTCDateTime(os.path.getmtime(locfn)) + locauthor = getOwner(locfn) + else: + loc = None + locctime = None + locauthor = None + pickcinfo = ope.CreationInfo(agency_id=authority_id, + author=phasauthor, + creation_time=phasctime) + loccinfo = ope.CreationInfo(agency_id=authority_id, + author=locauthor, + creation_time=locctime) + + eventNum = str(loc['ID'][0]) + + # retrieve eventID for the actual database + idsplit = eventNum.split('.') + + # retrieve date information + julday = int(idsplit[1]) + year = int(idsplit[2]) + hour = int(loc['hh']) + minute = int(loc['mm']) + second = int(loc['ss']) + + year = four_digits(year) + + eventDate = UTCDateTime(year=year, julday=julday, hour=hour, + minute=minute, second=second) + + stations = [stat for stat in phases['stat'][0:-1:3]] + + lat = float(loc['LAT']) + lon = float(loc['LON']) + dep = float(loc['DEP']) + + event = create_event(eventDate, loccinfo, originloc=(lat, lon, dep), + etype='earthquake', resID=eventNum, + authority_id=authority_id) + + picks = picksdict_from_pilot(phasfn) + + event.picks = picks_from_picksdict(picks, creation_info=pickcinfo) + + if event.origins: + origin = event.origins[0] + magnitude = create_magnitude(origin.get('id'), loccinfo) + magnitude.mag = float(loc['Mnet']) + magnitude.magnitude_type = 'Ml' + event.magnitudes.append(magnitude) + return event + + +def picksdict_from_pilot(fn): + from pylot.core.util.defaults import TIMEERROR_DEFAULTS + picks = dict() + phases_pilot = sio.loadmat(fn) + stations = stations_from_pilot(phases_pilot['stat']) + params = AutoPickParameter(TIMEERROR_DEFAULTS) + timeerrors = dict(P=params.get('timeerrorsP'), + S=params.get('timeerrorsS')) + for n, station in enumerate(stations): + phases = dict() + for onset_name in 'PS': + onset_label = '{0}time'.format(onset_name) + pick = phases_pilot[onset_label][n] + if not pick[0]: + continue + pick = convert_pilot_times(pick) + uncertainty_label = '{0}weight'.format(onset_name.lower()) + ierror = phases_pilot[uncertainty_label][0, n] + try: + spe = timeerrors[onset_name][ierror] + except IndexError as e: + print(e.message + '\ntake two times the largest default error value') + spe = timeerrors[onset_name][-1] * 2 + phases[onset_name] = dict(mpp=pick, spe=spe, weight=ierror) + picks[station] = phases + + return picks + + +def stations_from_pilot(stat_array): + stations = list() + cur_stat = None + for stat in stat_array: + stat = stat.strip() + if stat == cur_stat: + continue + cur_stat = stat + if stat not in stations: + stations.append(stat) + else: + warnings.warn('station {0} listed at least twice, might corrupt ' + 'phase times', RuntimeWarning) + + return stations + + +def convert_pilot_times(time_array): + times = [int(time) for time in time_array] + microseconds = int((time_array[-1] - times[-1]) * 1e6) + times.append(microseconds) + return UTCDateTime(*times) + + +def picksdict_from_obs(fn): + picks = dict() + station_name = str() + for line in open(fn, 'r'): + if line.startswith('#'): + continue + else: + phase_line = line.split() + if not station_name == phase_line[0]: + phase = dict() + station_name = phase_line[0] + phase_name = phase_line[4].upper() + pick = UTCDateTime(phase_line[6] + phase_line[7] + phase_line[8]) + phase[phase_name] = dict(mpp=pick, fm=phase_line[5]) + picks[station_name] = phase + return picks + + +def picksdict_from_picks(evt): + """ + Takes an Event object and return the pick dictionary commonly used within + PyLoT + :param evt: Event object contain all available information + :type evt: `~obspy.core.event.Event` + :return: pick dictionary + """ + picks = {} + for pick in evt.picks: + phase = {} + station = pick.waveform_id.station_code + try: + onsets = picks[station] + except KeyError as e: + #print(e) + onsets = {} + mpp = pick.time + spe = pick.time_errors.uncertainty + try: + lpp = mpp + pick.time_errors.upper_uncertainty + epp = mpp - pick.time_errors.lower_uncertainty + except TypeError as e: + msg = e.message + ',\n falling back to symmetric uncertainties' + warnings.warn(msg) + lpp = mpp + spe + epp = mpp - spe + phase['mpp'] = mpp + phase['epp'] = epp + phase['lpp'] = lpp + phase['spe'] = spe + try: + picker = str(pick.method_id) + if picker.startswith('smi:local/'): + picker = picker.split('smi:local/')[1] + phase['picker'] = picker + except IndexError: + pass + + onsets[pick.phase_hint] = phase.copy() + picks[station] = onsets.copy() + return picks + +def picks_from_picksdict(picks, creation_info=None): + picks_list = list() + for station, onsets in picks.items(): + for label, phase in onsets.items(): + if not isinstance(phase, dict): + continue + onset = phase['mpp'] + pick = ope.Pick() + if creation_info: + pick.creation_info = creation_info + pick.time = onset + error = phase['spe'] + pick.time_errors.uncertainty = error + try: + epp = phase['epp'] + lpp = phase['lpp'] + pick.time_errors.lower_uncertainty = onset - epp + pick.time_errors.upper_uncertainty = lpp - onset + except KeyError as e: + warnings.warn(e.message, RuntimeWarning) + try: + picker = phase['picker'] + except KeyError as e: + warnings.warn(e.message, RuntimeWarning) + picker = 'Unknown' + pick.phase_hint = label + pick.method_id = ope.ResourceIdentifier(id=picker) + pick.waveform_id = ope.WaveformStreamID(station_code=station) + try: + polarity = phase['fm'] + if polarity == 'U' or '+': + pick.polarity = 'positive' + elif polarity == 'D' or '-': + pick.polarity = 'negative' + else: + pick.polarity = 'undecidable' + except KeyError as e: + if 'fm' in e.message: # no polarity information found for this phase + pass + else: + raise e + picks_list.append(pick) + return picks_list + + +def reassess_pilot_db(root_dir, db_dir, out_dir=None, fn_param=None, verbosity=0): + import glob + + db_root = os.path.join(root_dir, db_dir) + evt_list = glob.glob1(db_root,'e????.???.??') + + for evt in evt_list: + if verbosity > 0: + print('Reassessing event {0}'.format(evt)) + reassess_pilot_event(root_dir, db_dir, evt, out_dir, fn_param, verbosity) + + + +def reassess_pilot_event(root_dir, db_dir, event_id, out_dir=None, fn_param=None, verbosity=0): + from obspy import read + + from pylot.core.io.inputs import AutoPickParameter + from pylot.core.pick.utils import earllatepicker + + if fn_param is None: + import pylot.core.util.defaults as defaults + fn_param = defaults.AUTOMATIC_DEFAULTS + + default = AutoPickParameter(fn_param, verbosity) + + search_base = os.path.join(root_dir, db_dir, event_id) + phases_file = glob.glob(os.path.join(search_base, 'PHASES.mat')) + if not phases_file: + return + if verbosity > 1: + print('Opening PILOT phases file: {fn}'.format(fn=phases_file[0])) + picks_dict = picksdict_from_pilot(phases_file[0]) + if verbosity > 0: + print('Dictionary read from PHASES.mat:\n{0}'.format(picks_dict)) + datacheck = list() + info = None + for station in picks_dict.keys(): + fn_pattern = os.path.join(search_base, '{0}*'.format(station)) + try: + st = read(fn_pattern) + except TypeError as e: + if 'Unknown format for file' in e.message: + try: + st = read(fn_pattern, format='GSE2') + except ValueError as e: + if e.message == 'second must be in 0..59': + info = 'A known Error was raised. Please find the list of corrupted files and double-check these files.' + datacheck.append(fn_pattern + ' (time info)\n') + continue + else: + raise ValueError(e.message) + except Exception as e: + if 'No file matching file pattern:' in e.message: + if verbosity > 0: + warnings.warn('no waveform data found for station {station}'.format(station=station), RuntimeWarning) + datacheck.append(fn_pattern + ' (no data)\n') + continue + else: + raise e + else: + raise e + for phase in picks_dict[station].keys(): + try: + mpp = picks_dict[station][phase]['mpp'] + except KeyError as e: + print(e.message, station) + continue + sel_st = select_for_phase(st, phase) + if not sel_st: + msg = 'no waveform data found for station {station}'.format(station=station) + warnings.warn(msg, RuntimeWarning) + continue + stime, etime = full_range(sel_st) + rel_pick = mpp - stime + epp, lpp, spe = earllatepicker(sel_st, + default.get('nfac{0}'.format(phase)), + default.get('tsnrz' if phase == 'P' else 'tsnrh'), + Pick1=rel_pick, + iplot=None, + stealth_mode=True) + if epp is None or lpp is None: + continue + epp = stime + epp + lpp = stime + lpp + min_diff = 3 * st[0].stats.delta + if lpp - mpp < min_diff: + lpp = mpp + min_diff + if mpp - epp < min_diff: + epp = mpp - min_diff + picks_dict[station][phase] = dict(epp=epp, mpp=mpp, lpp=lpp, spe=spe) + if datacheck: + if info: + if verbosity > 0: + print(info + ': {0}'.format(search_base)) + fncheck = open(os.path.join(search_base, 'datacheck_list'), 'w') + fncheck.writelines(datacheck) + fncheck.close() + del datacheck + # create Event object for export + evt = ope.Event(resource_id=event_id) + evt.picks = picks_from_picksdict(picks_dict) + # write phase information to file + if not out_dir: + fnout_prefix = os.path.join(root_dir, db_dir, event_id, '{0}.'.format(event_id)) + else: + out_dir = os.path.join(out_dir, db_dir) + if not os.path.isdir(out_dir): + os.makedirs(out_dir) + fnout_prefix = os.path.join(out_dir, '{0}.'.format(event_id)) + evt.write(fnout_prefix + 'xml', format='QUAKEML') + #evt.write(fnout_prefix + 'cnv', format='VELEST') + + +def writephases(arrivals, fformat, filename): + """ + Function of methods to write phases to the following standard file + formats used for locating earthquakes: + + HYPO71, NLLoc, VELEST, HYPOSAT, and hypoDD + + :param: arrivals + :type: dictionary containing all phase information including + station ID, phase, first motion, weight (uncertainty), + .... + + :param: fformat + :type: string, chosen file format (location routine), + choose between NLLoc, HYPO71, HYPOSAT, VELEST, + HYPOINVERSE, and hypoDD + + :param: filename, full path and name of phase file + :type: string + """ + + if fformat == 'NLLoc': + print ("Writing phases to %s for NLLoc" % filename) + fid = open("%s" % filename, 'w') + # write header + fid.write('# EQEVENT: Label: EQ001 Loc: X 0.00 Y 0.00 Z 10.00 OT 0.00 \n') + for key in arrivals: + # P onsets + if arrivals[key]['P']: + try: + fm = arrivals[key]['P']['fm'] + except KeyError as e: + print(e) + fm = None + if fm == None: + fm = '?' + onset = arrivals[key]['P']['mpp'] + year = onset.year + month = onset.month + day = onset.day + hh = onset.hour + mm = onset.minute + ss = onset.second + ms = onset.microsecond + ss_ms = ss + ms / 1000000.0 + pweight = 1 # use pick + try: + if arrivals[key]['P']['weight'] >= 4: + 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, + fm, + year, + month, + day, + hh, + mm, + ss_ms, + pweight)) + # S onsets + if arrivals[key].has_key('S') and arrivals[key]['S']: + fm = '?' + onset = arrivals[key]['S']['mpp'] + year = onset.year + month = onset.month + day = onset.day + hh = onset.hour + mm = onset.minute + ss = onset.second + ms = onset.microsecond + ss_ms = ss + ms / 1000000.0 + sweight = 1 # use pick + try: + if arrivals[key]['S']['weight'] >= 4: + 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, + fm, + year, + month, + day, + hh, + mm, + ss_ms, + sweight)) + + fid.close() + elif fformat == 'HYPO71': + print ("Writing phases to %s for HYPO71" % filename) + fid = open("%s" % filename, 'w') + # write header + fid.write(' EQ001\n') + for key in arrivals: + if arrivals[key]['P']['weight'] < 4: + Ponset = arrivals[key]['P']['mpp'] + Sonset = arrivals[key]['S']['mpp'] + pweight = arrivals[key]['P']['weight'] + sweight = arrivals[key]['S']['weight'] + fm = arrivals[key]['P']['fm'] + if fm is None: + fm = '-' + Ao = arrivals[key]['S']['Ao'] + if Ao is None: + Ao = '' + else: + Ao = str('%7.2f' % Ao) + year = Ponset.year + if year >= 2000: + year = year - 2000 + else: + year = year - 1900 + month = Ponset.month + day = Ponset.day + hh = Ponset.hour + mm = Ponset.minute + ss = Ponset.second + ms = Ponset.microsecond + ss_ms = ss + ms / 1000000.0 + if pweight < 2: + pstr = 'I' + elif pweight >= 2: + pstr = 'E' + if arrivals[key]['S']['weight'] < 4: + Sss = Sonset.second + Sms = Sonset.microsecond + Sss_ms = Sss + Sms / 1000000.0 + Sss_ms = str('%5.02f' % Sss_ms) + if sweight < 2: + sstr = 'I' + elif sweight >= 2: + sstr = 'E' + fid.write('%s%sP%s%d %02d%02d%02d%02d%02d%5.2f %s%sS %d %s\n' % (key, + pstr, + fm, + pweight, + year, + month, + day, + hh, + mm, + ss_ms, + Sss_ms, + sstr, + sweight, + Ao)) + else: + fid.write('%s%sP%s%d %02d%02d%02d%02d%02d%5.2f %s\n' % (key, + pstr, + fm, + pweight, + year, + month, + day, + hh, + mm, + ss_ms, + Ao)) + + fid.close() + + +def merge_picks(event, picks): + """ + takes an event object and a list of picks and searches for matching + entries by comparing station name and phase_hint and overwrites the time + and time_errors value of the event picks' with those from the picks + without changing the resource identifiers + :param event: `obspy.core.event.Event` object (e.g. from NLLoc output) + :param picks: list of `obspy.core.event.Pick` objects containing the + original time and time_errors values + :return: merged `obspy.core.event.Event` object + """ + for pick in picks: + time = pick.time + err = pick.time_errors + phase = pick.phase_hint + station = pick.waveform_id.station_code + for p in event.picks: + if p.waveform_id.station_code == station and p.phase_hint == phase: + p.time, p.time_errors = time, err + del time, err, phase, station + return event diff --git a/pylot/core/loc/__init__.py b/pylot/core/loc/__init__.py new file mode 100644 index 00000000..faa18be5 --- /dev/null +++ b/pylot/core/loc/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/pylot/core/loc/hsat.py b/pylot/core/loc/hsat.py new file mode 100644 index 00000000..b191f21b --- /dev/null +++ b/pylot/core/loc/hsat.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- 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 dictionary and exports picking data to a NLLOC-obs + 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) diff --git a/pylot/core/loc/nll.py b/pylot/core/loc/nll.py new file mode 100644 index 00000000..f9b36e76 --- /dev/null +++ b/pylot/core/loc/nll.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import subprocess +import os +import glob +from obspy import read_events +from pylot.core.io.phases import writephases +from pylot.core.util.utils import getPatternLine, runProgram, which +from pylot.core.util.version import get_git_version as _getVersionString + +__version__ = _getVersionString() + +class NLLocError(EnvironmentError): + pass + +def export(picks, fnout): + ''' + Take dictionary and exports picking data to a NLLOC-obs + 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, 'NLLoc', fnout) + + +def modify_inputs(ctrfn, root, nllocoutn, phasefn, tttn): + ''' + :param ctrfn: name of NLLoc-control file + :type: str + + :param root: root path to NLLoc working directory + :type: str + + :param nllocoutn: name of NLLoc-location output file + :type: str + + :param phasefn: name of NLLoc-input phase file + :type: str + + :param tttn: pattern of precalculated NLLoc traveltime tables + :type: str + ''' + # For locating the event the NLLoc-control file has to be modified! + # create comment line for NLLoc-control file NLLoc-output file + ctrfile = os.path.join(root, 'run', ctrfn) + nllocout = os.path.join(root, 'loc', nllocoutn) + phasefile = os.path.join(root, 'obs', phasefn) + tttable = os.path.join(root, 'time', tttn) + locfiles = 'LOCFILES %s NLLOC_OBS %s %s 0\n' % (phasefile, tttable, nllocout) + + # modification of NLLoc-control file + print ("Modifying NLLoc-control file %s ..." % ctrfile) + curlocfiles = getPatternLine(ctrfile, 'LOCFILES') + nllfile = open(ctrfile, 'r') + filedata = nllfile.read() + if filedata.find(locfiles) < 0: + # replace old command + filedata = filedata.replace(curlocfiles, locfiles) + nllfile = open(ctrfile, 'w') + nllfile.write(filedata) + nllfile.close() + + +def locate(fnin): + """ + takes an external program name + :param fnin: + :return: + """ + + exe_path = which('NLLoc') + if exe_path is None: + raise NLLocError('NonLinLoc executable not found; check your ' + 'environment variables') + + # locate the event utilizing external NonLinLoc installation + try: + runProgram(exe_path, fnin) + except subprocess.CalledProcessError as e: + raise RuntimeError(e.output) + + +def read_location(fn): + 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__': + pass diff --git a/pylot/core/loc/velest.py b/pylot/core/loc/velest.py new file mode 100644 index 00000000..faa18be5 --- /dev/null +++ b/pylot/core/loc/velest.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/pylot/core/pick/__init__.py b/pylot/core/pick/__init__.py new file mode 100644 index 00000000..ec51c5a2 --- /dev/null +++ b/pylot/core/pick/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/pylot/core/pick/autopick.py b/pylot/core/pick/autopick.py new file mode 100755 index 00000000..35111071 --- /dev/null +++ b/pylot/core/pick/autopick.py @@ -0,0 +1,882 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Function to run automated picking algorithms using AIC, +HOS and AR prediction. Uses objects CharFuns and Picker and +function conglomerate utils. + +:author: MAGS2 EP3 working group / Ludger Kueperkoch +""" + +import matplotlib.pyplot as plt +import numpy as np +from pylot.core.io.inputs import AutoPickParameter +from pylot.core.pick.picker import AICPicker, PragPicker +from pylot.core.pick.charfuns import CharacteristicFunction +from pylot.core.pick.charfuns import HOScf, AICcf, ARZcf, ARHcf, AR3Ccf +from pylot.core.pick.utils import checksignallength, checkZ4S, earllatepicker, \ + getSNR, fmpicker, checkPonsets, wadaticheck +from pylot.core.util.utils import getPatternLine +from pylot.core.io.data import Data + + +def autopickevent(data, param): + stations = [] + all_onsets = {} + + # get some parameters for quality control from + # parameter input file (usually autoPyLoT.in). + wdttolerance = param.get('wdttolerance') + mdttolerance = param.get('mdttolerance') + iplot = param.get('iplot') + apverbose = param.get('apverbose') + for n in range(len(data)): + station = data[n].stats.station + if station not in stations: + stations.append(station) + else: + continue + + for station in stations: + topick = data.select(station=station) + all_onsets[station] = autopickstation(topick, param, verbose=apverbose) + + # quality control + # median check and jackknife on P-onset times + jk_checked_onsets = checkPonsets(all_onsets, mdttolerance, iplot) + # check S-P times (Wadati) + return wadaticheck(jk_checked_onsets, wdttolerance, iplot) + + +def autopickstation(wfstream, pickparam, verbose=False): + """ + :param wfstream: `~obspy.core.stream.Stream` containing waveform + :type wfstream: obspy.core.stream.Stream + + :param pickparam: container of picking parameters from input file, + usually autoPyLoT.in + :type pickparam: AutoPickParameter + :param verbose: + :type verbose: bool + + """ + + # declaring pickparam variables (only for convenience) + # read your autoPyLoT.in for details! + + # special parameters for P picking + algoP = pickparam.get('algoP') + iplot = pickparam.get('iplot') + pstart = pickparam.get('pstart') + pstop = pickparam.get('pstop') + thosmw = pickparam.get('tlta') + tsnrz = pickparam.get('tsnrz') + hosorder = pickparam.get('hosorder') + bpz1 = pickparam.get('bpz1') + bpz2 = pickparam.get('bpz2') + pickwinP = pickparam.get('pickwinP') + tsmoothP = pickparam.get('tsmoothP') + ausP = pickparam.get('ausP') + nfacP = pickparam.get('nfacP') + tpred1z = pickparam.get('tpred1z') + tdet1z = pickparam.get('tdet1z') + Parorder = pickparam.get('Parorder') + addnoise = pickparam.get('addnoise') + Precalcwin = pickparam.get('Precalcwin') + minAICPslope = pickparam.get('minAICPslope') + minAICPSNR = pickparam.get('minAICPSNR') + timeerrorsP = pickparam.get('timeerrorsP') + # special parameters for S picking + algoS = pickparam.get('algoS') + sstart = pickparam.get('sstart') + sstop = pickparam.get('sstop') + bph1 = pickparam.get('bph1') + bph2 = pickparam.get('bph2') + tsnrh = pickparam.get('tsnrh') + pickwinS = pickparam.get('pickwinS') + tpred1h = pickparam.get('tpred1h') + tdet1h = pickparam.get('tdet1h') + tpred2h = pickparam.get('tpred2h') + tdet2h = pickparam.get('tdet2h') + Sarorder = pickparam.get('Sarorder') + aictsmoothS = pickparam.get('aictsmoothS') + tsmoothS = pickparam.get('tsmoothS') + ausS = pickparam.get('ausS') + minAICSslope = pickparam.get('minAICSslope') + minAICSSNR = pickparam.get('minAICSSNR') + Srecalcwin = pickparam.get('Srecalcwin') + nfacS = pickparam.get('nfacS') + timeerrorsS = pickparam.get('timeerrorsS') + # parameters for first-motion determination + minFMSNR = pickparam.get('minFMSNR') + fmpickwin = pickparam.get('fmpickwin') + minfmweight = pickparam.get('minfmweight') + # parameters for checking signal length + minsiglength = pickparam.get('minsiglength') + minpercent = pickparam.get('minpercent') + nfacsl = pickparam.get('noisefactor') + # parameter to check for spuriously picked S onset + zfac = pickparam.get('zfac') + # path to inventory-, dataless- or resp-files + + # initialize output + Pweight = 4 # weight for P onset + Sweight = 4 # weight for S onset + FM = 'N' # first motion (polarity) + SNRP = None # signal-to-noise ratio of P onset + SNRPdB = None # signal-to-noise ratio of P onset [dB] + SNRS = None # signal-to-noise ratio of S onset + SNRSdB = None # signal-to-noise ratio of S onset [dB] + mpickP = None # most likely P onset + lpickP = None # latest possible P onset + epickP = None # earliest possible P onset + mpickS = None # most likely S onset + lpickS = None # latest possible S onset + epickS = None # earliest possible S onset + Perror = None # symmetrized picking error P onset + Serror = None # symmetrized picking error S onset + + aicSflag = 0 + aicPflag = 0 + Pflag = 0 + Sflag = 0 + Pmarker = [] + Ao = None # Wood-Anderson peak-to-peak amplitude + picker = 'autoPyLoT' # name of the picking programm + + # split components + zdat = wfstream.select(component="Z") + if len(zdat) == 0: # check for other components + zdat = wfstream.select(component="3") + edat = wfstream.select(component="E") + if len(edat) == 0: # check for other components + edat = wfstream.select(component="2") + ndat = wfstream.select(component="N") + if len(ndat) == 0: # check for other components + ndat = wfstream.select(component="1") + + if algoP == 'HOS' or algoP == 'ARZ' and zdat is not None: + msg = '##########################################\nautopickstation:' \ + ' Working on P onset of station {station}\nFiltering vertical ' \ + 'trace ...\n{data}'.format(station=zdat[0].stats.station, + data=str(zdat)) + if verbose: print(msg) + z_copy = zdat.copy() + # filter and taper data + tr_filt = zdat[0].copy() + tr_filt.filter('bandpass', freqmin=bpz1[0], freqmax=bpz1[1], + zerophase=False) + tr_filt.taper(max_percentage=0.05, type='hann') + z_copy[0].data = tr_filt.data + ############################################################## + # check length of waveform and compare with cut times + Lc = pstop - pstart + Lwf = zdat[0].stats.endtime - zdat[0].stats.starttime + Ldiff = Lwf - Lc + if Ldiff < 0: + msg = 'autopickstation: Cutting times are too large for actual ' \ + 'waveform!\nUsing entire waveform instead!' + if verbose: print(msg) + pstart = 0 + pstop = len(zdat[0].data) * zdat[0].stats.delta + cuttimes = [pstart, pstop] + cf1 = None + if algoP == 'HOS': + # calculate HOS-CF using subclass HOScf of class + # CharacteristicFunction + cf1 = HOScf(z_copy, cuttimes, thosmw, hosorder) # instance of HOScf + elif algoP == 'ARZ': + # calculate ARZ-CF using subclass ARZcf of class + # CharcteristicFunction + cf1 = ARZcf(z_copy, cuttimes, tpred1z, Parorder, tdet1z, + addnoise) # instance of ARZcf + ############################################################## + # calculate AIC-HOS-CF using subclass AICcf of class + # CharacteristicFunction + # class needs stream object => build it + assert isinstance(cf1, CharacteristicFunction), 'cf2 is not set ' \ + 'correctly: maybe the algorithm name ({algoP}) is ' \ + 'corrupted'.format( + algoP=algoP) + tr_aic = tr_filt.copy() + tr_aic.data = cf1.getCF() + z_copy[0].data = tr_aic.data + aiccf = AICcf(z_copy, cuttimes) # instance of AICcf + ############################################################## + # get prelimenary onset time from AIC-HOS-CF using subclass AICPicker + # of class AutoPicking + aicpick = AICPicker(aiccf, tsnrz, pickwinP, iplot, None, tsmoothP) + ############################################################## + if aicpick.getpick() is not None: + # check signal length to detect spuriously picked noise peaks + # use all available components to avoid skipping correct picks + # on vertical traces with weak P coda + z_copy[0].data = tr_filt.data + zne = z_copy + if len(ndat) == 0 or len(edat) == 0: + msg = 'One or more horizontal component(s) missing!\nSignal ' \ + 'length only checked on vertical component!\n' \ + 'Decreasing minsiglengh from {0} to ' \ + '{1}'.format(minsiglength, minsiglength / 2) + if verbose: print(msg) + Pflag = checksignallength(zne, aicpick.getpick(), tsnrz, + minsiglength / 2, + nfacsl, minpercent, iplot) + else: + # filter and taper horizontal traces + trH1_filt = edat.copy() + trH2_filt = ndat.copy() + trH1_filt.filter('bandpass', freqmin=bph1[0], + freqmax=bph1[1], + zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph1[0], + freqmax=bph1[1], + zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + zne += trH1_filt + zne += trH2_filt + Pflag = checksignallength(zne, aicpick.getpick(), tsnrz, + minsiglength, + nfacsl, minpercent, iplot) + + if Pflag == 1: + # check for spuriously picked S onset + # both horizontal traces needed + if len(ndat) == 0 or len(edat) == 0: + msg = 'One or more horizontal components missing!\n' \ + 'Skipping control function checkZ4S.' + if verbose: print(msg) + else: + Pflag = checkZ4S(zne, aicpick.getpick(), zfac, + tsnrz[3], iplot) + if Pflag == 0: + Pmarker = 'SinsteadP' + Pweight = 9 + else: + Pmarker = 'shortsignallength' + Pweight = 9 + ############################################################## + # go on with processing if AIC onset passes quality control + if (aicpick.getSlope() >= minAICPslope and + aicpick.getSNR() >= minAICPSNR and Pflag == 1): + aicPflag = 1 + msg = 'AIC P-pick passes quality control: Slope: {0} counts/s, ' \ + 'SNR: {1}\nGo on with refined picking ...\n' \ + 'autopickstation: re-filtering vertical trace ' \ + '...'.format(aicpick.getSlope(), aicpick.getSNR()) + if verbose: print(msg) + # re-filter waveform with larger bandpass + z_copy = zdat.copy() + tr_filt = zdat[0].copy() + tr_filt.filter('bandpass', freqmin=bpz2[0], freqmax=bpz2[1], + zerophase=False) + tr_filt.taper(max_percentage=0.05, type='hann') + z_copy[0].data = tr_filt.data + ############################################################# + # re-calculate CF from re-filtered trace in vicinity of initial + # onset + cuttimes2 = [round(max([aicpick.getpick() - Precalcwin, 0])), + round(min([len(zdat[0].data) * zdat[0].stats.delta, + aicpick.getpick() + Precalcwin]))] + cf2 = None + if algoP == 'HOS': + # calculate HOS-CF using subclass HOScf of class + # CharacteristicFunction + cf2 = HOScf(z_copy, cuttimes2, thosmw, + hosorder) # instance of HOScf + elif algoP == 'ARZ': + # calculate ARZ-CF using subclass ARZcf of class + # CharcteristicFunction + cf2 = ARZcf(z_copy, cuttimes2, tpred1z, Parorder, tdet1z, + addnoise) # instance of ARZcf + ############################################################## + # get refined onset time from CF2 using class Picker + assert isinstance(cf2, CharacteristicFunction), 'cf2 is not set ' \ + 'correctly: maybe the algorithm name ({algoP}) is ' \ + 'corrupted'.format( + algoP=algoP) + refPpick = PragPicker(cf2, tsnrz, pickwinP, iplot, ausP, tsmoothP, + aicpick.getpick()) + mpickP = refPpick.getpick() + ############################################################# + if mpickP is not None: + # quality assessment + # get earliest/latest possible pick and symmetrized uncertainty + [epickP, lpickP, Perror] = earllatepicker(z_copy, nfacP, tsnrz, + mpickP, iplot) + + # get SNR + [SNRP, SNRPdB, Pnoiselevel] = getSNR(z_copy, tsnrz, mpickP) + + # weight P-onset using symmetric error + if Perror <= timeerrorsP[0]: + Pweight = 0 + elif timeerrorsP[0] < Perror <= timeerrorsP[1]: + Pweight = 1 + elif timeerrorsP[1] < Perror <= timeerrorsP[2]: + Pweight = 2 + elif timeerrorsP[2] < Perror <= timeerrorsP[3]: + Pweight = 3 + elif Perror > timeerrorsP[3]: + Pweight = 4 + + ############################################################## + # get first motion of P onset + # certain quality required + if Pweight <= minfmweight and SNRP >= minFMSNR: + FM = fmpicker(zdat, z_copy, fmpickwin, mpickP, iplot) + else: + FM = 'N' + + msg = "autopickstation: P-weight: {0}, " \ + "SNR: {1}, SNR[dB]: {2}, Polarity: {3}".format(Pweight, + SNRP, + SNRPdB, + FM) + print(msg) + Sflag = 1 + + else: + msg = 'Bad initial (AIC) P-pick, skipping this onset!\n' \ + 'AIC-SNR={0}, AIC-Slope={1}counts/s\n' \ + '(min. AIC-SNR={2}, ' \ + 'min. AIC-Slope={3}counts/s)'.format(aicpick.getSNR(), + aicpick.getSlope(), + minAICPSNR, + minAICPslope) + if verbose: print(msg) + Sflag = 0 + + else: + print('autopickstation: No vertical component data available!, ' + 'Skipping station!') + + if edat is not None and ndat is not None and len(edat) > 0 and len( + ndat) > 0 and Pweight < 4: + msg = 'Go on picking S onset ...\n' \ + '##################################################\n' \ + 'Working on S onset of station {0}\nFiltering horizontal ' \ + 'traces ...'.format(edat[0].stats.station) + if verbose: print(msg) + # determine time window for calculating CF after P onset + cuttimesh = [round(max([mpickP + sstart, 0])), + round(min([mpickP + sstop, Lwf]))] + + if algoS == 'ARH': + # re-create stream object including both horizontal components + hdat = edat.copy() + hdat += ndat + h_copy = hdat.copy() + # filter and taper data + trH1_filt = hdat[0].copy() + trH2_filt = hdat[1].copy() + trH1_filt.filter('bandpass', freqmin=bph1[0], freqmax=bph1[1], + zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph1[0], freqmax=bph1[1], + zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + h_copy[0].data = trH1_filt.data + h_copy[1].data = trH2_filt.data + elif algoS == 'AR3': + # re-create stream object including all components + hdat = zdat.copy() + hdat += edat + hdat += ndat + h_copy = hdat.copy() + # filter and taper data + trH1_filt = hdat[0].copy() + trH2_filt = hdat[1].copy() + trH3_filt = hdat[2].copy() + trH1_filt.filter('bandpass', freqmin=bph1[0], freqmax=bph1[1], + zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph1[0], freqmax=bph1[1], + zerophase=False) + trH3_filt.filter('bandpass', freqmin=bph1[0], freqmax=bph1[1], + zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + trH3_filt.taper(max_percentage=0.05, type='hann') + h_copy[0].data = trH1_filt.data + h_copy[1].data = trH2_filt.data + h_copy[2].data = trH3_filt.data + ############################################################## + if algoS == 'ARH': + # calculate ARH-CF using subclass ARHcf of class + # CharcteristicFunction + arhcf1 = ARHcf(h_copy, cuttimesh, tpred1h, Sarorder, tdet1h, + addnoise) # instance of ARHcf + elif algoS == 'AR3': + # calculate ARH-CF using subclass AR3cf of class + # CharcteristicFunction + arhcf1 = AR3Ccf(h_copy, cuttimesh, tpred1h, Sarorder, tdet1h, + addnoise) # instance of ARHcf + ############################################################## + # calculate AIC-ARH-CF using subclass AICcf of class + # CharacteristicFunction + # class needs stream object => build it + tr_arhaic = trH1_filt.copy() + tr_arhaic.data = arhcf1.getCF() + h_copy[0].data = tr_arhaic.data + # calculate ARH-AIC-CF + haiccf = AICcf(h_copy, cuttimesh) # instance of AICcf + ############################################################## + # get prelimenary onset time from AIC-HOS-CF using subclass AICPicker + # of class AutoPicking + aicarhpick = AICPicker(haiccf, tsnrh, pickwinS, iplot, None, + aictsmoothS) + ############################################################### + # go on with processing if AIC onset passes quality control + if (aicarhpick.getSlope() >= minAICSslope and + aicarhpick.getSNR() >= minAICSSNR and + aicarhpick.getpick() is not None): + aicSflag = 1 + msg = 'AIC S-pick passes quality control: Slope: {0} counts/s, ' \ + 'SNR: {1}\nGo on with refined picking ...\n' \ + 'autopickstation: re-filtering horizontal traces ' \ + '...'.format(aicarhpick.getSlope(), aicarhpick.getSNR()) + if verbose: print(msg) + # re-calculate CF from re-filtered trace in vicinity of initial + # onset + cuttimesh2 = [round(aicarhpick.getpick() - Srecalcwin), + round(aicarhpick.getpick() + Srecalcwin)] + # re-filter waveform with larger bandpass + h_copy = hdat.copy() + # filter and taper data + if algoS == 'ARH': + trH1_filt = hdat[0].copy() + trH2_filt = hdat[1].copy() + trH1_filt.filter('bandpass', freqmin=bph2[0], freqmax=bph2[1], + zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph2[0], freqmax=bph2[1], + zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + h_copy[0].data = trH1_filt.data + h_copy[1].data = trH2_filt.data + ############################################################# + arhcf2 = ARHcf(h_copy, cuttimesh2, tpred2h, Sarorder, tdet2h, + addnoise) # instance of ARHcf + elif algoS == 'AR3': + trH1_filt = hdat[0].copy() + trH2_filt = hdat[1].copy() + trH3_filt = hdat[2].copy() + trH1_filt.filter('bandpass', freqmin=bph2[0], freqmax=bph2[1], + zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph2[0], freqmax=bph2[1], + zerophase=False) + trH3_filt.filter('bandpass', freqmin=bph2[0], freqmax=bph2[1], + zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + trH3_filt.taper(max_percentage=0.05, type='hann') + h_copy[0].data = trH1_filt.data + h_copy[1].data = trH2_filt.data + h_copy[2].data = trH3_filt.data + ############################################################# + arhcf2 = AR3Ccf(h_copy, cuttimesh2, tpred2h, Sarorder, tdet2h, + addnoise) # instance of ARHcf + + # get refined onset time from CF2 using class Picker + refSpick = PragPicker(arhcf2, tsnrh, pickwinS, iplot, ausS, + tsmoothS, aicarhpick.getpick()) + mpickS = refSpick.getpick() + ############################################################# + if mpickS is not None: + # quality assessment + # get earliest/latest possible pick and symmetrized uncertainty + h_copy[0].data = trH1_filt.data + [epickS1, lpickS1, Serror1] = earllatepicker(h_copy, nfacS, + tsnrh, + mpickS, iplot) + + h_copy[0].data = trH2_filt.data + [epickS2, lpickS2, Serror2] = earllatepicker(h_copy, nfacS, + tsnrh, + mpickS, iplot) + if epickS1 is not None and epickS2 is not None: + if algoS == 'ARH': + # get earliest pick of both earliest possible picks + epick = [epickS1, epickS2] + lpick = [lpickS1, lpickS2] + pickerr = [Serror1, Serror2] + if epickS1 is None and epickS2 is not None: + ipick = 1 + elif epickS1 is not None and epickS2 is None: + ipick = 0 + elif epickS1 is not None and epickS2 is not None: + ipick = np.argmin([epickS1, epickS2]) + elif algoS == 'AR3': + [epickS3, lpickS3, Serror3] = earllatepicker(h_copy, + nfacS, + tsnrh, + mpickS, + iplot) + # get earliest pick of all three picks + epick = [epickS1, epickS2, epickS3] + lpick = [lpickS1, lpickS2, lpickS3] + pickerr = [Serror1, Serror2, Serror3] + if epickS1 is None and epickS2 is not None \ + and epickS3 is not None: + ipick = np.argmin([epickS2, epickS3]) + elif epickS1 is not None and epickS2 is None \ + and epickS3 is not None: + ipick = np.argmin([epickS2, epickS3]) + elif epickS1 is not None and epickS2 is not None \ + and epickS3 is None: + ipick = np.argmin([epickS1, epickS2]) + elif epickS1 is not None and epickS2 is not None \ + and epickS3 is not None: + ipick = np.argmin([epickS1, epickS2, epickS3]) + + epickS = epick[ipick] + lpickS = lpick[ipick] + Serror = pickerr[ipick] + + # get SNR + [SNRS, SNRSdB, Snoiselevel] = getSNR(h_copy, tsnrh, mpickS) + + # weight S-onset using symmetric error + if Serror <= timeerrorsS[0]: + Sweight = 0 + elif timeerrorsS[0] < Serror <= timeerrorsS[1]: + Sweight = 1 + elif Perror > timeerrorsS[1] and Serror <= timeerrorsS[2]: + Sweight = 2 + elif timeerrorsS[2] < Serror <= timeerrorsS[3]: + Sweight = 3 + elif Serror > timeerrorsS[3]: + Sweight = 4 + + print('autopickstation: S-weight: {0}, SNR: {1}, ' + 'SNR[dB]: {2}\n' + '################################################' + ''.format(Sweight, SNRS, SNRSdB)) + ################################################################ + # get Wood-Anderson peak-to-peak amplitude + # initialize Data object + data = Data() + # re-create stream object including both horizontal components + hdat = edat.copy() + hdat += ndat + else: + msg = 'Bad initial (AIC) S-pick, skipping this onset!\n' \ + 'AIC-SNR={0}, AIC-Slope={1}counts/s\n' \ + '(min. AIC-SNR={2}, ' \ + 'min. AIC-Slope={3}counts/s)\n' \ + '################################################' \ + ''.format(aicarhpick.getSNR(), + aicarhpick.getSlope(), + minAICSSNR, + minAICSslope) + if verbose: print(msg) + + ############################################################ + # get Wood-Anderson peak-to-peak amplitude + # initialize Data object + data = Data() + # re-create stream object including both horizontal components + hdat = edat.copy() + hdat += ndat + else: + print('autopickstation: No horizontal component data available or ' \ + 'bad P onset, skipping S picking!') + + ############################################################## + if iplot > 0: + # plot vertical trace + plt.figure() + plt.subplot(3, 1, 1) + tdata = np.arange(0, zdat[0].stats.npts / tr_filt.stats.sampling_rate, + tr_filt.stats.delta) + # check equal length of arrays, sometimes they are different!? + wfldiff = len(tr_filt.data) - len(tdata) + if wfldiff < 0: + tdata = tdata[0:len(tdata) - abs(wfldiff)] + p1, = plt.plot(tdata, tr_filt.data / max(tr_filt.data), 'k') + if Pweight < 4: + p2, = plt.plot(cf1.getTimeArray(), cf1.getCF() / max(cf1.getCF()), + 'b') + if aicPflag == 1: + p3, = plt.plot(cf2.getTimeArray(), + cf2.getCF() / max(cf2.getCF()), 'm') + p4, = plt.plot([aicpick.getpick(), aicpick.getpick()], [-1, 1], + 'r') + plt.plot([aicpick.getpick() - 0.5, aicpick.getpick() + 0.5], + [1, 1], 'r') + plt.plot([aicpick.getpick() - 0.5, aicpick.getpick() + 0.5], + [-1, -1], 'r') + p5, = plt.plot([refPpick.getpick(), refPpick.getpick()], + [-1.3, 1.3], 'r', linewidth=2) + plt.plot([refPpick.getpick() - 0.5, refPpick.getpick() + 0.5], + [1.3, 1.3], 'r', linewidth=2) + plt.plot([refPpick.getpick() - 0.5, refPpick.getpick() + 0.5], + [-1.3, -1.3], 'r', linewidth=2) + plt.plot([lpickP, lpickP], [-1.1, 1.1], 'r--') + plt.plot([epickP, epickP], [-1.1, 1.1], 'r--') + plt.legend([p1, p2, p3, p4, p5], + ['Data', 'CF1', 'CF2', 'Initial P Onset', + 'Final P Pick']) + plt.title('%s, %s, P Weight=%d, SNR=%7.2f, SNR[dB]=%7.2f ' + 'Polarity: %s' % (tr_filt.stats.station, + tr_filt.stats.channel, + Pweight, + SNRP, + SNRPdB, + FM)) + else: + plt.legend([p1, p2], ['Data', 'CF1']) + plt.title('%s, P Weight=%d, SNR=None, ' + 'SNRdB=None' % (tr_filt.stats.channel, Pweight)) + else: + plt.title('%s, %s, P Weight=%d' % (tr_filt.stats.station, + tr_filt.stats.channel, + Pweight)) + + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.ylabel('Normalized Counts') + plt.suptitle(tr_filt.stats.starttime) + + if len(edat[0]) > 1 and len(ndat[0]) > 1 and Sflag == 1: + # plot horizontal traces + plt.subplot(3, 1, 2) + th1data = np.arange(0, + trH1_filt.stats.npts / + trH1_filt.stats.sampling_rate, + trH1_filt.stats.delta) + # check equal length of arrays, sometimes they are different!? + wfldiff = len(trH1_filt.data) - len(th1data) + if wfldiff < 0: + th1data = th1data[0:len(th1data) - abs(wfldiff)] + p21, = plt.plot(th1data, trH1_filt.data / max(trH1_filt.data), 'k') + if Pweight < 4: + p22, = plt.plot(arhcf1.getTimeArray(), + arhcf1.getCF() / max(arhcf1.getCF()), 'b') + if aicSflag == 1: + p23, = plt.plot(arhcf2.getTimeArray(), + arhcf2.getCF() / max(arhcf2.getCF()), 'm') + p24, = plt.plot( + [aicarhpick.getpick(), aicarhpick.getpick()], + [-1, 1], 'g') + plt.plot( + [aicarhpick.getpick() - 0.5, + aicarhpick.getpick() + 0.5], + [1, 1], 'g') + plt.plot( + [aicarhpick.getpick() - 0.5, + aicarhpick.getpick() + 0.5], + [-1, -1], 'g') + p25, = plt.plot([refSpick.getpick(), refSpick.getpick()], + [-1.3, 1.3], 'g', linewidth=2) + plt.plot( + [refSpick.getpick() - 0.5, refSpick.getpick() + 0.5], + [1.3, 1.3], 'g', linewidth=2) + plt.plot( + [refSpick.getpick() - 0.5, refSpick.getpick() + 0.5], + [-1.3, -1.3], 'g', linewidth=2) + plt.plot([lpickS, lpickS], [-1.1, 1.1], 'g--') + plt.plot([epickS, epickS], [-1.1, 1.1], 'g--') + plt.legend([p21, p22, p23, p24, p25], + ['Data', 'CF1', 'CF2', 'Initial S Onset', + 'Final S Pick']) + plt.title('%s, S Weight=%d, SNR=%7.2f, SNR[dB]=%7.2f' % ( + trH1_filt.stats.channel, + Sweight, SNRS, SNRSdB)) + else: + plt.legend([p21, p22], ['Data', 'CF1']) + plt.title('%s, S Weight=%d, SNR=None, SNRdB=None' % ( + trH1_filt.stats.channel, Sweight)) + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.ylabel('Normalized Counts') + plt.suptitle(trH1_filt.stats.starttime) + + plt.subplot(3, 1, 3) + th2data = np.arange(0, + trH2_filt.stats.npts / + trH2_filt.stats.sampling_rate, + trH2_filt.stats.delta) + # check equal length of arrays, sometimes they are different!? + wfldiff = len(trH2_filt.data) - len(th2data) + if wfldiff < 0: + th2data = th2data[0:len(th2data) - abs(wfldiff)] + plt.plot(th2data, trH2_filt.data / max(trH2_filt.data), 'k') + if Pweight < 4: + p22, = plt.plot(arhcf1.getTimeArray(), + arhcf1.getCF() / max(arhcf1.getCF()), 'b') + if aicSflag == 1: + p23, = plt.plot(arhcf2.getTimeArray(), + arhcf2.getCF() / max(arhcf2.getCF()), 'm') + p24, = plt.plot( + [aicarhpick.getpick(), aicarhpick.getpick()], + [-1, 1], 'g') + plt.plot( + [aicarhpick.getpick() - 0.5, + aicarhpick.getpick() + 0.5], + [1, 1], 'g') + plt.plot( + [aicarhpick.getpick() - 0.5, + aicarhpick.getpick() + 0.5], + [-1, -1], 'g') + p25, = plt.plot([refSpick.getpick(), refSpick.getpick()], + [-1.3, 1.3], 'g', linewidth=2) + plt.plot( + [refSpick.getpick() - 0.5, refSpick.getpick() + 0.5], + [1.3, 1.3], 'g', linewidth=2) + plt.plot( + [refSpick.getpick() - 0.5, refSpick.getpick() + 0.5], + [-1.3, -1.3], 'g', linewidth=2) + plt.plot([lpickS, lpickS], [-1.1, 1.1], 'g--') + plt.plot([epickS, epickS], [-1.1, 1.1], 'g--') + plt.legend([p21, p22, p23, p24, p25], + ['Data', 'CF1', 'CF2', 'Initial S Onset', + 'Final S Pick']) + else: + plt.legend([p21, p22], ['Data', 'CF1']) + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.xlabel('Time [s] after %s' % tr_filt.stats.starttime) + plt.ylabel('Normalized Counts') + plt.title(trH2_filt.stats.channel) + plt.show() + raw_input() + plt.close() + ########################################################################## + # calculate "real" onset times + if lpickP is not None and lpickP == mpickP: + lpickP += timeerrorsP[0] + if epickP is not None and epickP == mpickP: + epickP -= timeerrorsP[0] + if mpickP is not None and epickP is not None and lpickP is not None: + lpickP = zdat[0].stats.starttime + lpickP + epickP = zdat[0].stats.starttime + epickP + mpickP = zdat[0].stats.starttime + mpickP + else: + # dummy values (start of seismic trace) in order to derive + # theoretical onset times for iteratve picking + lpickP = zdat[0].stats.starttime + timeerrorsP[3] + epickP = zdat[0].stats.starttime - timeerrorsP[3] + mpickP = zdat[0].stats.starttime + + if lpickS is not None and lpickS == mpickS: + lpickS += timeerrorsS[0] + if epickS is not None and epickS == mpickS: + epickS -= timeerrorsS[0] + if mpickS is not None and epickS is not None and lpickS is not None: + lpickS = edat[0].stats.starttime + lpickS + epickS = edat[0].stats.starttime + epickS + mpickS = edat[0].stats.starttime + mpickS + else: + # dummy values (start of seismic trace) in order to derive + # theoretical onset times for iteratve picking + lpickS = edat[0].stats.starttime + timeerrorsS[3] + epickS = edat[0].stats.starttime - timeerrorsS[3] + mpickS = edat[0].stats.starttime + + # create dictionary + # for P phase + ppick = dict(lpp=lpickP, epp=epickP, mpp=mpickP, spe=Perror, snr=SNRP, + snrdb=SNRPdB, weight=Pweight, fm=FM, w0=None, fc=None, Mo=None, + Mw=None, picker=picker, marked=Pmarker) + # add S phase + spick = dict(lpp=lpickS, epp=epickS, mpp=mpickS, spe=Serror, snr=SNRS, + snrdb=SNRSdB, weight=Sweight, fm=None, picker=picker, Ao=Ao) + # merge picks into returning dictionary + picks = dict(P=ppick, S=spick) + return picks + + +def iteratepicker(wf, NLLocfile, picks, badpicks, pickparameter): + ''' + Repicking of bad onsets. Uses theoretical onset times from NLLoc-location file. + + :param wf: waveform, obspy stream object + + :param NLLocfile: path/name of NLLoc-location file + + :param picks: dictionary of available onset times + + :param badpicks: picks to be repicked + + :param pickparameter: picking parameters from autoPyLoT-input file + ''' + + msg = '#######################################################\n' \ + 'autoPyLoT: Found {0} bad onsets at station(s) {1}, ' \ + 'starting re-picking them ...'.format(len(badpicks), badpicks) + print(msg) + + newpicks = {} + for i in range(0, len(badpicks)): + if len(badpicks[i][0]) > 4: + Ppattern = '%s ? ? ? P' % badpicks[i][0] + elif len(badpicks[i][0]) == 4: + Ppattern = '%s ? ? ? P' % badpicks[i][0] + elif len(badpicks[i][0]) < 4: + Ppattern = '%s ? ? ? P' % badpicks[i][0] + nllocline = getPatternLine(NLLocfile, Ppattern) + res = nllocline.split(None)[16] + # get theoretical P-onset time from residuum + badpicks[i][1] = picks[badpicks[i][0]]['P']['mpp'] - float(res) + + # get corresponding waveform stream + msg = '#######################################################\n' \ + 'iteratepicker: Re-picking station {0}'.format(badpicks[i][0]) + print(msg) + wf2pick = wf.select(station=badpicks[i][0]) + + # modify some picking parameters + pstart_old = pickparameter.get('pstart') + pstop_old = pickparameter.get('pstop') + sstop_old = pickparameter.get('sstop') + pickwinP_old = pickparameter.get('pickwinP') + Precalcwin_old = pickparameter.get('Precalcwin') + noisefactor_old = pickparameter.get('noisefactor') + zfac_old = pickparameter.get('zfac') + pickparameter.setParam( + pstart=max([0, badpicks[i][1] - wf2pick[0].stats.starttime \ + - pickparameter.get('tlta')])) + pickparameter.setParam(pstop=pickparameter.get('pstart') + \ + (3 * pickparameter.get('tlta'))) + pickparameter.setParam(sstop=pickparameter.get('sstop') / 2) + pickparameter.setParam(pickwinP=pickparameter.get('pickwinP') / 2) + pickparameter.setParam( + Precalcwin=pickparameter.get('Precalcwin') / 2) + pickparameter.setParam(noisefactor=1.0) + pickparameter.setParam(zfac=1.0) + print( + "iteratepicker: The following picking parameters have been modified for iterative picking:") + print( + "pstart: %fs => %fs" % (pstart_old, pickparameter.get('pstart'))) + print( + "pstop: %fs => %fs" % (pstop_old, pickparameter.get('pstop'))) + print( + "sstop: %fs => %fs" % (sstop_old, pickparameter.get('sstop'))) + print("pickwinP: %fs => %fs" % ( + pickwinP_old, pickparameter.get('pickwinP'))) + print("Precalcwin: %fs => %fs" % ( + Precalcwin_old, pickparameter.get('Precalcwin'))) + print("noisefactor: %f => %f" % ( + noisefactor_old, pickparameter.get('noisefactor'))) + print("zfac: %f => %f" % (zfac_old, pickparameter.get('zfac'))) + + # repick station + newpicks = autopickstation(wf2pick, pickparameter) + + # replace old dictionary with new one + picks[badpicks[i][0]] = newpicks + + # reset temporary change of picking parameters + print("iteratepicker: Resetting picking parameters ...") + pickparameter.setParam(pstart=pstart_old) + pickparameter.setParam(pstop=pstop_old) + pickparameter.setParam(sstop=sstop_old) + pickparameter.setParam(pickwinP=pickwinP_old) + pickparameter.setParam(Precalcwin=Precalcwin_old) + pickparameter.setParam(noisefactor=noisefactor_old) + pickparameter.setParam(zfac=zfac_old) + + return picks diff --git a/pylot/core/pick/charfuns.py b/pylot/core/pick/charfuns.py new file mode 100644 index 00000000..cb3ad543 --- /dev/null +++ b/pylot/core/pick/charfuns.py @@ -0,0 +1,710 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created Oct/Nov 2014 + +Implementation of the Characteristic Functions (CF) published and described in: + +Kueperkoch, L., Meier, T., Lee, J., Friederich, W., & EGELADOS Working Group, 2010: +Automated determination of P-phase arrival times at regional and local distances +using higher order statistics, Geophys. J. Int., 181, 1159-1170 + +Kueperkoch, L., Meier, T., Bruestle, A., Lee, J., Friederich, W., & EGELADOS +Working Group, 2012: Automated determination of S-phase arrival times using +autoregressive prediction: application ot local and regional distances, Geophys. J. Int., +188, 687-702. + +:author: MAGS2 EP3 working group +""" + +import matplotlib.pyplot as plt +import numpy as np +from obspy.core import Stream + + +class CharacteristicFunction(object): + ''' + SuperClass for different types of characteristic functions. + ''' + + def __init__(self, data, cut, t2=None, order=None, t1=None, fnoise=None, stealthMode=False): + ''' + Initialize data type object with information from the original + Seismogram. + + :param: data + :type: `~obspy.core.stream.Stream` + + :param: cut + :type: tuple + + :param: t2 + :type: float + + :param: order + :type: int + + :param: t1 + :type: float (optional, only for AR) + + :param: fnoise + :type: float (optional, only for AR) + ''' + + assert isinstance(data, Stream), "%s is not a stream object" % str(data) + + self.orig_data = data + self.dt = self.orig_data[0].stats.delta + self.setCut(cut) + self.setTime1(t1) + self.setTime2(t2) + self.setOrder(order) + self.setFnoise(fnoise) + self.setARdetStep(t2) + self.calcCF(self.getDataArray()) + self.arpara = np.array([]) + self.xpred = np.array([]) + self._stealthMode = stealthMode + + def __str__(self): + return '''\n\t{name} object:\n + Cut:\t\t{cut}\n + t1:\t{t1}\n + t2:\t{t2}\n + Order:\t\t{order}\n + Fnoise:\t{fnoise}\n + ARdetStep:\t{ardetstep}\n + '''.format(name=type(self).__name__, + cut=self.getCut(), + t1=self.getTime1(), + t2=self.getTime2(), + order=self.getOrder(), + fnoise=self.getFnoise(), + ardetstep=self.getARdetStep[0]()) + + def getCut(self): + return self.cut + + def setCut(self, cut): + self.cut = cut + + def getTime1(self): + return self.t1 + + def setTime1(self, t1): + self.t1 = t1 + + def getTime2(self): + return self.t2 + + def setTime2(self, t2): + self.t2 = t2 + + def getARdetStep(self): + return self.ARdetStep + + def setARdetStep(self, t1): + if t1: + self.ARdetStep = [] + self.ARdetStep.append(t1 / 4) + self.ARdetStep.append(int(np.ceil(self.getTime2() / self.getIncrement()) / 4)) + + def getOrder(self): + return self.order + + def setOrder(self, order): + self.order = order + + def getIncrement(self): + """ + :rtype : int + """ + return self.dt + + def getTimeArray(self): + incr = self.getIncrement() + self.TimeArray = np.arange(0, len(self.getCF()) * incr, incr) + self.getCut()[0] + return self.TimeArray + + def getFnoise(self): + return self.fnoise + + def setFnoise(self, fnoise): + self.fnoise = fnoise + + def getCF(self): + return self.cf + + def getXCF(self): + return self.xcf + + def _getStealthMode(self): + return self._stealthMode() + + def getDataArray(self, cut=None): + ''' + If cut times are given, time series is cut from cut[0] (start time) + till cut[1] (stop time) in order to calculate CF for certain part + only where you expect the signal! + input: cut (tuple) () + cutting window + ''' + if cut is not None: + if len(self.orig_data) == 1: + if self.cut[0] == 0 and self.cut[1] == 0: + start = 0 + stop = len(self.orig_data[0]) + elif self.cut[0] == 0 and self.cut[1] is not 0: + start = 0 + stop = self.cut[1] / self.dt + else: + start = self.cut[0] / self.dt + stop = self.cut[1] / self.dt + zz = self.orig_data.copy() + z1 = zz[0].copy() + zz[0].data = z1.data[int(start):int(stop)] + data = zz + return data + elif len(self.orig_data) == 2: + if self.cut[0] == 0 and self.cut[1] == 0: + start = 0 + stop = min([len(self.orig_data[0]), len(self.orig_data[1])]) + elif self.cut[0] == 0 and self.cut[1] is not 0: + start = 0 + stop = min([self.cut[1] / self.dt, len(self.orig_data[0]), + len(self.orig_data[1])]) + else: + start = max([0, self.cut[0] / self.dt]) + stop = min([self.cut[1] / self.dt, len(self.orig_data[0]), + len(self.orig_data[1])]) + hh = self.orig_data.copy() + h1 = hh[0].copy() + h2 = hh[1].copy() + hh[0].data = h1.data[int(start):int(stop)] + hh[1].data = h2.data[int(start):int(stop)] + data = hh + return data + elif len(self.orig_data) == 3: + if self.cut[0] == 0 and self.cut[1] == 0: + start = 0 + stop = min([self.cut[1] / self.dt, len(self.orig_data[0]), + len(self.orig_data[1]), len(self.orig_data[2])]) + elif self.cut[0] == 0 and self.cut[1] is not 0: + start = 0 + stop = self.cut[1] / self.dt + else: + start = max([0, self.cut[0] / self.dt]) + stop = min([self.cut[1] / self.dt, len(self.orig_data[0]), + len(self.orig_data[1]), len(self.orig_data[2])]) + hh = self.orig_data.copy() + h1 = hh[0].copy() + h2 = hh[1].copy() + h3 = hh[2].copy() + hh[0].data = h1.data[int(start):int(stop)] + hh[1].data = h2.data[int(start):int(stop)] + hh[2].data = h3.data[int(start):int(stop)] + data = hh + return data + else: + data = self.orig_data.copy() + return data + + def calcCF(self, data=None): + self.cf = data + + +class AICcf(CharacteristicFunction): + ''' + Function to calculate the Akaike Information Criterion (AIC) after + Maeda (1985). + :param: data, time series (whether seismogram or CF) + :type: tuple + + Output: AIC function + ''' + + def calcCF(self, data): + + # if self._getStealthMode() is False: + # print 'Calculating AIC ...' + x = self.getDataArray() + xnp = x[0].data + nn = np.isnan(xnp) + if len(nn) > 1: + xnp[nn] = 0 + datlen = len(xnp) + k = np.arange(1, datlen) + cf = np.zeros(datlen) + cumsumcf = np.cumsum(np.power(xnp, 2)) + i = np.where(cumsumcf == 0) + cumsumcf[i] = np.finfo(np.float64).eps + cf[k] = ((k - 1) * np.log(cumsumcf[k] / k) + (datlen - k + 1) * + np.log((cumsumcf[datlen - 1] - cumsumcf[k - 1]) / (datlen - k + 1))) + cf[0] = cf[1] + inf = np.isinf(cf) + ff = np.where(inf == True) + if len(ff) >= 1: + cf[ff] = 0 + + self.cf = cf - np.mean(cf) + self.xcf = x + + +class HOScf(CharacteristicFunction): + ''' + Function to calculate skewness (statistics of order 3) or kurtosis + (statistics of order 4), using one long moving window, as published + in Kueperkoch et al. (2010). + ''' + + def calcCF(self, data): + + x = self.getDataArray(self.getCut()) + xnp = x[0].data + nn = np.isnan(xnp) + if len(nn) > 1: + xnp[nn] = 0 + if self.getOrder() == 3: # this is skewness + # if self._getStealthMode() is False: + # print 'Calculating skewness ...' + y = np.power(xnp, 3) + y1 = np.power(xnp, 2) + elif self.getOrder() == 4: # this is kurtosis + # if self._getStealthMode() is False: + # print 'Calculating kurtosis ...' + y = np.power(xnp, 4) + y1 = np.power(xnp, 2) + + # Initialisation + # t2: long term moving window + ilta = int(round(self.getTime2() / self.getIncrement())) + lta = y[0] + lta1 = y1[0] + # moving windows + LTA = np.zeros(len(xnp)) + for j in range(0, len(xnp)): + if j < 4: + LTA[j] = 0 + elif j <= ilta: + lta = (y[j] + lta * (j - 1)) / j + lta1 = (y1[j] + lta1 * (j - 1)) / j + else: + lta = (y[j] - y[j - ilta]) / ilta + lta + lta1 = (y1[j] - y1[j - ilta]) / ilta + lta1 + # define LTA + if self.getOrder() == 3: + LTA[j] = lta / np.power(lta1, 1.5) + elif self.getOrder() == 4: + LTA[j] = lta / np.power(lta1, 2) + + nn = np.isnan(LTA) + if len(nn) > 1: + LTA[nn] = 0 + self.cf = LTA + self.xcf = x + + +class ARZcf(CharacteristicFunction): + def calcCF(self, data): + + print 'Calculating AR-prediction error from single trace ...' + x = self.getDataArray(self.getCut()) + xnp = x[0].data + nn = np.isnan(xnp) + if len(nn) > 1: + xnp[nn] = 0 + # some parameters needed + # add noise to time series + xnoise = xnp + np.random.normal(0.0, 1.0, len(xnp)) * self.getFnoise() * max(abs(xnp)) + tend = len(xnp) + # Time1: length of AR-determination window [sec] + # Time2: length of AR-prediction window [sec] + ldet = int(round(self.getTime1() / self.getIncrement())) # length of AR-determination window [samples] + lpred = int(np.ceil(self.getTime2() / self.getIncrement())) # length of AR-prediction window [samples] + + cf = np.zeros(len(xnp)) + loopstep = self.getARdetStep() + arcalci = ldet + self.getOrder() # AR-calculation index + for i in range(ldet + self.getOrder(), tend - lpred - 1): + if i == arcalci: + # determination of AR coefficients + # to speed up calculation, AR-coefficients are calculated only every i+loopstep[1]! + self.arDetZ(xnoise, self.getOrder(), i - ldet, i) + arcalci = arcalci + loopstep[1] + # AR prediction of waveform using calculated AR coefficients + self.arPredZ(xnp, self.arpara, i + 1, lpred) + # prediction error = CF + cf[i + lpred - 1] = np.sqrt(np.sum(np.power(self.xpred[i:i + lpred - 1] - xnp[i:i + lpred - 1], 2)) / lpred) + nn = np.isnan(cf) + if len(nn) > 1: + cf[nn] = 0 + # remove zeros and artefacts + tap = np.hanning(len(cf)) + cf = tap * cf + io = np.where(cf == 0) + ino = np.where(cf > 0) + cf[io] = cf[ino[0][0]] + + self.cf = cf + self.xcf = x + + def arDetZ(self, data, order, rind, ldet): + ''' + Function to calculate AR parameters arpara after Thomas Meier (CAU), published + in Kueperkoch et al. (2012). This function solves SLE using the Moore- + Penrose inverse, i.e. the least-squares approach. + :param: data, time series to calculate AR parameters from + :type: array + + :param: order, order of AR process + :type: int + + :param: rind, first running summation index + :type: int + + :param: ldet, length of AR-determination window (=end of summation index) + :type: int + + Output: AR parameters arpara + ''' + + # recursive calculation of data vector (right part of eq. 6.5 in Kueperkoch et al. (2012) + rhs = np.zeros(self.getOrder()) + for k in range(0, self.getOrder()): + for i in range(rind, ldet + 1): + ki = k + 1 + rhs[k] = rhs[k] + data[i] * data[i - ki] + + # recursive calculation of data array (second sum at left part of eq. 6.5 in Kueperkoch et al. 2012) + A = np.zeros((self.getOrder(), self.getOrder())) + for k in range(1, self.getOrder() + 1): + for j in range(1, k + 1): + for i in range(rind, ldet + 1): + ki = k - 1 + ji = j - 1 + A[ki, ji] = A[ki, ji] + data[i - j] * data[i - k] + + A[ji, ki] = A[ki, ji] + + # apply Moore-Penrose inverse for SVD yielding the AR-parameters + self.arpara = np.dot(np.linalg.pinv(A), rhs) + + def arPredZ(self, data, arpara, rind, lpred): + ''' + Function to predict waveform, assuming an autoregressive process of order + p (=size(arpara)), with AR parameters arpara calculated in arDet. After + Thomas Meier (CAU), published in Kueperkoch et al. (2012). + :param: data, time series to be predicted + :type: array + + :param: arpara, AR parameters + :type: float + + :param: rind, first running summation index + :type: int + + :param: lpred, length of prediction window (=end of summation index) + :type: int + + Output: predicted waveform z + ''' + # be sure of the summation indeces + if rind < len(arpara): + rind = len(arpara) + if rind > len(data) - lpred: + rind = len(data) - lpred + if lpred < 1: + lpred = 1 + if lpred > len(data) - 2: + lpred = len(data) - 2 + + z = np.append(data[0:rind], np.zeros(lpred)) + for i in range(rind, rind + lpred): + for j in range(1, len(arpara) + 1): + ji = j - 1 + z[i] = z[i] + arpara[ji] * z[i - j] + + self.xpred = z + + +class ARHcf(CharacteristicFunction): + def calcCF(self, data): + + print 'Calculating AR-prediction error from both horizontal traces ...' + + xnp = self.getDataArray(self.getCut()) + n0 = np.isnan(xnp[0].data) + if len(n0) > 1: + xnp[0].data[n0] = 0 + n1 = np.isnan(xnp[1].data) + if len(n1) > 1: + xnp[1].data[n1] = 0 + + # some parameters needed + # add noise to time series + xenoise = xnp[0].data + np.random.normal(0.0, 1.0, len(xnp[0].data)) * self.getFnoise() * max(abs(xnp[0].data)) + xnnoise = xnp[1].data + np.random.normal(0.0, 1.0, len(xnp[1].data)) * self.getFnoise() * max(abs(xnp[1].data)) + Xnoise = np.array([xenoise.tolist(), xnnoise.tolist()]) + tend = len(xnp[0].data) + # Time1: length of AR-determination window [sec] + # Time2: length of AR-prediction window [sec] + ldet = int(round(self.getTime1() / self.getIncrement())) # length of AR-determination window [samples] + lpred = int(np.ceil(self.getTime2() / self.getIncrement())) # length of AR-prediction window [samples] + + cf = np.zeros(len(xenoise)) + loopstep = self.getARdetStep() + arcalci = lpred + self.getOrder() - 1 # AR-calculation index + # arcalci = ldet + self.getOrder() - 1 #AR-calculation index + for i in range(lpred + self.getOrder() - 1, tend - 2 * lpred + 1): + if i == arcalci: + # determination of AR coefficients + # to speed up calculation, AR-coefficients are calculated only every i+loopstep[1]! + self.arDetH(Xnoise, self.getOrder(), i - ldet, i) + arcalci = arcalci + loopstep[1] + # AR prediction of waveform using calculated AR coefficients + self.arPredH(xnp, self.arpara, i + 1, lpred) + # prediction error = CF + cf[i + lpred] = np.sqrt(np.sum(np.power(self.xpred[0][i:i + lpred] - xnp[0][i:i + lpred], 2) \ + + np.power(self.xpred[1][i:i + lpred] - xnp[1][i:i + lpred], 2)) / ( + 2 * lpred)) + nn = np.isnan(cf) + if len(nn) > 1: + cf[nn] = 0 + # remove zeros and artefacts + tap = np.hanning(len(cf)) + cf = tap * cf + io = np.where(cf == 0) + ino = np.where(cf > 0) + cf[io] = cf[ino[0][0]] + + self.cf = cf + self.xcf = xnp + + def arDetH(self, data, order, rind, ldet): + ''' + Function to calculate AR parameters arpara after Thomas Meier (CAU), published + in Kueperkoch et al. (2012). This function solves SLE using the Moore- + Penrose inverse, i.e. the least-squares approach. "data" is a structured array. + AR parameters are calculated based on both horizontal components in order + to account for polarization. + :param: data, horizontal component seismograms to calculate AR parameters from + :type: structured array + + :param: order, order of AR process + :type: int + + :param: rind, first running summation index + :type: int + + :param: ldet, length of AR-determination window (=end of summation index) + :type: int + + Output: AR parameters arpara + ''' + + # recursive calculation of data vector (right part of eq. 6.5 in Kueperkoch et al. (2012) + rhs = np.zeros(self.getOrder()) + for k in range(0, self.getOrder()): + for i in range(rind, ldet): + rhs[k] = rhs[k] + data[0, i] * data[0, i - k] + data[1, i] * data[1, i - k] + + # recursive calculation of data array (second sum at left part of eq. 6.5 in Kueperkoch et al. 2012) + A = np.zeros((4, 4)) + for k in range(1, self.getOrder() + 1): + for j in range(1, k + 1): + for i in range(rind, ldet): + ki = k - 1 + ji = j - 1 + A[ki, ji] = A[ki, ji] + data[0, i - ji] * data[0, i - ki] + data[1, i - ji] * data[1, i - ki] + + A[ji, ki] = A[ki, ji] + + # apply Moore-Penrose inverse for SVD yielding the AR-parameters + self.arpara = np.dot(np.linalg.pinv(A), rhs) + + def arPredH(self, data, arpara, rind, lpred): + ''' + Function to predict waveform, assuming an autoregressive process of order + p (=size(arpara)), with AR parameters arpara calculated in arDet. After + Thomas Meier (CAU), published in Kueperkoch et al. (2012). + :param: data, horizontal component seismograms to be predicted + :type: structured array + + :param: arpara, AR parameters + :type: float + + :param: rind, first running summation index + :type: int + + :param: lpred, length of prediction window (=end of summation index) + :type: int + + Output: predicted waveform z + :type: structured array + ''' + # be sure of the summation indeces + if rind < len(arpara) + 1: + rind = len(arpara) + 1 + if rind > len(data[0]) - lpred + 1: + rind = len(data[0]) - lpred + 1 + if lpred < 1: + lpred = 1 + if lpred > len(data[0]) - 1: + lpred = len(data[0]) - 1 + + z1 = np.append(data[0][0:rind], np.zeros(lpred)) + z2 = np.append(data[1][0:rind], np.zeros(lpred)) + for i in range(rind, rind + lpred): + for j in range(1, len(arpara) + 1): + ji = j - 1 + z1[i] = z1[i] + arpara[ji] * z1[i - ji] + z2[i] = z2[i] + arpara[ji] * z2[i - ji] + + z = np.array([z1.tolist(), z2.tolist()]) + self.xpred = z + + +class AR3Ccf(CharacteristicFunction): + def calcCF(self, data): + + print 'Calculating AR-prediction error from all 3 components ...' + + xnp = self.getDataArray(self.getCut()) + n0 = np.isnan(xnp[0].data) + if len(n0) > 1: + xnp[0].data[n0] = 0 + n1 = np.isnan(xnp[1].data) + if len(n1) > 1: + xnp[1].data[n1] = 0 + n2 = np.isnan(xnp[2].data) + if len(n2) > 1: + xnp[2].data[n2] = 0 + + # some parameters needed + # add noise to time series + xenoise = xnp[0].data + np.random.normal(0.0, 1.0, len(xnp[0].data)) * self.getFnoise() * max(abs(xnp[0].data)) + xnnoise = xnp[1].data + np.random.normal(0.0, 1.0, len(xnp[1].data)) * self.getFnoise() * max(abs(xnp[1].data)) + xznoise = xnp[2].data + np.random.normal(0.0, 1.0, len(xnp[2].data)) * self.getFnoise() * max(abs(xnp[2].data)) + Xnoise = np.array([xenoise.tolist(), xnnoise.tolist(), xznoise.tolist()]) + tend = len(xnp[0].data) + # Time1: length of AR-determination window [sec] + # Time2: length of AR-prediction window [sec] + ldet = int(round(self.getTime1() / self.getIncrement())) # length of AR-determination window [samples] + lpred = int(np.ceil(self.getTime2() / self.getIncrement())) # length of AR-prediction window [samples] + + cf = np.zeros(len(xenoise)) + loopstep = self.getARdetStep() + arcalci = ldet + self.getOrder() - 1 # AR-calculation index + for i in range(ldet + self.getOrder() - 1, tend - 2 * lpred + 1): + if i == arcalci: + # determination of AR coefficients + # to speed up calculation, AR-coefficients are calculated only every i+loopstep[1]! + self.arDet3C(Xnoise, self.getOrder(), i - ldet, i) + arcalci = arcalci + loopstep[1] + + # AR prediction of waveform using calculated AR coefficients + self.arPred3C(xnp, self.arpara, i + 1, lpred) + # prediction error = CF + cf[i + lpred] = np.sqrt(np.sum(np.power(self.xpred[0][i:i + lpred] - xnp[0][i:i + lpred], 2) \ + + np.power(self.xpred[1][i:i + lpred] - xnp[1][i:i + lpred], 2) \ + + np.power(self.xpred[2][i:i + lpred] - xnp[2][i:i + lpred], 2)) / ( + 3 * lpred)) + nn = np.isnan(cf) + if len(nn) > 1: + cf[nn] = 0 + # remove zeros and artefacts + tap = np.hanning(len(cf)) + cf = tap * cf + io = np.where(cf == 0) + ino = np.where(cf > 0) + cf[io] = cf[ino[0][0]] + + self.cf = cf + self.xcf = xnp + + def arDet3C(self, data, order, rind, ldet): + ''' + Function to calculate AR parameters arpara after Thomas Meier (CAU), published + in Kueperkoch et al. (2012). This function solves SLE using the Moore- + Penrose inverse, i.e. the least-squares approach. "data" is a structured array. + AR parameters are calculated based on both horizontal components and vertical + componant. + :param: data, horizontal component seismograms to calculate AR parameters from + :type: structured array + + :param: order, order of AR process + :type: int + + :param: rind, first running summation index + :type: int + + :param: ldet, length of AR-determination window (=end of summation index) + :type: int + + Output: AR parameters arpara + ''' + + # recursive calculation of data vector (right part of eq. 6.5 in Kueperkoch et al. (2012) + rhs = np.zeros(self.getOrder()) + for k in range(0, self.getOrder()): + for i in range(rind, ldet): + rhs[k] = rhs[k] + data[0, i] * data[0, i - k] + data[1, i] * data[1, i - k] \ + + data[2, i] * data[2, i - k] + + # recursive calculation of data array (second sum at left part of eq. 6.5 in Kueperkoch et al. 2012) + A = np.zeros((4, 4)) + for k in range(1, self.getOrder() + 1): + for j in range(1, k + 1): + for i in range(rind, ldet): + ki = k - 1 + ji = j - 1 + A[ki, ji] = A[ki, ji] + data[0, i - ji] * data[0, i - ki] + data[1, i - ji] * data[1, i - ki] \ + + data[2, i - ji] * data[2, i - ki] + + A[ji, ki] = A[ki, ji] + + # apply Moore-Penrose inverse for SVD yielding the AR-parameters + self.arpara = np.dot(np.linalg.pinv(A), rhs) + + def arPred3C(self, data, arpara, rind, lpred): + ''' + Function to predict waveform, assuming an autoregressive process of order + p (=size(arpara)), with AR parameters arpara calculated in arDet3C. After + Thomas Meier (CAU), published in Kueperkoch et al. (2012). + :param: data, horizontal and vertical component seismograms to be predicted + :type: structured array + + :param: arpara, AR parameters + :type: float + + :param: rind, first running summation index + :type: int + + :param: lpred, length of prediction window (=end of summation index) + :type: int + + Output: predicted waveform z + :type: structured array + ''' + # be sure of the summation indeces + if rind < len(arpara) + 1: + rind = len(arpara) + 1 + if rind > len(data[0]) - lpred + 1: + rind = len(data[0]) - lpred + 1 + if lpred < 1: + lpred = 1 + if lpred > len(data[0]) - 1: + lpred = len(data[0]) - 1 + + z1 = np.append(data[0][0:rind], np.zeros(lpred)) + z2 = np.append(data[1][0:rind], np.zeros(lpred)) + z3 = np.append(data[2][0:rind], np.zeros(lpred)) + for i in range(rind, rind + lpred): + for j in range(1, len(arpara) + 1): + ji = j - 1 + z1[i] = z1[i] + arpara[ji] * z1[i - ji] + z2[i] = z2[i] + arpara[ji] * z2[i - ji] + z3[i] = z3[i] + arpara[ji] * z3[i - ji] + + z = np.array([z1.tolist(), z2.tolist(), z3.tolist()]) + self.xpred = z diff --git a/pylot/core/pick/compare.py b/pylot/core/pick/compare.py new file mode 100644 index 00000000..3150d157 --- /dev/null +++ b/pylot/core/pick/compare.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import copy +import operator +import os +import numpy as np +import glob +import matplotlib.pyplot as plt + +from obspy import read_events + +from pylot.core.io.phases import picksdict_from_picks +from pylot.core.util.pdf import ProbabilityDensityFunction +from pylot.core.util.utils import find_in_list +from pylot.core.util.version import get_git_version as _getVersionString + +__version__ = _getVersionString() +__author__ = 'sebastianw' + + +class Comparison(object): + """ + A Comparison object contains information on the evaluated picks' probability + density function and compares these in terms of building the difference of + compared pick sets. The results can be displayed as histograms showing its + properties. + """ + + def __init__(self, **kwargs): + names = list() + self._pdfs = dict() + for name, fn in kwargs.items(): + if isinstance(fn, PDFDictionary): + self._pdfs[name] = fn + elif isinstance(fn, dict): + self._pdfs[name] = PDFDictionary(fn) + else: + self._pdfs[name] = PDFDictionary.from_quakeml(fn) + names.append(name) + if len(names) > 2: + raise ValueError('Comparison is only defined for two ' + 'arguments!') + self._names = names + self._compare = self.compare_picksets() + + def __nonzero__(self): + if not len(self.names) == 2 or not self._pdfs: + return False + return True + + def get(self, name): + return self._pdfs[name] + + @property + def names(self): + return self._names + + @names.setter + def names(self, names): + assert isinstance(names, list) and len(names) == 2, 'variable "names"' \ + ' is either not a' \ + ' list or its ' \ + 'length is not 2:' \ + 'names : {names}'.format( + names=names) + self._names = names + + @property + def comparison(self): + return self._compare + + @property + def stations(self): + return self.comparison.keys() + + @property + def nstations(self): + return len(self.stations) + + def compare_picksets(self, type='exp'): + """ + Compare two picksets A and B and return a dictionary compiling the results. + Comparison is carried out with the help of pdf representation of the picks + and a probabilistic approach to the time difference of two onset + measurements. + :param a: filename for pickset A + :type a: str + :param b: filename for pickset B + :type b: str + :return: dictionary containing the resulting comparison pdfs for all picks + :rtype: dict + """ + compare_pdfs = dict() + + pdf_a = self.get(self.names[0]).generate_pdf_data(type) + pdf_b = self.get(self.names[1]).generate_pdf_data(type) + + for station, phases in pdf_a.items(): + if station in pdf_b.keys(): + compare_pdf = dict() + for phase in phases: + if phase in pdf_b[station].keys(): + compare_pdf[phase] = phases[phase] - pdf_b[station][ + phase] + if compare_pdf is not None: + compare_pdfs[station] = compare_pdf + + return compare_pdfs + + def plot(self, stations=None): + if stations is None: + nstations = self.nstations + stations = self.stations + else: + nstations = len(stations) + istations = range(nstations) + fig, axarr = plt.subplots(nstations, 2, sharex='col', sharey='row') + + for n in istations: + station = stations[n] + if station not in self.comparison.keys(): + continue + compare_pdf = self.comparison[station] + for l, phase in enumerate(compare_pdf.keys()): + axarr[n, l].plot(compare_pdf[phase].axis, + compare_pdf[phase].data) + if n is 0: + axarr[n, l].set_title(phase) + if l is 0: + axann = axarr[n, l].annotate(station, xy=(.05, .5), + xycoords='axes fraction') + bbox_props = dict(boxstyle='round', facecolor='lightgrey', + alpha=.7) + axann.set_bbox(bbox_props) + if n == int(np.median(istations)) and l is 0: + label = 'probability density (qualitative)' + axarr[n, l].set_ylabel(label) + plt.setp([a.get_xticklabels() for a in axarr[0, :]], visible=False) + plt.setp([a.get_yticklabels() for a in axarr[:, 1]], visible=False) + plt.setp([a.get_yticklabels() for a in axarr[:, 0]], visible=False) + + plt.show() + + def get_all(self, phasename): + pdf_dict = self.comparison + rlist = list() + for phases in pdf_dict.values(): + try: + rlist.append(phases[phasename]) + except KeyError: + continue + return rlist + + def get_array(self, phase, method_name): + method = operator.methodcaller(method_name) + pdf_list = self.get_all(phase) + rarray = map(method, pdf_list) + return np.array(rarray) + + def get_expectation_array(self, phase): + return self.get_array(phase, 'expectation') + + def get_std_array(self, phase): + return self.get_array(phase, 'standard_deviation') + + def hist_expectation(self, phases='all', bins=20, normed=False): + phases.strip() + if phases.find('all') is 0: + phases = 'ps' + phases = phases.upper() + nsp = len(phases) + fig, axarray = plt.subplots(1, nsp, sharey=True) + for n, phase in enumerate(phases): + ax = axarray[n] + data = self.get_expectation_array(phase) + xlims = [min(data), max(data)] + ax.hist(data, range=xlims, bins=bins, normed=normed) + title_str = 'phase: {0}, samples: {1}'.format(phase, len(data)) + ax.set_title(title_str) + ax.set_xlabel('expectation [s]') + if n is 0: + ax.set_ylabel('abundance [-]') + plt.setp([a.get_yticklabels() for a in axarray[1:]], visible=False) + plt.show() + + def hist_standard_deviation(self, phases='all', bins=20, normed=False): + phases.strip() + if phases.find('all') == 0: + phases = 'ps' + phases = phases.upper() + nsp = len(phases) + fig, axarray = plt.subplots(1, nsp, sharey=True) + for n, phase in enumerate(phases): + ax = axarray[n] + data = self.get_std_array(phase) + xlims = [min(data), max(data)] + ax.hist(data, range=xlims, bins=bins, normed=normed) + title_str = 'phase: {0}, samples: {1}'.format(phase, len(data)) + ax.set_title(title_str) + ax.set_xlabel('standard deviation [s]') + if n is 0: + ax.set_ylabel('abundance [-]') + plt.setp([a.get_yticklabels() for a in axarray[1:]], visible=False) + plt.show() + + def hist(self, type='std'): + pass + + +class PDFDictionary(object): + """ + A PDFDictionary is a dictionary like object containing structured data on + the probability density function of seismic phase onsets. + """ + + def __init__(self, data): + self._pickdata = data + self._pdfdata = self.generate_pdf_data() + + def __nonzero__(self): + if len(self.pick_data) < 1: + return False + else: + return True + + def __getitem__(self, item): + return self.pdf_data[item] + + @property + def pdf_data(self): + return self._pdfdata + + @pdf_data.setter + def pdf_data(self, data): + self._pdfdata = data + + @property + def pick_data(self): + return self._pickdata + + @pick_data.setter + def pick_data(self, data): + self._pickdata = data + + @property + def stations(self): + return self.pick_data.keys() + + @property + def nstations(self): + return len(self.stations) + + @classmethod + def from_quakeml(self, fn): + cat = read_events(fn) + if len(cat) > 1: + raise NotImplementedError('reading more than one event at the same ' + 'time is not implemented yet! Sorry!') + return PDFDictionary(picksdict_from_picks(cat[0])) + + def get_all(self, phase): + rlist = list() + for phases in self.pdf_data.values(): + try: + rlist.append(phases[phase]) + except KeyError: + continue + return rlist + + def generate_pdf_data(self, type='exp'): + """ + Returns probabiliy density function dictionary containing the + representation of the actual pick_data. + :param type: type of the returned + `~pylot.core.util.pdf.ProbabilityDensityFunction` object + :type type: str + :return: a dictionary containing the picks represented as pdfs + """ + + pdf_picks = copy.deepcopy(self.pick_data) + + for station, phases in pdf_picks.items(): + for phase, values in phases.items(): + if phase not in 'PS': + continue + phases[phase] = ProbabilityDensityFunction.from_pick( + values['epp'], + values['mpp'], + values['lpp'], + type=type) + + return pdf_picks + + def plot(self, stations=None): + ''' + plots the all probability density function for either desired STATIONS + or all available date + :param stations: list of stations to be plotted + :type stations: list + :return: matplotlib figure object containing the plot + ''' + assert stations is not None or not isinstance(stations, list), \ + 'parameter stations should be a list not {0}'.format(type(stations)) + if not stations: + nstations = self.nstations + stations = self.stations + else: + nstations = len(stations) + + istations = range(nstations) + fig, axarr = plt.subplots(nstations, 2, sharex='col', sharey='row') + hide_labels = True + + for n in istations: + station = stations[n] + pdfs = self.pdf_data[station] + for l, phase in enumerate(pdfs.keys()): + try: + axarr[n, l].plot(pdfs[phase].axis, pdfs[phase].data()) + if n is 0: + axarr[n, l].set_title(phase) + if l is 0: + axann = axarr[n, l].annotate(station, xy=(.05, .5), + xycoords='axes fraction') + bbox_props = dict(boxstyle='round', facecolor='lightgrey', + alpha=.7) + axann.set_bbox(bbox_props) + if n == int(np.median(istations)) and l is 0: + label = 'probability density (qualitative)' + axarr[n, l].set_ylabel(label) + except IndexError as e: + print('trying aligned plotting\n{0}'.format(e)) + hide_labels = False + axarr[l].plot(pdfs[phase].axis, pdfs[phase].data()) + axarr[l].set_title(phase) + if l is 0: + axann = axarr[l].annotate(station, xy=(.05, .5), + xycoords='axes fraction') + bbox_props = dict(boxstyle='round', facecolor='lightgrey', + alpha=.7) + axann.set_bbox(bbox_props) + if hide_labels: + plt.setp([a.get_xticklabels() for a in axarr[0, :]], visible=False) + plt.setp([a.get_yticklabels() for a in axarr[:, 1]], visible=False) + plt.setp([a.get_yticklabels() for a in axarr[:, 0]], visible=False) + + return fig + + +class PDFstatistics(object): + """ + This object can be used to get various statistic values from probabillity density functions. + Takes a path as argument. + """ + + + def __init__(self, directory): + """Initiates some values needed when dealing with pdfs later""" + self._rootdir = directory + self._evtlist = list() + self._rphase = None + self.make_fnlist() + + def make_fnlist(self, fn_pattern='*.xml'): + """ + Takes a file pattern and searches for that recursively in the set path for the object. + :param fn_pattern: A pattern that can identify all datafiles. Default Value = '*.xml' + :type fn_pattern: string + :return: creates a list of events saved in the PDFstatistics object. + """ + evtlist = list() + for root, _, files in os.walk(self.root): + for file in files: + if file.endswith(fn_pattern[1:]): + evtlist.append(os.path.join(root, file)) + self._evtlist = evtlist + + def __iter__(self): + for evt in self._evtlist: + yield PDFDictionary.from_quakeml(evt) + + def __getitem__(self, item): + evt = find_in_list(self._evtlist, item) + if evt: + return PDFDictionary.from_quakeml(evt) + return None + + @property + def root(self): + return self._rootdir + + @root.setter + def root(self, value): + if os.path.exists(value): + self._rootdir = value + else: + raise ValueError("path doesn't exist: %s" % value) + + @property + def curphase(self): + """ + return the current phase type of interest + :return: current phase + """ + return self._rphase + + @curphase.setter + def curphase(self, type): + """ + setter method for property curphase + :param type: specify the phase type of interest + :type type: string ('p' or 's') + :return: - + """ + if type.upper() not in 'PS': + raise ValueError("phase type must be either 'P' or 'S'!") + else: + self._rphase = type.upper() + + def get(self, property='std', value=None): + """ + takes a property str and a probability value and returns all + property's values for the current phase of interest + :func:`self.curphase` + + :param property: property name (default: 'std') + :type property: str + :param value: probability value :math:\alpha + :type value: float + :return: list containing all property's values + """ + assert isinstance(self.curphase, + str), 'phase has to be set before being ' \ + 'able to iterate over items...' + rlist = [] + method_options = dict(STD='standard_deviation', + Q='quantile', + QD='quantile_distance', + QDF='quantile_dist_frac') + + # create method caller for easy mapping + if property.upper() == 'STD': + method = operator.methodcaller(method_options[property.upper()]) + elif value is not None: + try: + method = operator.methodcaller(method_options[property.upper()], + value) + except KeyError: + raise KeyError('unknwon property: {0}'.format(property.upper())) + else: + raise ValueError("for call to method {0} value has to be " + "defined but is 'None' ".format(method_options[ + property.upper()])) + + for pdf_dict in self: + # create worklist + wlist = pdf_dict.get_all(self.curphase) + # map method calls to object in worklist + rlist += map(method, wlist) + + return rlist + + def writeThetaToFile(self,array,out_dir): + """ + Method to write array like data to file. Useful since acquiring can take + serious amount of time when dealing with large databases. + :param array: List of values. + :type array: list + :param out_dir: Path to save file to including file name. + :type out_dir: str + :return: Saves a file at given output directory. + """ + fid = open(os.path.join(out_dir), 'w') + for val in array: + fid.write(str(val)+'\n') + fid.close() + + +def main(): + root_dir ='/home/sebastianp/Codetesting/xmls/' + Insheim = PDFstatistics(root_dir) + Insheim.curphase = 'p' + qdlist = Insheim.get('qdf', 0.2) + print qdlist + + +if __name__ == "__main__": + import cProfile + + pr = cProfile.Profile() + pr.enable() + main() + pr.disable() + # after your program ends + pr.print_stats(sort="calls") \ No newline at end of file diff --git a/pylot/core/pick/picker.py b/pylot/core/pick/picker.py new file mode 100644 index 00000000..cc6ee7d9 --- /dev/null +++ b/pylot/core/pick/picker.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +""" +Created Dec 2014 to Feb 2015 +Implementation of the automated picking algorithms published and described in: + +Kueperkoch, L., Meier, T., Lee, J., Friederich, W., & Egelados Working Group, 2010: +Automated determination of P-phase arrival times at regional and local distances +using higher order statistics, Geophys. J. Int., 181, 1159-1170 + +Kueperkoch, L., Meier, T., Bruestle, A., Lee, J., Friederich, W., & Egelados +Working Group, 2012: Automated determination of S-phase arrival times using +autoregressive prediction: application ot local and regional distances, Geophys. J. Int., +188, 687-702. + +The picks with the above described algorithms are assumed to be the most likely picks. +For each most likely pick the corresponding earliest and latest possible picks are +calculated after Diehl & Kissling (2009). + +:author: MAGS2 EP3 working group / Ludger Kueperkoch +""" + +import numpy as np +import matplotlib.pyplot as plt +from pylot.core.pick.utils import getnoisewin, getsignalwin +from pylot.core.pick.charfuns import CharacteristicFunction +import warnings + + +class AutoPicker(object): + ''' + Superclass of different, automated picking algorithms applied on a CF determined + using AIC, HOS, or AR prediction. + ''' + + warnings.simplefilter('ignore') + + def __init__(self, cf, TSNR, PickWindow, iplot=None, aus=None, Tsmooth=None, Pick1=None): + ''' + :param: cf, characteristic function, on which the picking algorithm is applied + :type: `~pylot.core.pick.CharFuns.CharacteristicFunction` object + + :param: TSNR, length of time windows around pick used to determine SNR [s] + :type: tuple (T_noise, T_gap, T_signal) + + :param: PickWindow, length of pick window [s] + :type: float + + :param: iplot, no. of figure window for plotting interims results + :type: integer + + :param: aus ("artificial uplift of samples"), find local minimum at i if aic(i-1)*(1+aus) >= aic(i) + :type: float + + :param: Tsmooth, length of moving smoothing window to calculate smoothed CF [s] + :type: float + + :param: Pick1, initial (prelimenary) onset time, starting point for PragPicker and + EarlLatePicker + :type: float + + ''' + + assert isinstance(cf, CharacteristicFunction), "%s is not a CharacteristicFunction object" % str(cf) + + self.cf = cf.getCF() + self.Tcf = cf.getTimeArray() + self.Data = cf.getXCF() + self.dt = cf.getIncrement() + self.setTSNR(TSNR) + self.setPickWindow(PickWindow) + self.setiplot(iplot) + self.setaus(aus) + self.setTsmooth(Tsmooth) + self.setpick1(Pick1) + self.calcPick() + + def __str__(self): + return '''\n\t{name} object:\n + TSNR:\t\t\t{TSNR}\n + PickWindow:\t{PickWindow}\n + aus:\t{aus}\n + Tsmooth:\t{Tsmooth}\n + Pick1:\t{Pick1}\n + '''.format(name=type(self).__name__, + TSNR=self.getTSNR(), + PickWindow=self.getPickWindow(), + aus=self.getaus(), + Tsmooth=self.getTsmooth(), + Pick1=self.getpick1()) + + def getTSNR(self): + return self.TSNR + + def setTSNR(self, TSNR): + self.TSNR = TSNR + + def getPickWindow(self): + return self.PickWindow + + def setPickWindow(self, PickWindow): + self.PickWindow = PickWindow + + def getaus(self): + return self.aus + + def setaus(self, aus): + self.aus = aus + + def setTsmooth(self, Tsmooth): + self.Tsmooth = Tsmooth + + def getTsmooth(self): + return self.Tsmooth + + def getpick(self): + return self.Pick + + def getSNR(self): + return self.SNR + + def getSlope(self): + return self.slope + + def getiplot(self): + return self.iplot + + def setiplot(self, iplot): + self.iplot = iplot + + def getpick1(self): + return self.Pick1 + + def setpick1(self, Pick1): + self.Pick1 = Pick1 + + def calcPick(self): + self.Pick = None + + +class AICPicker(AutoPicker): + ''' + Method to derive the onset time of an arriving phase based on CF + derived from AIC. In order to get an impression of the quality of this inital pick, + a quality assessment is applied based on SNR and slope determination derived from the CF, + from which the AIC has been calculated. + ''' + + def calcPick(self): + + print('AICPicker: Get initial onset time (pick) from AIC-CF ...') + + self.Pick = None + self.slope = None + self.SNR = None + # find NaN's + nn = np.isnan(self.cf) + if len(nn) > 1: + self.cf[nn] = 0 + # taper AIC-CF to get rid off side maxima + tap = np.hanning(len(self.cf)) + aic = tap * self.cf + max(abs(self.cf)) + # smooth AIC-CF + ismooth = int(round(self.Tsmooth / self.dt)) + aicsmooth = np.zeros(len(aic)) + if len(aic) < ismooth: + print('AICPicker: Tsmooth larger than CF!') + return + else: + for i in range(1, len(aic)): + if i > ismooth: + ii1 = i - ismooth + aicsmooth[i] = aicsmooth[i - 1] + (aic[i] - aic[ii1]) / ismooth + else: + aicsmooth[i] = np.mean(aic[1: i]) + # remove offset + offset = abs(min(aic) - min(aicsmooth)) + aicsmooth = aicsmooth - offset + # get maximum of 1st derivative of AIC-CF (more stable!) as starting point + diffcf = np.diff(aicsmooth) + # find NaN's + nn = np.isnan(diffcf) + if len(nn) > 1: + diffcf[nn] = 0 + # taper CF to get rid off side maxima + tap = np.hanning(len(diffcf)) + diffcf = tap * diffcf * max(abs(aicsmooth)) + icfmax = np.argmax(diffcf) + + # find minimum in AIC-CF front of maximum + lpickwindow = int(round(self.PickWindow / self.dt)) + for i in range(icfmax - 1, max([icfmax - lpickwindow, 2]), -1): + if aicsmooth[i - 1] >= aicsmooth[i]: + self.Pick = self.Tcf[i] + break + # if no minimum could be found: + # search in 1st derivative of AIC-CF + if self.Pick is None: + for i in range(icfmax - 1, max([icfmax - lpickwindow, 2]), -1): + if diffcf[i - 1] >= diffcf[i]: + self.Pick = self.Tcf[i] + break + + # quality assessment using SNR and slope from CF + if self.Pick is not None: + # get noise window + inoise = getnoisewin(self.Tcf, self.Pick, self.TSNR[0], self.TSNR[1]) + # check, if these are counts or m/s, important for slope estimation! + # this is quick and dirty, better solution? + if max(self.Data[0].data < 1e-3): + self.Data[0].data = self.Data[0].data * 1000000 + # get signal window + isignal = getsignalwin(self.Tcf, self.Pick, self.TSNR[2]) + # calculate SNR from CF + self.SNR = max(abs(aic[isignal] - np.mean(aic[isignal]))) / \ + max(abs(aic[inoise] - np.mean(aic[inoise]))) + # calculate slope from CF after initial pick + # get slope window + tslope = self.TSNR[3] # slope determination window + islope = np.where((self.Tcf <= min([self.Pick + tslope, len(self.Data[0].data)])) \ + & (self.Tcf >= self.Pick)) + # find maximum within slope determination window + # 'cause slope should be calculated up to first local minimum only! + imax = np.argmax(self.Data[0].data[islope]) + if imax == 0: + print('AICPicker: Maximum for slope determination right at the beginning of the window!') + print('Choose longer slope determination window!') + if self.iplot > 1: + p = plt.figure(self.iplot) + x = self.Data[0].data + p1, = plt.plot(self.Tcf, x / max(x), 'k') + p2, = plt.plot(self.Tcf, aicsmooth / max(aicsmooth), 'r') + plt.legend([p1, p2], ['(HOS-/AR-) Data', 'Smoothed AIC-CF']) + plt.xlabel('Time [s] since %s' % self.Data[0].stats.starttime) + plt.yticks([]) + plt.title(self.Data[0].stats.station) + plt.show() + raw_input() + plt.close(p) + return + islope = islope[0][0:imax] + dataslope = self.Data[0].data[islope] + # calculate slope as polynomal fit of order 1 + xslope = np.arange(0, len(dataslope), 1) + P = np.polyfit(xslope, dataslope, 1) + datafit = np.polyval(P, xslope) + if datafit[0] >= datafit[len(datafit) - 1]: + print('AICPicker: Negative slope, bad onset skipped!') + return + self.slope = 1 / tslope * (datafit[len(dataslope) - 1] - datafit[0]) + + else: + self.SNR = None + self.slope = None + + if self.iplot > 1: + p = plt.figure(self.iplot) + x = self.Data[0].data + p1, = plt.plot(self.Tcf, x / max(x), 'k') + p2, = plt.plot(self.Tcf, aicsmooth / max(aicsmooth), 'r') + if self.Pick is not None: + p3, = plt.plot([self.Pick, self.Pick], [-0.1, 0.5], 'b', linewidth=2) + plt.legend([p1, p2, p3], ['(HOS-/AR-) Data', 'Smoothed AIC-CF', 'AIC-Pick']) + else: + plt.legend([p1, p2], ['(HOS-/AR-) Data', 'Smoothed AIC-CF']) + plt.xlabel('Time [s] since %s' % self.Data[0].stats.starttime) + plt.yticks([]) + plt.title(self.Data[0].stats.station) + + if self.Pick is not None: + plt.figure(self.iplot + 1) + p11, = plt.plot(self.Tcf, x, 'k') + p12, = plt.plot(self.Tcf[inoise], self.Data[0].data[inoise]) + p13, = plt.plot(self.Tcf[isignal], self.Data[0].data[isignal], 'r') + p14, = plt.plot(self.Tcf[islope], dataslope, 'g--') + p15, = plt.plot(self.Tcf[islope], datafit, 'g', linewidth=2) + plt.legend([p11, p12, p13, p14, p15], + ['Data', 'Noise Window', 'Signal Window', 'Slope Window', 'Slope'], + loc='best') + plt.title('Station %s, SNR=%7.2f, Slope= %12.2f counts/s' % (self.Data[0].stats.station, + self.SNR, self.slope)) + plt.xlabel('Time [s] since %s' % self.Data[0].stats.starttime) + plt.ylabel('Counts') + plt.yticks([]) + + plt.show() + raw_input() + plt.close(p) + + if self.Pick == None: + print('AICPicker: Could not find minimum, picking window too short?') + + +class PragPicker(AutoPicker): + ''' + Method of pragmatic picking exploiting information given by CF. + ''' + + def calcPick(self): + + if self.getpick1() is not None: + print('PragPicker: Get most likely pick from HOS- or AR-CF using pragmatic picking algorithm ...') + + self.Pick = None + self.SNR = None + self.slope = None + pickflag = 0 + # smooth CF + ismooth = int(round(self.Tsmooth / self.dt)) + cfsmooth = np.zeros(len(self.cf)) + if len(self.cf) < ismooth: + print('PragPicker: Tsmooth larger than CF!') + return + else: + for i in range(1, len(self.cf)): + if i > ismooth: + ii1 = i - ismooth + cfsmooth[i] = cfsmooth[i - 1] + (self.cf[i] - self.cf[ii1]) / ismooth + else: + cfsmooth[i] = np.mean(self.cf[1: i]) + + # select picking window + # which is centered around tpick1 + ipick = np.where((self.Tcf >= self.getpick1() - self.PickWindow / 2) \ + & (self.Tcf <= self.getpick1() + self.PickWindow / 2)) + cfipick = self.cf[ipick] - np.mean(self.cf[ipick]) + Tcfpick = self.Tcf[ipick] + cfsmoothipick = cfsmooth[ipick] - np.mean(self.cf[ipick]) + ipick1 = np.argmin(abs(self.Tcf - self.getpick1())) + cfpick1 = 2 * self.cf[ipick1] + + # check trend of CF, i.e. differences of CF and adjust aus regarding this trend + # prominent trend: decrease aus + # flat: use given aus + cfdiff = np.diff(cfipick) + i0diff = np.where(cfdiff > 0) + cfdiff = cfdiff[i0diff] + minaus = min(cfdiff * (1 + self.aus)) + aus1 = max([minaus, self.aus]) + + # at first we look to the right until the end of the pick window is reached + flagpick_r = 0 + flagpick_l = 0 + cfpick_r = 0 + cfpick_l = 0 + lpickwindow = int(round(self.PickWindow / self.dt)) + for i in range(max(np.insert(ipick, 0, 2)), min([ipick1 + lpickwindow + 1, len(self.cf) - 1])): + if self.cf[i + 1] > self.cf[i] and self.cf[i - 1] >= self.cf[i]: + if cfsmooth[i - 1] * (1 + aus1) >= cfsmooth[i]: + if cfpick1 >= self.cf[i]: + pick_r = self.Tcf[i] + self.Pick = pick_r + flagpick_l = 1 + cfpick_r = self.cf[i] + break + + # now we look to the left + for i in range(ipick1, max([ipick1 - lpickwindow + 1, 2]), -1): + if self.cf[i + 1] > self.cf[i] and self.cf[i - 1] >= self.cf[i]: + if cfsmooth[i - 1] * (1 + aus1) >= cfsmooth[i]: + if cfpick1 >= self.cf[i]: + pick_l = self.Tcf[i] + self.Pick = pick_l + flagpick_r = 1 + cfpick_l = self.cf[i] + break + + # now decide which pick: left or right? + if flagpick_l > 0 and flagpick_r > 0 and cfpick_l <= 3 * cfpick_r: + self.Pick = pick_l + pickflag = 1 + elif flagpick_l > 0 and flagpick_r > 0 and cfpick_l >= cfpick_r: + self.Pick = pick_r + pickflag = 1 + elif flagpick_l == 0 and flagpick_r > 0 and cfpick_l >= cfpick_r: + self.Pick = pick_l + pickflag = 1 + else: + print('PragPicker: Could not find reliable onset!') + self.Pick = None + pickflag = 0 + + if self.getiplot() > 1: + p = plt.figure(self.getiplot()) + p1, = plt.plot(Tcfpick, cfipick, 'k') + p2, = plt.plot(Tcfpick, cfsmoothipick, 'r') + if pickflag > 0: + p3, = plt.plot([self.Pick, self.Pick], [min(cfipick), max(cfipick)], 'b', linewidth=2) + plt.legend([p1, p2, p3], ['CF', 'Smoothed CF', 'Pick']) + plt.xlabel('Time [s] since %s' % self.Data[0].stats.starttime) + plt.yticks([]) + plt.title(self.Data[0].stats.station) + plt.show() + raw_input() + plt.close(p) + + else: + print('PragPicker: No initial onset time given! Check input!') + self.Pick = None + return diff --git a/pylot/core/pick/utils.py b/pylot/core/pick/utils.py new file mode 100644 index 00000000..20c2227c --- /dev/null +++ b/pylot/core/pick/utils.py @@ -0,0 +1,989 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# -*- coding: utf-8 -*- +""" + Created Mar/Apr 2015 + Collection of helpful functions for manual and automatic picking. + + :author: Ludger Kueperkoch / MAGS2 EP3 working group +""" + +import warnings + +import matplotlib.pyplot as plt +import numpy as np +from obspy.core import Stream, UTCDateTime + + +def earllatepicker(X, nfac, TSNR, Pick1, iplot=None, stealth_mode=False): + ''' + Function to derive earliest and latest possible pick after Diehl & Kissling (2009) + as reasonable uncertainties. Latest possible pick is based on noise level, + earliest possible pick is half a signal wavelength in front of most likely + pick given by PragPicker or manually set by analyst. Most likely pick + (initial pick Pick1) must be given. + + :param: X, time series (seismogram) + :type: `~obspy.core.stream.Stream` + + :param: nfac (noise factor), nfac times noise level to calculate latest possible pick + :type: int + + :param: TSNR, length of time windows around pick used to determine SNR [s] + :type: tuple (T_noise, T_gap, T_signal) + + :param: Pick1, initial (most likely) onset time, starting point for earllatepicker + :type: float + + :param: iplot, if given, results are plotted in figure(iplot) + :type: int + ''' + + assert isinstance(X, Stream), "%s is not a stream object" % str(X) + + LPick = None + EPick = None + PickError = None + if stealth_mode is False: + print('earllatepicker: Get earliest and latest possible pick' + ' relative to most likely pick ...') + + x = X[0].data + t = np.arange(0, X[0].stats.npts / X[0].stats.sampling_rate, + X[0].stats.delta) + inoise = getnoisewin(t, Pick1, TSNR[0], TSNR[1]) + # get signal window + isignal = getsignalwin(t, Pick1, TSNR[2]) + # remove mean + x = x - np.mean(x[inoise]) + # calculate noise level + nlevel = np.sqrt(np.mean(np.square(x[inoise]))) * nfac + # get time where signal exceeds nlevel + ilup, = np.where(x[isignal] > nlevel) + ildown, = np.where(x[isignal] < -nlevel) + if not ilup.size and not ildown.size: + if stealth_mode is False: + print ("earllatepicker: Signal lower than noise level!\n" + "Skip this trace!") + return LPick, EPick, PickError + il = min(np.min(ilup) if ilup.size else float('inf'), + np.min(ildown) if ildown.size else float('inf')) + LPick = t[isignal][il] + + # get earliest possible pick + + EPick = np.nan; + count = 0 + pis = isignal + + # if EPick stays NaN the signal window size will be doubled + while np.isnan(EPick): + if count > 0: + if stealth_mode is False: + print("\nearllatepicker: Doubled signal window size %s time(s) " + "because of NaN for earliest pick." % count) + isigDoubleWinStart = pis[-1] + 1 + isignalDoubleWin = np.arange(isigDoubleWinStart, + isigDoubleWinStart + len(pis)) + if (isigDoubleWinStart + len(pis)) < X[0].data.size: + pis = np.concatenate((pis, isignalDoubleWin)) + else: + if stealth_mode is False: + print("Could not double signal window. Index out of bounds.") + break + count += 1 + # determine all zero crossings in signal window (demeaned) + zc = crossings_nonzero_all(x[pis] - x[pis].mean()) + # calculate mean half period T0 of signal as the average of the + T0 = np.mean(np.diff(zc)) * X[0].stats.delta # this is half wave length! + EPick = Pick1 - T0 # half wavelength as suggested by Diehl et al. + + # get symmetric pick error as mean from earliest and latest possible pick + # by weighting latest possible pick two times earliest possible pick + diffti_tl = LPick - Pick1 + diffti_te = Pick1 - EPick + PickError = symmetrize_error(diffti_te, diffti_tl) + + if iplot > 1: + p = plt.figure(iplot) + p1, = plt.plot(t, x, 'k') + p2, = plt.plot(t[inoise], x[inoise]) + p3, = plt.plot(t[isignal], x[isignal], 'r') + p4, = plt.plot([t[0], t[int(len(t)) - 1]], [nlevel, nlevel], '--k') + p5, = plt.plot(t[isignal[zc]], np.zeros(len(zc)), '*g', + markersize=14) + plt.legend([p1, p2, p3, p4, p5], + ['Data', 'Noise Window', 'Signal Window', 'Noise Level', + 'Zero Crossings'], + loc='best') + plt.plot([t[0], t[int(len(t)) - 1]], [-nlevel, -nlevel], '--k') + plt.plot([Pick1, Pick1], [max(x), -max(x)], 'b', linewidth=2) + plt.plot([LPick, LPick], [max(x) / 2, -max(x) / 2], '--k') + plt.plot([EPick, EPick], [max(x) / 2, -max(x) / 2], '--k') + plt.plot([Pick1 + PickError, Pick1 + PickError], + [max(x) / 2, -max(x) / 2], 'r--') + plt.plot([Pick1 - PickError, Pick1 - PickError], + [max(x) / 2, -max(x) / 2], 'r--') + plt.xlabel('Time [s] since %s' % X[0].stats.starttime) + plt.yticks([]) + plt.title( + 'Earliest-/Latest Possible/Most Likely Pick & Symmetric Pick Error, %s' % + X[0].stats.station) + plt.show() + raw_input() + plt.close(p) + + return EPick, LPick, PickError + + +def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=None): + ''' + Function to derive first motion (polarity) of given phase onset Pick. + Calculation is based on zero crossings determined within time window pickwin + after given onset time. + + :param: Xraw, unfiltered time series (seismogram) + :type: `~obspy.core.stream.Stream` + + :param: Xfilt, filtered time series (seismogram) + :type: `~obspy.core.stream.Stream` + + :param: pickwin, time window after onset Pick within zero crossings are calculated + :type: float + + :param: Pick, initial (most likely) onset time, starting point for fmpicker + :type: float + + :param: iplot, if given, results are plotted in figure(iplot) + :type: int + ''' + + warnings.simplefilter('ignore', np.RankWarning) + + assert isinstance(Xraw, Stream), "%s is not a stream object" % str(Xraw) + assert isinstance(Xfilt, Stream), "%s is not a stream object" % str(Xfilt) + + FM = None + if Pick is not None: + print ("fmpicker: Get first motion (polarity) of onset using unfiltered seismogram...") + + xraw = Xraw[0].data + xfilt = Xfilt[0].data + t = np.arange(0, Xraw[0].stats.npts / Xraw[0].stats.sampling_rate, + Xraw[0].stats.delta) + # get pick window + ipick = np.where( + (t <= min([Pick + pickwin, len(Xraw[0])])) & (t >= Pick)) + # remove mean + xraw[ipick] = xraw[ipick] - np.mean(xraw[ipick]) + xfilt[ipick] = xfilt[ipick] - np.mean(xfilt[ipick]) + + # get zero crossings after most likely pick + # initial onset is assumed to be the first zero crossing + # first from unfiltered trace + zc1 = [] + zc1.append(Pick) + index1 = [] + i = 0 + for j in range(ipick[0][1], ipick[0][len(t[ipick]) - 1]): + i = i + 1 + if xraw[j - 1] <= 0 <= xraw[j]: + zc1.append(t[ipick][i]) + index1.append(i) + elif xraw[j - 1] > 0 >= xraw[j]: + zc1.append(t[ipick][i]) + index1.append(i) + if len(zc1) == 3: + break + + # if time difference betweeen 1st and 2cnd zero crossing + # is too short, get time difference between 1st and 3rd + # to derive maximum + if zc1[1] - zc1[0] <= Xraw[0].stats.delta: + li1 = index1[1] + else: + li1 = index1[0] + if np.size(xraw[ipick[0][1]:ipick[0][li1]]) == 0: + print ("fmpicker: Onset on unfiltered trace too emergent for first motion determination!") + P1 = None + else: + imax1 = np.argmax(abs(xraw[ipick[0][1]:ipick[0][li1]])) + if imax1 == 0: + imax1 = np.argmax(abs(xraw[ipick[0][1]:ipick[0][index1[1]]])) + if imax1 == 0: + print ("fmpicker: Zero crossings too close!") + print ("Skip first motion determination!") + return FM + + islope1 = np.where((t >= Pick) & (t <= Pick + t[imax1])) + # calculate slope as polynomal fit of order 1 + xslope1 = np.arange(0, len(xraw[islope1]), 1) + P1 = np.polyfit(xslope1, xraw[islope1], 1) + datafit1 = np.polyval(P1, xslope1) + + # now using filterd trace + # next zero crossings after most likely pick + zc2 = [] + zc2.append(Pick) + index2 = [] + i = 0 + for j in range(ipick[0][1], ipick[0][len(t[ipick]) - 1]): + i = i + 1 + if xfilt[j - 1] <= 0 <= xfilt[j]: + zc2.append(t[ipick][i]) + index2.append(i) + elif xfilt[j - 1] > 0 >= xfilt[j]: + zc2.append(t[ipick][i]) + index2.append(i) + if len(zc2) == 3: + break + + # if time difference betweeen 1st and 2cnd zero crossing + # is too short, get time difference between 1st and 3rd + # to derive maximum + if zc2[1] - zc2[0] <= Xfilt[0].stats.delta: + li2 = index2[1] + else: + li2 = index2[0] + if np.size(xfilt[ipick[0][1]:ipick[0][li2]]) == 0: + print ("fmpicker: Onset on filtered trace too emergent for first motion determination!") + P2 = None + else: + imax2 = np.argmax(abs(xfilt[ipick[0][1]:ipick[0][li2]])) + if imax2 == 0: + imax2 = np.argmax(abs(xfilt[ipick[0][1]:ipick[0][index2[1]]])) + if imax2 == 0: + print ("fmpicker: Zero crossings too close!") + print ("Skip first motion determination!") + return FM + + islope2 = np.where((t >= Pick) & (t <= Pick + t[imax2])) + # calculate slope as polynomal fit of order 1 + xslope2 = np.arange(0, len(xfilt[islope2]), 1) + P2 = np.polyfit(xslope2, xfilt[islope2], 1) + datafit2 = np.polyval(P2, xslope2) + + # compare results + if P1 is not None and P2 is not None: + if P1[0] < 0 and P2[0] < 0: + FM = 'D' + elif P1[0] >= 0 > P2[0]: + FM = '-' + elif P1[0] < 0 <= P2[0]: + FM = '-' + elif P1[0] > 0 and P2[0] > 0: + FM = 'U' + elif P1[0] <= 0 < P2[0]: + FM = '+' + elif P1[0] > 0 >= P2[0]: + FM = '+' + + print ("fmpicker: Found polarity %s" % FM) + + if iplot > 1: + plt.figure(iplot) + plt.subplot(2, 1, 1) + plt.plot(t, xraw, 'k') + p1, = plt.plot([Pick, Pick], [max(xraw), -max(xraw)], 'b', linewidth=2) + if P1 is not None: + p2, = plt.plot(t[islope1], xraw[islope1]) + p3, = plt.plot(zc1, np.zeros(len(zc1)), '*g', markersize=14) + p4, = plt.plot(t[islope1], datafit1, '--g', linewidth=2) + plt.legend([p1, p2, p3, p4], + ['Pick', 'Slope Window', 'Zero Crossings', 'Slope'], + loc='best') + plt.text(Pick + 0.02, max(xraw) / 2, '%s' % FM, fontsize=14) + ax = plt.gca() + plt.yticks([]) + plt.title('First-Motion Determination, %s, Unfiltered Data' % Xraw[ + 0].stats.station) + + plt.subplot(2, 1, 2) + plt.title('First-Motion Determination, Filtered Data') + plt.plot(t, xfilt, 'k') + p1, = plt.plot([Pick, Pick], [max(xfilt), -max(xfilt)], 'b', + linewidth=2) + if P2 is not None: + p2, = plt.plot(t[islope2], xfilt[islope2]) + p3, = plt.plot(zc2, np.zeros(len(zc2)), '*g', markersize=14) + p4, = plt.plot(t[islope2], datafit2, '--g', linewidth=2) + plt.text(Pick + 0.02, max(xraw) / 2, '%s' % FM, fontsize=14) + ax = plt.gca() + plt.xlabel('Time [s] since %s' % Xraw[0].stats.starttime) + plt.yticks([]) + plt.show() + raw_input() + plt.close(iplot) + + return FM + + +def crossings_nonzero_all(data): + pos = data > 0 + npos = ~pos + return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0] + + +def symmetrize_error(dte, dtl): + """ + takes earliest and latest possible pick and returns the symmetrized pick + uncertainty value + :param dte: relative lower uncertainty + :param dtl: relative upper uncertainty + :return: symmetrized error + """ + return (dte + 2 * dtl) / 3 + + +def getSNR(X, TSNR, t1, tracenum=0): + ''' + Function to calculate SNR of certain part of seismogram relative to + given time (onset) out of given noise and signal windows. A safety gap + between noise and signal part can be set. Returns SNR and SNR [dB] and + noiselevel. + + :param: X, time series (seismogram) + :type: `~obspy.core.stream.Stream` + + :param: TSNR, length of time windows [s] around t1 (onset) used to determine SNR + :type: tuple (T_noise, T_gap, T_signal) + + :param: t1, initial time (onset) from which noise and signal windows are calculated + :type: float + ''' + + assert isinstance(X, Stream), "%s is not a stream object" % str(X) + + x = X[tracenum].data + npts = X[tracenum].stats.npts + sr = X[tracenum].stats.sampling_rate + dt = X[tracenum].stats.delta + t = np.arange(0, npts / sr, dt) + + # get noise window + inoise = getnoisewin(t, t1, TSNR[0], TSNR[1]) + + # get signal window + isignal = getsignalwin(t, t1, TSNR[2]) + if np.size(inoise) < 1: + print ("getSNR: Empty array inoise, check noise window!") + return + elif np.size(isignal) < 1: + print ("getSNR: Empty array isignal, check signal window!") + return + + # demean over entire waveform + x = x - np.mean(x[inoise]) + + # calculate ratios + # noiselevel = np.sqrt(np.mean(np.square(x[inoise]))) + # signallevel = np.sqrt(np.mean(np.square(x[isignal]))) + + noiselevel = np.abs(x[inoise]).max() + signallevel = np.abs(x[isignal]).max() + + SNR = signallevel / noiselevel + SNRdB = 10 * np.log10(SNR) + + return SNR, SNRdB, noiselevel + + +def getnoisewin(t, t1, tnoise, tgap): + ''' + Function to extract indeces of data out of time series for noise calculation. + Returns an array of indeces. + + :param: t, array of time stamps + :type: numpy array + + :param: t1, time from which relativ to it noise window is extracted + :type: float + + :param: tnoise, length of time window [s] for noise part extraction + :type: float + + :param: tgap, safety gap between t1 (onset) and noise window to + ensure, that noise window contains no signal + :type: float + ''' + + # get noise window + inoise, = np.where((t <= max([t1 - tgap, 0])) \ + & (t >= max([t1 - tnoise - tgap, 0]))) + if np.size(inoise) < 1: + print ("getnoisewin: Empty array inoise, check noise window!") + + return inoise + + +def getsignalwin(t, t1, tsignal): + ''' + Function to extract data out of time series for signal level calculation. + Returns an array of indeces. + + :param: t, array of time stamps + :type: numpy array + + :param: t1, time from which relativ to it signal window is extracted + :type: float + + :param: tsignal, length of time window [s] for signal level calculation + :type: float + ''' + + # get signal window + isignal, = np.where((t <= min([t1 + tsignal, len(t)])) \ + & (t >= t1)) + if np.size(isignal) < 1: + print ("getsignalwin: Empty array isignal, check signal window!") + + return isignal + + +def getResolutionWindow(snr): + """ + Number -> Float + produce the half of the time resolution window width from given SNR + value + SNR >= 3 -> 2 sec HRW + 3 > SNR >= 2 -> 5 sec MRW + 2 > SNR >= 1.5 -> 10 sec LRW + 1.5 > SNR -> 15 sec VLRW + see also Diehl et al. 2009 + + >>> getResolutionWindow(0.5) + 7.5 + >>> getResolutionWindow(1.8) + 5.0 + >>> getResolutionWindow(2.3) + 2.5 + >>> getResolutionWindow(4) + 1.0 + >>> getResolutionWindow(2) + 2.5 + """ + + res_wins = {'HRW': 2., 'MRW': 5., 'LRW': 10., 'VLRW': 15.} + + if snr < 1.5: + time_resolution = res_wins['VLRW'] + elif snr < 2.: + time_resolution = res_wins['LRW'] + elif snr < 3.: + time_resolution = res_wins['MRW'] + else: + time_resolution = res_wins['HRW'] + + return time_resolution / 2 + + +def select_for_phase(st, phase): + ''' + takes a STream object and a phase name and returns that particular component + which presumably shows the chosen PHASE best + + :param st: stream object containing one or more component[s] + :type st: `~obspy.core.stream.Stream` + :param phase: label of the phase for which the stream selection is carried + out; 'P' or 'S' + :type phase: str + :return: + ''' + from pylot.core.util.defaults import COMPNAME_MAP + + sel_st = Stream() + if phase.upper() == 'P': + comp = 'Z' + alter_comp = COMPNAME_MAP[comp] + sel_st += st.select(component=comp) + sel_st += st.select(component=alter_comp) + elif phase.upper() == 'S': + comps = 'NE' + for comp in comps: + alter_comp = COMPNAME_MAP[comp] + sel_st += st.select(component=comp) + sel_st += st.select(component=alter_comp) + else: + raise TypeError('Unknown phase label: {0}'.format(phase)) + return sel_st + + +def wadaticheck(pickdic, dttolerance, iplot): + ''' + Function to calculate Wadati-diagram from given P and S onsets in order + to detect S pick outliers. If a certain S-P time deviates by dttolerance + from regression of S-P time the S pick is marked and down graded. + + : param: pickdic, dictionary containing picks and quality parameters + : type: dictionary + + : param: dttolerance, maximum adjusted deviation of S-P time from + S-P time regression + : type: float + + : param: iplot, if iplot > 1, Wadati diagram is shown + : type: int + ''' + + checkedonsets = pickdic + + # search for good quality picks and calculate S-P time + Ppicks = [] + Spicks = [] + SPtimes = [] + for key in pickdic: + if pickdic[key]['P']['weight'] < 4 and pickdic[key]['S']['weight'] < 4: + # calculate S-P time + spt = pickdic[key]['S']['mpp'] - pickdic[key]['P']['mpp'] + # add S-P time to dictionary + pickdic[key]['SPt'] = spt + # add P onsets and corresponding S-P times to list + UTCPpick = UTCDateTime(pickdic[key]['P']['mpp']) + UTCSpick = UTCDateTime(pickdic[key]['S']['mpp']) + Ppicks.append(UTCPpick.timestamp) + Spicks.append(UTCSpick.timestamp) + SPtimes.append(spt) + + if len(SPtimes) >= 3: + # calculate slope + p1 = np.polyfit(Ppicks, SPtimes, 1) + wdfit = np.polyval(p1, Ppicks) + wfitflag = 0 + + # calculate vp/vs ratio before check + vpvsr = p1[0] + 1 + print ("###############################################") + print ("wadaticheck: Average Vp/Vs ratio before check: %f" % vpvsr) + + checkedPpicks = [] + checkedSpicks = [] + checkedSPtimes = [] + # calculate deviations from Wadati regression + ii = 0 + ibad = 0 + for key in pickdic: + if pickdic[key].has_key('SPt'): + wddiff = abs(pickdic[key]['SPt'] - wdfit[ii]) + ii += 1 + # check, if deviation is larger than adjusted + if wddiff > dttolerance: + # mark onset and downgrade S-weight to 9 + # (not used anymore) + marker = 'badWadatiCheck' + pickdic[key]['S']['weight'] = 9 + ibad += 1 + else: + marker = 'goodWadatiCheck' + checkedPpick = UTCDateTime(pickdic[key]['P']['mpp']) + checkedPpicks.append(checkedPpick.timestamp) + checkedSpick = UTCDateTime(pickdic[key]['S']['mpp']) + checkedSpicks.append(checkedSpick.timestamp) + checkedSPtime = pickdic[key]['S']['mpp'] - pickdic[key]['P']['mpp'] + checkedSPtimes.append(checkedSPtime) + + pickdic[key]['S']['marked'] = marker + + if len(checkedPpicks) >= 3: + # calculate new slope + p2 = np.polyfit(checkedPpicks, checkedSPtimes, 1) + wdfit2 = np.polyval(p2, checkedPpicks) + + # calculate vp/vs ratio after check + cvpvsr = p2[0] + 1 + print ("wadaticheck: Average Vp/Vs ratio after check: %f" % cvpvsr) + print ("wadatacheck: Skipped %d S pick(s)" % ibad) + else: + print ("###############################################") + print ("wadatacheck: Not enough checked S-P times available!") + print ("Skip Wadati check!") + + checkedonsets = pickdic + + else: + print ("wadaticheck: Not enough S-P times available for reliable regression!") + print ("Skip wadati check!") + wfitflag = 1 + + # plot results + if iplot > 1: + plt.figure(iplot) + f1, = plt.plot(Ppicks, SPtimes, 'ro') + if wfitflag == 0: + f2, = plt.plot(Ppicks, wdfit, 'k') + f3, = plt.plot(checkedPpicks, checkedSPtimes, 'ko') + f4, = plt.plot(checkedPpicks, wdfit2, 'g') + plt.title('Wadati-Diagram, %d S-P Times, Vp/Vs(raw)=%5.2f,' \ + 'Vp/Vs(checked)=%5.2f' % (len(SPtimes), vpvsr, cvpvsr)) + plt.legend([f1, f2, f3, f4], ['Skipped S-Picks', 'Wadati 1', + 'Reliable S-Picks', 'Wadati 2'], loc='best') + else: + plt.title('Wadati-Diagram, %d S-P Times' % len(SPtimes)) + + plt.ylabel('S-P Times [s]') + plt.xlabel('P Times [s]') + plt.show() + raw_input() + plt.close(iplot) + + return checkedonsets + + +def checksignallength(X, pick, TSNR, minsiglength, nfac, minpercent, iplot): + ''' + Function to detect spuriously picked noise peaks. + Uses RMS trace of all 3 components (if available) to determine, + how many samples [per cent] after P onset are below certain + threshold, calculated from noise level times noise factor. + + : param: X, time series (seismogram) + : type: `~obspy.core.stream.Stream` + + : param: pick, initial (AIC) P onset time + : type: float + + : param: TSNR, length of time windows around initial pick [s] + : type: tuple (T_noise, T_gap, T_signal) + + : param: minsiglength, minium required signal length [s] to + declare pick as P onset + : type: float + + : param: nfac, noise factor (nfac * noise level = threshold) + : type: float + + : param: minpercent, minimum required percentage of samples + above calculated threshold + : type: float + + : param: iplot, if iplot > 1, results are shown in figure + : type: int + ''' + + assert isinstance(X, Stream), "%s is not a stream object" % str(X) + + print ("Checking signal length ...") + + if len(X) > 1: + # all three components available + # make sure, all components have equal lengths + ilen = min([len(X[0].data), len(X[1].data), len(X[2].data)]) + x1 = X[0][0:ilen] + x2 = X[1][0:ilen] + x3 = X[2][0:ilen] + # get RMS trace + rms = np.sqrt((np.power(x1, 2) + np.power(x2, 2) + np.power(x3, 2)) / 3) + else: + x1 = X[0].data + rms = np.sqrt(np.power(2, x1)) + + t = np.arange(0, ilen / X[0].stats.sampling_rate, + X[0].stats.delta) + + # get noise window in front of pick plus saftey gap + inoise = getnoisewin(t, pick - 0.5, TSNR[0], TSNR[1]) + # get signal window + isignal = getsignalwin(t, pick, minsiglength) + # calculate minimum adjusted signal level + minsiglevel = max(rms[inoise]) * nfac + # minimum adjusted number of samples over minimum signal level + minnum = len(isignal) * minpercent / 100 + # get number of samples above minimum adjusted signal level + numoverthr = len(np.where(rms[isignal] >= minsiglevel)[0]) + + if numoverthr >= minnum: + print ("checksignallength: Signal reached required length.") + returnflag = 1 + else: + print ("checksignallength: Signal shorter than required minimum signal length!") + print ("Presumably picked noise peak, pick is rejected!") + print ("(min. signal length required: %s s)" % minsiglength) + returnflag = 0 + + if iplot == 2: + plt.figure(iplot) + p1, = plt.plot(t, rms, 'k') + p2, = plt.plot(t[inoise], rms[inoise], 'c') + p3, = plt.plot(t[isignal], rms[isignal], 'r') + p4, = plt.plot([t[isignal[0]], t[isignal[len(isignal) - 1]]], + [minsiglevel, minsiglevel], 'g', linewidth=2) + p5, = plt.plot([pick, pick], [min(rms), max(rms)], 'b', linewidth=2) + plt.legend([p1, p2, p3, p4, p5], ['RMS Data', 'RMS Noise Window', + 'RMS Signal Window', 'Minimum Signal Level', + 'Onset'], loc='best') + plt.xlabel('Time [s] since %s' % X[0].stats.starttime) + plt.ylabel('Counts') + plt.title('Check for Signal Length, Station %s' % X[0].stats.station) + plt.yticks([]) + plt.show() + raw_input() + plt.close(iplot) + + return returnflag + + +def checkPonsets(pickdic, dttolerance, iplot): + ''' + Function to check statistics of P-onset times: Control deviation from + median (maximum adjusted deviation = dttolerance) and apply pseudo- + bootstrapping jackknife. + + : param: pickdic, dictionary containing picks and quality parameters + : type: dictionary + + : param: dttolerance, maximum adjusted deviation of P-onset time from + median of all P onsets + : type: float + + : param: iplot, if iplot > 1, Wadati diagram is shown + : type: int + ''' + + checkedonsets = pickdic + + # search for good quality P picks + Ppicks = [] + stations = [] + for key in pickdic: + if pickdic[key]['P']['weight'] < 4: + # add P onsets to list + UTCPpick = UTCDateTime(pickdic[key]['P']['mpp']) + Ppicks.append(UTCPpick.timestamp) + stations.append(key) + + # apply jackknife bootstrapping on variance of P onsets + print ("###############################################") + print ("checkPonsets: Apply jackknife bootstrapping on P-onset times ...") + [xjack, PHI_pseudo, PHI_sub] = jackknife(Ppicks, 'VAR', 1) + # get pseudo variances smaller than average variances + # (times safety factor), these picks passed jackknife test + ij = np.where(PHI_pseudo <= 2 * xjack) + # these picks did not pass jackknife test + badjk = np.where(PHI_pseudo > 2 * xjack) + badjkstations = np.array(stations)[badjk] + print ("checkPonsets: %d pick(s) did not pass jackknife test!" % len(badjkstations)) + + # calculate median from these picks + pmedian = np.median(np.array(Ppicks)[ij]) + # find picks that deviate less than dttolerance from median + ii = np.where(abs(np.array(Ppicks)[ij] - pmedian) <= dttolerance) + jj = np.where(abs(np.array(Ppicks)[ij] - pmedian) > dttolerance) + igood = ij[0][ii] + ibad = ij[0][jj] + goodstations = np.array(stations)[igood] + badstations = np.array(stations)[ibad] + + print ("checkPonsets: %d pick(s) deviate too much from median!" % len(ibad)) + print ("checkPonsets: Skipped %d P pick(s) out of %d" % (len(badstations) \ + + len(badjkstations), len(stations))) + + goodmarker = 'goodPonsetcheck' + badmarker = 'badPonsetcheck' + badjkmarker = 'badjkcheck' + for i in range(0, len(goodstations)): + # mark P onset as checked and keep P weight + pickdic[goodstations[i]]['P']['marked'] = goodmarker + for i in range(0, len(badstations)): + # mark P onset and downgrade P weight to 9 + # (not used anymore) + pickdic[badstations[i]]['P']['marked'] = badmarker + pickdic[badstations[i]]['P']['weight'] = 9 + for i in range(0, len(badjkstations)): + # mark P onset and downgrade P weight to 9 + # (not used anymore) + pickdic[badjkstations[i]]['P']['marked'] = badjkmarker + pickdic[badjkstations[i]]['P']['weight'] = 9 + + checkedonsets = pickdic + + if iplot > 1: + p1, = plt.plot(np.arange(0, len(Ppicks)), Ppicks, 'r+', markersize=14) + p2, = plt.plot(igood, np.array(Ppicks)[igood], 'g*', markersize=14) + p3, = plt.plot([0, len(Ppicks) - 1], [pmedian, pmedian], 'g', + linewidth=2) + for i in range(0, len(Ppicks)): + plt.text(i, Ppicks[i] + 0.2, stations[i]) + + plt.xlabel('Number of P Picks') + plt.ylabel('Onset Time [s] from 1.1.1970') + plt.legend([p1, p2, p3], ['Skipped P Picks', 'Good P Picks', 'Median'], + loc='best') + plt.title('Check P Onsets') + plt.show() + raw_input() + + return checkedonsets + + +def jackknife(X, phi, h): + ''' + Function to calculate the Jackknife Estimator for a given quantity, + special type of boot strapping. Returns the jackknife estimator PHI_jack + the pseudo values PHI_pseudo and the subgroup parameters PHI_sub. + + : param: X, given quantity + : type: list + + : param: phi, chosen estimator, choose between: + "MED" for median + "MEA" for arithmetic mean + "VAR" for variance + : type: string + + : param: h, size of subgroups, optinal, default = 1 + : type: integer + ''' + + PHI_jack = None + PHI_pseudo = None + PHI_sub = None + + # determine number of subgroups + g = len(X) / h + + if type(g) is not int: + print ("jackknife: Cannot divide quantity X in equal sized subgroups!") + print ("Choose another size for subgroups!") + return PHI_jack, PHI_pseudo, PHI_sub + else: + # estimator of undisturbed spot check + if phi == 'MEA': + phi_sc = np.mean(X) + elif phi == 'VAR': + phi_sc = np.var(X) + elif phi == 'MED': + phi_sc = np.median(X) + + # estimators of subgroups + PHI_pseudo = [] + PHI_sub = [] + for i in range(0, g - 1): + # subgroup i, remove i-th sample + xx = X[:] + del xx[i] + # calculate estimators of disturbed spot check + if phi == 'MEA': + phi_sub = np.mean(xx) + elif phi == 'VAR': + phi_sub = np.var(xx) + elif phi == 'MED': + phi_sub = np.median(xx) + + PHI_sub.append(phi_sub) + # pseudo values + phi_pseudo = g * phi_sc - ((g - 1) * phi_sub) + PHI_pseudo.append(phi_pseudo) + # jackknife estimator + PHI_jack = np.mean(PHI_pseudo) + + return PHI_jack, PHI_pseudo, PHI_sub + + +def checkZ4S(X, pick, zfac, checkwin, iplot): + ''' + Function to compare energy content of vertical trace with + energy content of horizontal traces to detect spuriously + picked S onsets instead of P onsets. Usually, P coda shows + larger longitudal energy on vertical trace than on horizontal + traces, where the transversal energy is larger within S coda. + Be careful: there are special circumstances, where this is not + the case! + + : param: X, fitered(!) time series, three traces + : type: `~obspy.core.stream.Stream` + + : param: pick, initial (AIC) P onset time + : type: float + + : param: zfac, factor for threshold determination, + vertical energy must exceed coda level times zfac + to declare a pick as P onset + : type: float + + : param: checkwin, window length [s] for calculating P-coda + energy content + : type: float + + : param: iplot, if iplot > 1, energy content and threshold + are shown + : type: int + ''' + + assert isinstance(X, Stream), "%s is not a stream object" % str(X) + + print ("Check for spuriously picked S onset instead of P onset ...") + + returnflag = 0 + + # split components + zdat = X.select(component="Z") + if len(zdat) == 0: # check for other components + zdat = X.select(component="3") + edat = X.select(component="E") + if len(edat) == 0: # check for other components + edat = X.select(component="2") + ndat = X.select(component="N") + if len(ndat) == 0: # check for other components + ndat = X.select(component="1") + + z = zdat[0].data + tz = np.arange(0, zdat[0].stats.npts / zdat[0].stats.sampling_rate, + zdat[0].stats.delta) + + # calculate RMS trace from vertical component + absz = np.sqrt(np.power(z, 2)) + # calculate RMS trace from both horizontal traces + # make sure, both traces have equal lengths + lene = len(edat[0].data) + lenn = len(ndat[0].data) + minlen = min([lene, lenn]) + absen = np.sqrt(np.power(edat[0].data[0:minlen - 1], 2) \ + + np.power(ndat[0].data[0:minlen - 1], 2)) + + # get signal window + isignal = getsignalwin(tz, pick, checkwin) + + # calculate energy levels + zcodalevel = max(absz[isignal]) + encodalevel = max(absen[isignal]) + + # calculate threshold + minsiglevel = encodalevel * zfac + + # vertical P-coda level must exceed horizontal P-coda level + # zfac times encodalevel + if zcodalevel < minsiglevel: + print ("checkZ4S: Maybe S onset? Skip this P pick!") + else: + print ("checkZ4S: P onset passes checkZ4S test!") + returnflag = 1 + + if iplot > 1: + te = np.arange(0, edat[0].stats.npts / edat[0].stats.sampling_rate, + edat[0].stats.delta) + tn = np.arange(0, ndat[0].stats.npts / ndat[0].stats.sampling_rate, + ndat[0].stats.delta) + plt.plot(tz, z / max(z), 'k') + plt.plot(tz[isignal], z[isignal] / max(z), 'r') + plt.plot(te, edat[0].data / max(edat[0].data) + 1, 'k') + plt.plot(te[isignal], edat[0].data[isignal] / max(edat[0].data) + 1, 'r') + plt.plot(tn, ndat[0].data / max(ndat[0].data) + 2, 'k') + plt.plot(tn[isignal], ndat[0].data[isignal] / max(ndat[0].data) + 2, 'r') + plt.plot([tz[isignal[0]], tz[isignal[len(isignal) - 1]]], + [minsiglevel / max(z), minsiglevel / max(z)], 'g', + linewidth=2) + plt.xlabel('Time [s] since %s' % zdat[0].stats.starttime) + plt.ylabel('Normalized Counts') + plt.yticks([0, 1, 2], [zdat[0].stats.channel, edat[0].stats.channel, + ndat[0].stats.channel]) + plt.title('CheckZ4S, Station %s' % zdat[0].stats.station) + plt.show() + raw_input() + + return returnflag + + +if __name__ == '__main__': + import doctest + + doctest.testmod() diff --git a/pylot/core/util/__init__.py b/pylot/core/util/__init__.py new file mode 100755 index 00000000..128de997 --- /dev/null +++ b/pylot/core/util/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from pylot.core.util.version import get_git_version as _getVersionString diff --git a/pylot/core/util/connection.py b/pylot/core/util/connection.py new file mode 100644 index 00000000..9fafa46f --- /dev/null +++ b/pylot/core/util/connection.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import urllib2 + + +def checkurl(url='https://ariadne.geophysik.rub.de/trac/PyLoT'): + try: + urllib2.urlopen(url, timeout=1) + return True + except urllib2.URLError: + pass + return False diff --git a/pylot/core/util/dataprocessing.py b/pylot/core/util/dataprocessing.py new file mode 100644 index 00000000..147eaf78 --- /dev/null +++ b/pylot/core/util/dataprocessing.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import glob +import sys +from obspy.io.xseed import Parser + +import numpy as np + +from obspy import UTCDateTime, read_inventory, read +from obspy.io.xseed import Parser +from pylot.core.util.utils import key_for_set_value, find_in_list, \ + remove_underscores + + +def time_from_header(header): + """ + Function takes in the second line from a .gse file and takes out the date and time from that line. + :param header: second line from .gse file + :type header: string + :return: a list of integers of form [year, month, day, hour, minute, second, microsecond] + """ + timeline = header.split(' ') + time = timeline[1].split('/') + timeline[2].split(':') + time = time[:-1] + time[-1].split('.') + return [int(t) for t in time] + + +def check_time(datetime): + """ + Function takes in date and time as list and validates it's values by trying to make an UTCDateTime object from it + :param datetime: list of integers [year, month, day, hour, minute, second, microsecond] + :type datetime: list + :return: returns True if Values are in supposed range, returns False otherwise + + >>> check_time([1999, 01, 01, 23, 59, 59, 999000]) + True + >>> check_time([1999, 01, 01, 23, 59, 60, 999000]) + False + >>> check_time([1999, 01, 01, 23, 59, 59, 1000000]) + False + >>> check_time([1999, 01, 01, 23, 60, 59, 999000]) + False + >>> check_time([1999, 01, 01, 23, 60, 59, 999000]) + False + >>> check_time([1999, 01, 01, 24, 59, 59, 999000]) + False + >>> check_time([1999, 01, 31, 23, 59, 59, 999000]) + True + >>> check_time([1999, 02, 30, 23, 59, 59, 999000]) + False + >>> check_time([1999, 02, 29, 23, 59, 59, 999000]) + False + >>> check_time([2000, 02, 29, 23, 59, 59, 999000]) + True + >>> check_time([2000, 13, 29, 23, 59, 59, 999000]) + False + """ + try: + UTCDateTime(*datetime) + return True + except ValueError: + return False + + +def get_file_list(root_dir): + """ + Function uses a directorie to get all the *.gse files from it. + :param root_dir: a directorie leading to the .gse files + :type root_dir: string + :return: returns a list of filenames (without path to them) + """ + file_list = glob.glob1(root_dir, '*.gse') + return file_list + + +def checks_station_second(datetime, file): + """ + Function uses the given list to check if the parameter 'second' is set to 60 by mistake + and sets the time correctly if so. Can only correct time if no date change would be necessary. + :param datetime: [year, month, day, hour, minute, second, microsecond] + :return: returns the input with the correct value for second + """ + if datetime[5] == 60: + if datetime[4] == 59: + if datetime[3] == 23: + err_msg = 'Date should be next day. ' \ + 'File not changed: {0}'.format(file) + raise ValueError(err_msg) + else: + datetime[3] += 1 + datetime[4] = 0 + datetime[5] = 0 + else: + datetime[4] += 1 + datetime[5] = 0 + return datetime + + +def make_time_line(line, datetime): + """ + Function takes in the original line from a .gse file and a list of date and + time values to make a new line with corrected date and time. + :param line: second line from .gse file. + :type line: string + :param datetime: list of integers [year, month, day, hour, minute, second, microsecond] + :type datetime: list + :return: returns a string to write it into a file. + """ + ins_form = '{0:02d}:{1:02d}:{2:02d}.{3:03d}' + insertion = ins_form.format(int(datetime[3]), + int(datetime[4]), + int(datetime[5]), + int(datetime[6] * 1e-3)) + newline = line[:16] + insertion + line[28:] + return newline + + +def evt_head_check(root_dir, out_dir = None): + """ + A function to make sure that an arbitrary number of .gse files have correct values in their header. + :param root_dir: a directory leading to the .gse files. + :type root_dir: string + :param out_dir: a directory to store the new files somwhere els. + :return: returns nothing + """ + if not out_dir: + print('WARNING files are going to be overwritten!') + inp = str(raw_input('Continue? [y/N]')) + if not inp == 'y': + sys.exit() + filelist = get_file_list(root_dir) + nfiles = 0 + for file in filelist: + infile = open(os.path.join(root_dir, file), 'r') + lines = infile.readlines() + infile.close() + datetime = time_from_header(lines[1]) + if check_time(datetime): + continue + else: + nfiles += 1 + datetime = checks_station_second(datetime, file) + print('writing ' + file) + # write File + lines[1] = make_time_line(lines[1], datetime) + if not out_dir: + out = open(os.path.join(root_dir, file), 'w') + out.writelines(lines) + out.close() + else: + out = open(os.path.join(out_dir, file), 'w') + out.writelines(lines) + out.close() + print(nfiles) + + +def read_metadata(path_to_inventory): + """ + take path_to_inventory and return either the corresponding list of files + found or the Parser object for a network dataless seed volume to prevent + read overhead for large dataless seed volumes + :param path_to_inventory: + :return: tuple containing a either list of files or `obspy.io.xseed.Parser` + object and the inventory type found + :rtype: tuple + """ + dlfile = list() + invfile = list() + respfile = list() + inv = dict(dless=dlfile, xml=invfile, resp=respfile) + if os.path.isfile(path_to_inventory): + ext = os.path.splitext(path_to_inventory)[1].split('.')[1] + inv[ext] += [path_to_inventory] + else: + for ext in inv.keys(): + inv[ext] += glob.glob1(path_to_inventory, '*.{0}'.format(ext)) + + invtype = key_for_set_value(inv) + + if invtype is None: + raise IOError("Neither dataless-SEED file, inventory-xml file nor " + "RESP-file found!") + elif invtype == 'dless': # prevent multiple read of large dlsv + print("Reading metadata information from dataless-SEED file ...") + if len(inv[invtype]) == 1: + fullpath_inv = os.path.join(path_to_inventory, inv[invtype][0]) + robj = Parser(fullpath_inv) + else: + robj = inv[invtype] + else: + print("Reading metadata information from inventory-xml file ...") + robj = inv[invtype] + return invtype, robj + + +def restitute_data(data, invtype, inobj, unit='VEL', force=False): + """ + takes a data stream and a path_to_inventory and returns the corrected + waveform data stream + :param data: seismic data stream + :param invtype: type of found metadata + :param inobj: either list of metadata files or `obspy.io.xseed.Parser` + object + :param unit: unit to correct for (default: 'VEL') + :param force: force restitution for already corrected traces (default: + False) + :return: corrected data stream + """ + + restflag = list() + + data = remove_underscores(data) + + # loop over traces + for tr in data: + seed_id = tr.get_id() + # check, whether this trace has already been corrected + if 'processing' in tr.stats.keys() \ + and np.any(['remove' in p for p in tr.stats.processing]) \ + and not force: + print("Trace {0} has already been corrected!".format(seed_id)) + continue + stime = tr.stats.starttime + prefilt = get_prefilt(tr) + if invtype == 'resp': + fresp = find_in_list(inobj, seed_id) + if not fresp: + raise IOError('no response file found ' + 'for trace {0}'.format(seed_id)) + fname = fresp + seedresp = dict(filename=fname, + date=stime, + units=unit) + kwargs = dict(paz_remove=None, pre_filt=prefilt, seedresp=seedresp) + elif invtype == 'dless': + if type(inobj) is list: + fname = Parser(find_in_list(inobj, seed_id)) + else: + fname = inobj + seedresp = dict(filename=fname, + date=stime, + units=unit) + kwargs = dict(pre_filt=prefilt, seedresp=seedresp) + elif invtype == 'xml': + invlist = inobj + if len(invlist) > 1: + finv = find_in_list(invlist, seed_id) + else: + finv = invlist[0] + inventory = read_inventory(finv, format='STATIONXML') + else: + data.remove(tr) + continue + # apply restitution to data + try: + if invtype in ['resp', 'dless']: + tr.simulate(**kwargs) + else: + tr.attach_response(inventory) + tr.remove_response(output=unit, + pre_filt=prefilt) + except ValueError as e: + msg0 = 'Response for {0} not found in Parser'.format(seed_id) + msg1 = 'evalresp failed to calculate response' + if msg0 not in e.message or msg1 not in e.message: + raise + else: + # restitution done to copies of data thus deleting traces + # that failed should not be a problem + data.remove(tr) + continue + restflag.append(True) + # check if ALL traces could be restituted, take care of large datasets + # better try restitution for smaller subsets of data (e.g. station by + # station) + if len(restflag) > 0: + restflag = bool(np.all(restflag)) + else: + restflag = False + return data, restflag + + +def get_prefilt(trace, tlow=(0.5, 0.9), thi=(5., 2.), verbosity=0): + """ + takes a `obspy.core.stream.Trace` object, taper parameters tlow and thi and + returns the pre-filtering corner frequencies for the cosine taper for + further processing + :param trace: seismic data trace + :type trace: `obspy.core.stream.Trace` + :param tlow: tuple or list containing the desired lower corner + frequenices for a cosine taper + :type tlow: tuple or list + :param thi: tuple or list containing the percentage values of the + Nyquist frequency for the desired upper corner frequencies of the cosine + taper + :type thi: tuple or list + :param verbosity: verbosity level + :type verbosity: int + :return: pre-filt cosine taper corner frequencies + :rtype: tuple + + ..example:: + + >>> st = read() + >>> get_prefilt(st[0]) + (0.5, 0.9, 47.5, 49.0) + """ + if verbosity: + print("Calculating pre-filter values for %s, %s ..." % ( + trace.stats.station, trace.stats.channel)) + # get corner frequencies for pre-filtering + fny = trace.stats.sampling_rate / 2 + fc21 = fny - (fny * thi[0]/100.) + fc22 = fny - (fny * thi[1]/100.) + return (tlow[0], tlow[1], fc21, fc22) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/pylot/core/util/defaults.py b/pylot/core/util/defaults.py new file mode 100644 index 00000000..0e69bb5d --- /dev/null +++ b/pylot/core/util/defaults.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on Wed Feb 26 12:31:25 2014 + +@author: sebastianw +""" + +import os +from pylot.core.loc import nll +from pylot.core.loc import hsat +from pylot.core.loc import velest + + +def readFilterInformation(fname): + def convert2FreqRange(*args): + if len(args) > 1: + return [float(arg) for arg in args] + elif len(args) == 1: + return float(args[0]) + return None + + filter_file = open(fname, 'r') + filter_information = dict() + for filter_line in filter_file.readlines(): + filter_line = filter_line.split(' ') + for n, pos in enumerate(filter_line): + if pos == '\n': + filter_line[n] = '' + filter_information[filter_line[0]] = {'filtertype': filter_line[1] + if filter_line[1] + else None, + 'order': int(filter_line[2]) + if filter_line[1] + else None, + 'freq': convert2FreqRange(*filter_line[3:]) + if filter_line[1] + else None} + return filter_information + + +FILTERDEFAULTS = readFilterInformation(os.path.join(os.path.expanduser('~'), + '.pylot', + 'filter.in')) + +AUTOMATIC_DEFAULTS = os.path.join(os.path.expanduser('~'), + '.pylot', + 'autoPyLoT.in') + +TIMEERROR_DEFAULTS = os.path.join(os.path.expanduser('~'), + '.pylot', + 'PILOT_TimeErrors.in') + +OUTPUTFORMATS = {'.xml': 'QUAKEML', + '.cnv': 'CNV', + '.obs': 'NLLOC_OBS'} + +LOCTOOLS = dict(nll=nll, hsat=hsat, velest=velest) + +COMPPOSITION_MAP = dict(Z=2, N=1, E=0) +COMPPOSITION_MAP['1'] = 1 +COMPPOSITION_MAP['2'] = 0 +COMPPOSITION_MAP['3'] = 2 + +COMPNAME_MAP = dict(Z='3', N='1', E='2') diff --git a/pylot/core/util/errors.py b/pylot/core/util/errors.py new file mode 100644 index 00000000..8805b488 --- /dev/null +++ b/pylot/core/util/errors.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Mar 20 09:47:04 2014 + +@author: sebastianw +""" + + +class OptionsError(Exception): + pass + + +class FormatError(Exception): + pass + + +class DatastructureError(Exception): + pass + + +class OverwriteError(IOError): + pass + + +class ParameterError(Exception): + pass + +class ProcessingError(RuntimeError): + pass diff --git a/pylot/core/util/pdf.py b/pylot/core/util/pdf.py new file mode 100644 index 00000000..39174b38 --- /dev/null +++ b/pylot/core/util/pdf.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import warnings +import numpy as np +from obspy import UTCDateTime +from pylot.core.util.utils import fit_curve, find_nearest, clims +from pylot.core.util.version import get_git_version as _getVersionString + +__version__ = _getVersionString() +__author__ = 'sebastianw' + +def create_axis(x0, incr, npts): + ax = np.zeros(npts) + for i in range(npts): + ax[i] = x0 + incr * i + return ax + +def gauss_parameter(te, tm, tl, eta): + ''' + takes three onset times and returns the parameters sig1, sig2, a1 and a2 + to represent the pick as a probability density funtion (PDF) with two + Gauss branches + :param te: + :param tm: + :param tl: + :param eta: + :return: + ''' + + sig1 = (tm - te) / np.sqrt(2 * np.log(1 / eta)) + sig2 = (tl - tm) / np.sqrt(2 * np.log(1 / eta)) + + a1 = 2 / (1 + sig2 / sig1) + a2 = 2 / (1 + sig1 / sig2) + + return tm, sig1, sig2, a1, a2 + + +def exp_parameter(te, tm, tl, eta): + ''' + takes three onset times te, tm and tl and returns the parameters sig1, + sig2 and a to represent the pick as a probability density function (PDF) + with two exponential decay branches + :param te: + :param tm: + :param tl: + :param eta: + :return: + ''' + + sig1 = np.log(eta) / (te - tm) + sig2 = np.log(eta) / (tm - tl) + a = 1 / (1 / sig1 + 1 / sig2) + + return tm, sig1, sig2, a + + +def gauss_branches(k, (mu, sig1, sig2, a1, a2)): + ''' + function gauss_branches takes an axes x, a center value mu, two sigma + values sig1 and sig2 and two scaling factors a1 and a2 and return a + list containing the values of a probability density function (PDF) + consisting of gauss branches + :param x: + :type x: + :param mu: + :type mu: + :param sig1: + :type sig1: + :param sig2: + :type sig2: + :param a1: + :type a1: + :param a2: + :returns fun_vals: list with function values along axes x + ''' + + def _func(k, mu, sig1, sig2, a1, a2): + if k < mu: + rval = a1 * 1 / (np.sqrt(2 * np.pi) * sig1) * np.exp(-((k - mu) / sig1) ** 2 / 2) + else: + rval = a2 * 1 / (np.sqrt(2 * np.pi) * sig2) * np.exp(-((k - mu) / + sig2) ** 2 / 2) + return rval + + try: + return [_func(x, mu, sig1, sig2, a1, a2) for x in iter(k)] + except TypeError: + return _func(k, mu, sig1, sig2, a1, a2) + + +def exp_branches(k, (mu, sig1, sig2, a)): + ''' + function exp_branches takes an axes x, a center value mu, two sigma + values sig1 and sig2 and a scaling factor a and return a + list containing the values of a probability density function (PDF) + consisting of exponential decay branches + :param x: + :param mu: + :param sig1: + :param sig2: + :param a: + :returns fun_vals: list with function values along axes x: + ''' + + def _func(k, mu, sig1, sig2, a): + mu = float(mu) + if k < mu: + rval = a * np.exp(sig1 * (k - mu)) + else: + rval = a * np.exp(-sig2 * (k - mu)) + return rval + + try: + return [_func(x, mu, sig1, sig2, a) for x in iter(k)] + except TypeError: + return _func(k, mu, sig1, sig2, a) + + +# define container dictionaries for different types of pdfs +parameter = dict(gauss=gauss_parameter, exp=exp_parameter) +branches = dict(gauss=gauss_branches, exp=exp_branches) + + +class ProbabilityDensityFunction(object): + ''' + A probability density function toolkit. + ''' + + version = __version__ + + def __init__(self, x0, incr, npts, pdf, mu, params, eta=0.01): + self.x0 = x0 + self.incr = incr + self.npts = npts + self.axis = create_axis(x0, incr, npts) + self.mu = mu + self.eta = eta + self._pdf = pdf + self.params = params + + def __add__(self, other): + + assert self.eta == other.eta, 'decline factors differ please use equally defined pdfs for comparison' + + eta = self.eta + + x0, incr, npts = self.commonparameter(other) + + axis = create_axis(x0, incr, npts) + pdf_self = np.array(self.data(axis)) + pdf_other = np.array(other.data(axis)) + + pdf = np.convolve(pdf_self, pdf_other, 'full') * incr + + # shift axis values for correct plotting + npts = pdf.size + x0 *= 2 + axis = create_axis(x0, incr, npts) + mu = axis[np.where(pdf == max(pdf))][0] + + func, params = fit_curve(axis, pdf) + + return ProbabilityDensityFunction(x0, incr, npts, func, mu, + params, eta) + + def __sub__(self, other): + + assert self.eta == other.eta, 'decline factors differ please use equally defined pdfs for comparison' + + eta = self.eta + + x0, incr, npts = self.commonparameter(other) + + axis = create_axis(x0, incr, npts) + pdf_self = np.array(self.data(axis)) + pdf_other = np.array(other.data(axis)) + + pdf = np.correlate(pdf_self, pdf_other, 'full') * incr + + # shift axis values for correct plotting + npts = len(pdf) + midpoint = npts / 2 + x0 = -incr * midpoint + axis = create_axis(x0, incr, npts) + mu = axis[np.where(pdf == max(pdf))][0] + + func, params = fit_curve(axis, pdf) + + return ProbabilityDensityFunction(x0, incr, npts, func, mu, + params, eta) + + def __nonzero__(self): + prec = self.precision(self.incr) + data = np.array(self.data()) + gtzero = np.all(data >= 0) + probone = bool(np.round(self.prob_gt_val(self.axis[0]), prec) == 1.) + return bool(gtzero and probone) + + def __str__(self): + return str([self.data()]) + + @staticmethod + def precision(incr): + prec = int(np.ceil(np.abs(np.log10(incr)))) - 2 + return prec if prec >= 0 else 0 + + def data(self, value=None): + if value is None: + return self._pdf(self.axis, self.params) + return self._pdf(value, self.params) + + @property + def eta(self): + return self._eta + + @eta.setter + def eta(self, value): + self._eta = value + + @property + def mu(self): + return self._mu + + @mu.setter + def mu(self, mu): + self._mu = mu + + @property + def axis(self): + return self._x + + @axis.setter + def axis(self, x): + self._x = np.array(x) + + @classmethod + def from_pick(self, lbound, barycentre, rbound, incr=0.001, decfact=0.01, + type='gauss'): + ''' + Initialize a new ProbabilityDensityFunction object. + Takes incr, lbound, barycentre and rbound to derive x0 and the number + of points npts for the axis vector. + Maximum density + is given at the barycentre and on the boundaries the function has + declined to decfact times the maximum value. Integration of the + function over a particular interval gives the probability for the + variable value to be in that interval. + ''' + + # derive adequate window of definition + margin = 2. * np.max([barycentre - lbound, rbound - barycentre]) + + # find midpoint accounting also for `~obspy.UTCDateTime` object usage + try: + midpoint = (rbound + lbound) / 2 + except TypeError: + try: + midpoint = (rbound + float(lbound)) / 2 + except TypeError: + midpoint = float(rbound + float(lbound)) / 2 + + # find x0 on a grid point and sufficient npts + was_datetime = None + if isinstance(barycentre, UTCDateTime): + barycentre = float(barycentre) + was_datetime = True + n = int(np.ceil((barycentre - midpoint) / incr)) + m = int(np.ceil((margin / incr))) + midpoint = barycentre - n * incr + margin = m * incr + x0 = midpoint - margin + npts = 2 * m + + if was_datetime: + barycentre = UTCDateTime(barycentre) + + # calculate parameter for pdf representing function + params = parameter[type](lbound, barycentre, rbound, decfact) + + # select pdf type + pdf = branches[type] + + # return the object + return ProbabilityDensityFunction(x0, incr, npts, pdf, barycentre, + params, decfact) + + def broadcast(self, pdf, si, ei, data): + try: + pdf[si:ei] = data + except ValueError as e: + warnings.warn(str(e), Warning) + return self.broadcast(pdf, si, ei, data[:-1]) + return pdf + + def expectation(self): + ''' + returns the expectation value of the actual pdf object + + ..formula:: + mu_{\Delta t} = \int\limits_{-\infty}^\infty x \cdot f(x)dx + + :return float: rval + ''' + + rval = 0 + for x in self.axis: + rval += x * self.data(x) + return rval * self.incr + + def standard_deviation(self): + mu = self.mu + rval = 0 + for x in self.axis: + rval += (x - float(mu)) ** 2 * self.data(x) + return rval * self.incr + + def prob_lt_val(self, value): + if value <= self.axis[0] or value > self.axis[-1]: + raise ValueError('value out of bounds: {0}'.format(value)) + return self.prob_limits((self.axis[0], value)) + + def prob_gt_val(self, value): + if value < self.axis[0] or value >= self.axis[-1]: + raise ValueError('value out of bounds: {0}'.format(value)) + return self.prob_limits((value, self.axis[-1])) + + def prob_limits(self, limits, oversampling=1.): + sampling = self.incr / oversampling + lim = np.arange(limits[0], limits[1], sampling) + data = self.data(lim) + min_est, max_est = 0., 0. + for n in range(len(data) - 1): + min_est += min(data[n], data[n + 1]) + max_est += max(data[n], data[n + 1]) + return (min_est + max_est) / 2. * sampling + + def prob_val(self, value): + if not (self.axis[0] <= value <= self.axis[-1]): + Warning('{0} not on axis'.format(value)) + return None + return self.data(value) * self.incr + + def quantile(self, prob_value, eps=0.01): + ''' + + :param prob_value: + :param eps: + :return: + ''' + l = self.axis[0] + r = self.axis[-1] + m = (r + l) / 2 + diff = prob_value - self.prob_lt_val(m) + while abs(diff) > eps and ((r - l) > self.incr): + if diff > 0: + l = m + else: + r = m + m = (r + l) / 2 + diff = prob_value - self.prob_lt_val(m) + return m + + def quantile_distance(self, prob_value): + """ + takes a probability value and and returns the distance + between two complementary quantiles + + .. math:: + + QA_\alpha = Q(1 - \alpha) - Q(\alpha) + + :param value: probability value :math:\alpha + :type value: float + :return: quantile distance + """ + if 0 >= prob_value or prob_value >= 0.5: + raise ValueError('Value out of range.') + ql = self.quantile(prob_value) + qu = self.quantile(1 - prob_value) + return qu - ql + + + def quantile_dist_frac(self, x): + """ + takes a probability value and returns the fraction of two + corresponding quantile distances ( + :func:`pylot.core.util.pdf.ProbabilityDensityFunction + #quantile_distance`) + + .. math:: + + Q\Theta_\alpha = \frac{QA(0.5 - \alpha)}{QA(\alpha)} + + :param value: probability value :math:\alpha + :return: quantile distance fraction + """ + if x <= 0 or x >= 0.25: + raise ValueError('Value out of range.') + return self.quantile_distance(0.5-x)/self.quantile_distance(x) + + + def plot(self, label=None): + import matplotlib.pyplot as plt + + plt.plot(self.axis, self.data()) + plt.xlabel('x') + plt.ylabel('f(x)') + plt.autoscale(axis='x', tight=True) + if self: + title_str = 'Probability density function ' + if label: + title_str += label + title_str.strip() + else: + title_str = 'Function not suitable as probability density function' + plt.title(title_str) + plt.show() + + def limits(self): + l1 = self.x0 + r1 = l1 + self.incr * self.npts + + return l1, r1 + + def cincr(self, other): + if not self.incr == other.incr: + raise NotImplementedError( + 'Upsampling of the lower sampled PDF not implemented yet!') + else: + return self.incr + + def commonlimits(self, incr, other, max_npts=1e5): + ''' + Takes an increment incr and two left and two right limits and returns + the left most limit and the minimum number of points needed to cover + the whole given interval. + :param incr: + :param l1: + :param l2: + :param r1: + :param r2: + :param max_npts: + :return: + ''' + + x0, r = clims(self.limits(), other.limits()) + + # calculate index for rounding + ri = self.precision(incr) + + npts = int(round(r - x0, ri) // incr) + + if npts > max_npts: + raise ValueError('Maximum number of points exceeded:\n' + 'max_npts - %d\n' + 'npts - %d\n' % (max_npts, npts)) + + npts = np.max([npts, self.npts, other.npts]) + + if npts < self.npts or npts < other.npts: + raise ValueError('new npts is to small') + + return x0, npts + + def commonparameter(self, other): + assert isinstance(other, ProbabilityDensityFunction), \ + 'both operands must be of type ProbabilityDensityFunction' + + incr = self.cincr(other) + + x0, npts = self.commonlimits(incr, other) + + return x0, incr, npts + diff --git a/pylot/core/util/plotting.py b/pylot/core/util/plotting.py new file mode 100644 index 00000000..3efc3d86 --- /dev/null +++ b/pylot/core/util/plotting.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import matplotlib.pyplot as plt + +def create_bin_list(l_boundary, u_boundary, nbins=100): + """ + takes two boundaries and a number of bins and creates a list of bins for + histogram plotting + :param l_boundary: Any number. + :type l_boundary: float + :param u_boundary: Any number that is greater than l_boundary. + :type u_boundary: float + :param nbins: Any positive integer. + :type nbins: int + :return: A list of equidistant bins. + """ + if u_boundary <= l_boundary: + raise ValueError('Upper boundary must be greather than lower!') + elif nbins <= 0: + raise ValueError('Number of bins is not valid.') + binlist = [] + for i in range(nbins): + binlist.append(l_boundary + i * (u_boundary - l_boundary) / nbins) + return binlist + + +def histplot(array, binlist, xlab='Values', + ylab='Frequency', title=None, fnout=None): + """ + function to quickly show some distribution of data. Takes array like data, + and a list of bins. Editing detail and inserting a legend is not possible. + :param array: List of values. + :type array: Array like + :param binlist: List of bins. + :type binlist: list + :param xlab: A label for the x-axes. + :type xlab: str + :param ylab: A label for the y-axes. + :type ylab: str + :param title: A title for the Plot. + :type title: str + :param fnout: A path to save the plot instead of showing. + Has to contain filename and type. Like: 'path/to/file.png' + :type fnout. str + :return: - + """ + + plt.hist(array, bins=binlist) + plt.xlabel(xlab) + plt.ylabel(ylab) + if title: + plt.title(title) + if fnout: + plt.savefig(fnout) + else: + plt.show() \ No newline at end of file diff --git a/pylot/core/util/structure.py b/pylot/core/util/structure.py new file mode 100644 index 00000000..68a16552 --- /dev/null +++ b/pylot/core/util/structure.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Created on Wed Jan 26 17:47:25 2015 + +@author: sebastianw +""" + +from pylot.core.io.data import SeiscompDataStructure, PilotDataStructure + +DATASTRUCTURE = {'PILOT': PilotDataStructure, 'SeisComP': SeiscompDataStructure, + None: None} diff --git a/pylot/core/util/thread.py b/pylot/core/util/thread.py new file mode 100644 index 00000000..a9f5e7f6 --- /dev/null +++ b/pylot/core/util/thread.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +import sys +from PySide.QtCore import QThread, Signal + + +class AutoPickThread(QThread): + message = Signal(str) + finished = Signal() + + def __init__(self, parent, func, data, param): + super(AutoPickThread, self).__init__() + self.setParent(parent) + self.func = func + self.data = data + self.param = param + + def run(self): + sys.stdout = self + + picks = self.func(self.data, self.param) + + print("Autopicking finished!\n") + + try: + for station in picks: + self.parent().addPicks(station, picks[station], type='auto') + except AttributeError: + print(picks) + sys.stdout = sys.__stdout__ + self.finished.emit() + + def write(self, text): + self.message.emit(text) diff --git a/pylot/core/util/utils.py b/pylot/core/util/utils.py new file mode 100644 index 00000000..8b7f8dc8 --- /dev/null +++ b/pylot/core/util/utils.py @@ -0,0 +1,524 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import hashlib +import numpy as np +from scipy.interpolate import splrep, splev +import os +import pwd +import re +import subprocess +from obspy import UTCDateTime, read +from pylot.core.io.inputs import AutoPickParameter + + +def _pickle_method(m): + if m.im_self is None: + return getattr, (m.im_class, m.im_func.func_name) + else: + return getattr, (m.im_self, m.im_func.func_name) + +def fit_curve(x, y): + return splev, splrep(x, y) + +def getindexbounds(f, eta): + mi = f.argmax() + m = max(f) + b = m * eta + l = find_nearest(f[:mi], b) + u = find_nearest(f[mi:], b) + mi + return mi, l, u + +def worker(func, input, cores='max', async=False): + import multiprocessing + + if cores == 'max': + cores = multiprocessing.cpu_count() + + pool = multiprocessing.Pool(cores) + if async == True: + result = pool.map_async(func, input) + else: + result = pool.map(func, input) + pool.close() + return result + +def clims(lim1, lim2): + """ + takes two pairs of limits and returns one pair of common limts + :param lim1: + :param lim2: + :return: + + >>> clims([0, 4], [1, 3]) + [0, 4] + >>> clims([1, 4], [0, 3]) + [0, 4] + >>> clims([1, 3], [0, 4]) + [0, 4] + >>> clims([0, 3], [1, 4]) + [0, 4] + >>> clims([0, 3], [0, 4]) + [0, 4] + >>> clims([1, 4], [0, 4]) + [0, 4] + >>> clims([0, 4], [0, 4]) + [0, 4] + >>> clims([0, 4], [1, 4]) + [0, 4] + >>> clims([0, 4], [0, 3]) + [0, 4] + """ + lim = [None, None] + if lim1[0] < lim2[0]: + lim[0] = lim1[0] + else: + lim[0] = lim2[0] + if lim1[1] > lim2[1]: + lim[1] = lim1[1] + else: + lim[1] = lim2[1] + return lim + + +def demeanTrace(trace, window): + """ + takes a trace object and returns the same trace object but with data + demeaned within a certain time window + :param trace: waveform trace object + :type trace: `~obspy.core.stream.Trace` + :param window: + :type window: tuple + :return: trace + :rtype: `~obspy.core.stream.Trace` + """ + trace.data -= trace.data[window].mean() + return trace + + +def findComboBoxIndex(combo_box, val): + """ + Function findComboBoxIndex takes a QComboBox object and a string and + returns either 0 or the index throughout all QComboBox items. + :param combo_box: Combo box object. + :type combo_box: `~QComboBox` + :param val: Name of a combo box to search for. + :type val: basestring + :return: index value of item with name val or 0 + """ + return combo_box.findText(val) if combo_box.findText(val) is not -1 else 0 + +def find_in_list(list, str): + """ + takes a list of strings and a string and returns the first list item + matching the string pattern + :param list: list to search in + :param str: pattern to search for + :return: first list item containing pattern + + .. example:: + + >>> l = ['/dir/e1234.123.12', '/dir/e2345.123.12', 'abc123', 'def456'] + >>> find_in_list(l, 'dir') + '/dir/e1234.123.12' + >>> find_in_list(l, 'e1234') + '/dir/e1234.123.12' + >>> find_in_list(l, 'e2') + '/dir/e2345.123.12' + >>> find_in_list(l, 'ABC') + 'abc123' + >>> find_in_list(l, 'f456') + 'def456' + >>> find_in_list(l, 'gurke') + + """ + rlist = [s for s in list if str.lower() in s.lower()] + if rlist: + return rlist[0] + return None + +def find_nearest(array, value): + ''' + function find_nearest takes an array and a value and returns the + index of the nearest value found in the array + :param array: array containing values + :type array: `~numpy.ndarray` + :param value: number searched for + :return: index of the array item being nearest to the value + + >>> a = np.array([ 1.80339578, -0.72546654, 0.95769195, -0.98320759, 0.85922623]) + >>> find_nearest(a, 1.3) + 2 + >>> find_nearest(a, 0) + 1 + >>> find_nearest(a, 2) + 0 + >>> find_nearest(a, -1) + 3 + >>> a = np.array([ 1.1, -0.7, 0.9, -0.9, 0.8]) + >>> find_nearest(a, 0.849) + 4 + ''' + return (np.abs(array - value)).argmin() + + +def fnConstructor(s): + ''' + takes a string and returns a valid filename (especially on windows machines) + :param s: desired filename + :type s: str + :return: valid filename + ''' + if type(s) is str: + s = s.split(':')[-1] + else: + s = getHash(UTCDateTime()) + + badchars = re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^$') + badsuffix = re.compile(r'(aux|com[1-9]|con|lpt[1-9]|prn)(\.|$)') + + fn = badchars.sub('_', s) + + if badsuffix.match(fn): + fn = '_' + fn + return fn + + +def four_digits(year): + """ + takes a two digit year integer and returns the correct four digit equivalent + from the last 100 years + :param year: two digit year + :type year: int + :return: four digit year correspondant + + >>> four_digits(20) + 1920 + >>> four_digits(16) + 2016 + >>> four_digits(00) + 2000 + """ + if year + 2000 <= UTCDateTime.utcnow().year: + year += 2000 + else: + year += 1900 + return year + + +def common_range(stream): + ''' + takes a stream object and returns the earliest end and the latest start + time of all contained trace objects + :param stream: seismological data stream + :type stream: `~obspy.core.stream.Stream` + :return: maximum start time and minimum end time + ''' + max_start = None + min_end = None + for trace in stream: + if max_start is None or trace.stats.starttime > max_start: + max_start = trace.stats.starttime + if min_end is None or trace.stats.endtime < min_end: + min_end = trace.stats.endtime + return max_start, min_end + + +def full_range(stream): + ''' + takes a stream object and returns the latest end and the earliest start + time of all contained trace objects + :param stream: seismological data stream + :type stream: `~obspy.core.stream.Stream` + :return: minimum start time and maximum end time + ''' + min_start = UTCDateTime() + max_end = None + for trace in stream: + if trace.stats.starttime < min_start: + min_start = trace.stats.starttime + if max_end is None or trace.stats.endtime > max_end: + max_end = trace.stats.endtime + return min_start, max_end + + +def getHash(time): + ''' + takes a time object and returns the corresponding SHA1 hash of the + formatted date string + :param time: time object for which a hash should be calculated + :type time: :class: `~obspy.core.utcdatetime.UTCDateTime` object + :return: str + ''' + hg = hashlib.sha1() + hg.update(time.strftime('%Y-%m-%d %H:%M:%S.%f')) + return hg.hexdigest() + + +def getLogin(): + ''' + returns the actual user's login ID + :return: login ID + ''' + return pwd.getpwuid(os.getuid())[0] + + +def getOwner(fn): + ''' + takes a filename and return the login ID of the actual owner of the file + :param fn: filename of the file tested + :type fn: str + :return: login ID of the file's owner + ''' + return pwd.getpwuid(os.stat(fn).st_uid).pw_name + + +def getPatternLine(fn, pattern): + """ + takes a file name and a pattern string to search for in the file and + returns the first line which contains the pattern string otherwise 'None' + :param fn: file name + :type fn: str + :param pattern: pattern string to search for + :type pattern: str + :return: the complete line containing the pattern string or None + + >>> getPatternLine('utils.py', 'python') + '#!/usr/bin/env python\\n' + >>> print(getPatternLine('version.py', 'palindrome')) + None + """ + fobj = open(fn, 'r') + for line in fobj.readlines(): + if pattern in line: + fobj.close() + return line + + 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): + ''' + takes an iterable and returns 'True' if the items are in order otherwise + 'False' + :param iterable: an iterable object + :type iterable: + :return: Boolean + + >>> isSorted(1) + Traceback (most recent call last): + ... + AssertionError: object is not iterable; object: 1 + >>> isSorted([1,2,3,4]) + True + >>> isSorted('abcd') + True + >>> isSorted('bcad') + False + >>> isSorted([2,3,1,4]) + False + ''' + assert isIterable(iterable), 'object is not iterable; object: {' \ + '0}'.format(iterable) + if type(iterable) is str: + iterable = [s for s in iterable] + return sorted(iterable) == iterable + + +def isIterable(obj): + """ + takes a python object and returns 'True' is the object is iterable and + 'False' otherwise + :param obj: a python object + :return: True of False + """ + try: + iterator = iter(obj) + except TypeError as te: + return False + return True + + +def key_for_set_value(d): + """ + takes a dictionary and returns the first key for which's value the + boolean is True + :param d: dictionary containing values + :type d: dict + :return: key to the first non-False value found; None if no value's + boolean equals True + """ + r = None + for k, v in d.items(): + if v: + return k + return r + + +def prepTimeAxis(stime, trace): + ''' + takes a starttime and a trace object and returns a valid time axis for + plotting + :param stime: start time of the actual seismogram as UTCDateTime + :param trace: seismic trace object + :return: valid numpy array with time stamps for plotting + ''' + nsamp = trace.stats.npts + srate = trace.stats.sampling_rate + tincr = trace.stats.delta + etime = stime + nsamp / srate + time_ax = np.arange(stime, etime, tincr) + if len(time_ax) < nsamp: + print('elongate time axes by one datum') + time_ax = np.arange(stime, etime + tincr, tincr) + elif len(time_ax) > nsamp: + print('shorten time axes by one datum') + time_ax = np.arange(stime, etime - tincr, tincr) + if len(time_ax) != nsamp: + raise ValueError('{0} samples of data \n ' + '{1} length of time vector \n' + 'delta: {2}'.format(nsamp, len(time_ax), tincr)) + return time_ax + + +def find_horizontals(data): + """ + takes `obspy.core.stream.Stream` object and returns a list containing the component labels of the horizontal components available + :param data: waveform data + :type data: `obspy.core.stream.Stream` + :return: components list + :rtype: list + + ..example:: + + >>> st = read() + >>> find_horizontals(st) + [u'N', u'E'] + """ + rval = [] + for tr in data: + if tr.stats.channel[-1].upper() in ['Z', '3']: + continue + else: + rval.append(tr.stats.channel[-1].upper()) + return rval + + +def remove_underscores(data): + """ + takes a `obspy.core.stream.Stream` object and removes all underscores + from stationnames + :param data: stream of seismic data + :type data: `obspy.core.stream.Stream` + :return: data stream + """ + for tr in data: + # remove underscores + tr.stats.station = tr.stats.station.strip('_') + return data + + +def scaleWFData(data, factor=None, components='all'): + """ + produce scaled waveforms from given waveform data and a scaling factor, + waveform may be selected by their components name + :param data: waveform data to be scaled + :type data: `~obspy.core.stream.Stream` object + :param factor: scaling factor + :type factor: float + :param components: components labels for the traces in data to be scaled by + the scaling factor (optional, default: 'all') + :type components: tuple + :return: scaled waveform data + :rtype: `~obspy.core.stream.Stream` object + """ + if components is not 'all': + for comp in components: + if factor is None: + max_val = np.max(np.abs(data.select(component=comp)[0].data)) + data.select(component=comp)[0].data /= 2 * max_val + else: + data.select(component=comp)[0].data /= 2 * factor + else: + for tr in data: + if factor is None: + max_val = float(np.max(np.abs(tr.data))) + tr.data /= 2 * max_val + else: + tr.data /= 2 * factor + + return data + + +def runProgram(cmd, parameter=None): + """ + run an external program specified by cmd with parameters input returning the + stdout output + :param cmd: name of the command to run + :type cmd: str + :param parameter: filename of parameter file or parameter string + :type parameter: str + :return: stdout output + :rtype: str + """ + + if parameter: + cmd.strip() + cmd += ' %s 2>&1' % parameter + + subprocess.check_output('{} | tee /dev/stderr'.format(cmd), 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)) + bpath = os.path.join(os.path.expanduser('~'), '.pylot', 'autoPyLoT.in') + if os.path.exists(bpath): + nllocpath = ":" + AutoPickParameter(bpath).get('nllocbin') + os.environ['PATH'] += nllocpath + 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__": + import doctest + + doctest.testmod() diff --git a/pylot/core/util/version.py b/pylot/core/util/version.py new file mode 100755 index 00000000..c4006c12 --- /dev/null +++ b/pylot/core/util/version.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# Author: Douglas Creager +# This file is placed into the public domain. + +# Calculates the current version number. If possible, this is the +# output of “git describe”, modified to conform to the versioning +# scheme that setuptools uses. If “git describe” returns an error +# (most likely because we're in an unpacked copy of a release tarball, +# rather than in a git working copy), then we fall back on reading the +# contents of the RELEASE-VERSION file. +# +# To use this script, simply import it your setup.py file, and use the +# results of get_git_version() as your package version: +# +# from version import * +# +# setup( +# version=get_git_version(), +# . +# . +# . +# ) +# +# This will automatically update the RELEASE-VERSION file, if +# necessary. Note that the RELEASE-VERSION file should *not* be +# checked into git; please add it to your top-level .gitignore file. +# +# You'll probably want to distribute the RELEASE-VERSION file in your +# sdist tarballs; to do this, just create a MANIFEST.in file that +# contains the following line: +# +# include RELEASE-VERSION + +from __future__ import print_function + +__all__ = "get_git_version" + +# NO IMPORTS FROM PYLOT IN THIS FILE! (file gets used at installation time) +import os +import inspect +from subprocess import Popen, PIPE + +# NO IMPORTS FROM PYLOT IN THIS FILE! (file gets used at installation time) + +script_dir = os.path.abspath(os.path.dirname(inspect.getfile( + inspect.currentframe()))) +PYLOT_ROOT = os.path.abspath(os.path.join(script_dir, os.pardir, + os.pardir, os.pardir)) +VERSION_FILE = os.path.join(PYLOT_ROOT, "pylot", "RELEASE-VERSION") + + +def call_git_describe(abbrev=4): + try: + p = Popen(['git', 'rev-parse', '--show-toplevel'], + cwd=PYLOT_ROOT, stdout=PIPE, stderr=PIPE) + p.stderr.close() + path = p.stdout.readlines()[0].strip() + except: + return None + if os.path.normpath(path) != PYLOT_ROOT: + return None + try: + p = Popen(['git', 'describe', '--dirty', '--abbrev=%d' % abbrev, + '--always'], + cwd=PYLOT_ROOT, stdout=PIPE, stderr=PIPE) + p.stderr.close() + line = p.stdout.readlines()[0] + # (this line prevents official releases) + if "-" not in line and "." not in line: + line = "0.0.0-g%s" % line + return line.strip() + except: + return None + + +def read_release_version(): + try: + version = open(VERSION_FILE, "r").readlines()[0] + return version.strip() + except: + return None + + +def write_release_version(version): + open(VERSION_FILE, "w").write("%s\n" % version) + + +def get_git_version(abbrev=4): + # Read in the version that's currently in RELEASE-VERSION. + release_version = read_release_version() + + # First try to get the current version using “git describe”. + version = call_git_describe(abbrev) + + # If that doesn't work, fall back on the value that's in + # RELEASE-VERSION. + if version is None: + version = release_version + + # If we still don't have anything, that's an error. + if version is None: + return '0.0.0-tar/zipball' + + # If the current version is different from what's in the + # RELEASE-VERSION file, update the file to be current. + if version != release_version: + write_release_version(version) + + # Finally, return the current version. + return version + + +if __name__ == "__main__": + print(get_git_version()) diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py new file mode 100644 index 00000000..fc20e0c1 --- /dev/null +++ b/pylot/core/util/widgets.py @@ -0,0 +1,1636 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 19 11:27:35 2014 + +@author: sebastianw +""" + +import warnings +import copy +import datetime +import numpy as np + +from matplotlib.figure import Figure + +try: + from matplotlib.backends.backend_qt4agg import FigureCanvas +except ImportError: + from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT +from matplotlib.widgets import MultiCursor +from PySide.QtGui import QAction, QApplication, QCheckBox, QComboBox, \ + QDateTimeEdit, QDialog, QDialogButtonBox, QDoubleSpinBox, QGroupBox, \ + QGridLayout, QIcon, QKeySequence, QLabel, QLineEdit, QMessageBox, \ + QPixmap, QSpinBox, QTabWidget, QToolBar, QVBoxLayout, QWidget, \ + QPushButton, QFileDialog, QInputDialog +from PySide.QtCore import QSettings, Qt, QUrl, Signal, Slot +from PySide.QtWebKit import QWebView +from obspy import Stream, UTCDateTime +from pylot.core.io.inputs import FilterOptions +from pylot.core.pick.utils import getSNR, earllatepicker, getnoisewin, \ + getResolutionWindow +from pylot.core.pick.compare import Comparison +from pylot.core.util.defaults import OUTPUTFORMATS, FILTERDEFAULTS, LOCTOOLS, \ + COMPPOSITION_MAP +from pylot.core.util.utils import prepTimeAxis, full_range, scaleWFData, \ + demeanTrace, isSorted, findComboBoxIndex, clims +import icons_rc + +def getDataType(parent): + type = QInputDialog().getItem(parent, "Select phases type", "Type:", + ["manual", "automatic"]) + + if type[0].startswith('auto'): + type = 'auto' + else: + type = type[0] + + return type + +def plot_pdf(_axes, x, y, annotation, bbox_props, xlabel=None, ylabel=None, + title=None): + _axes.plot(x, y) + if title: + _axes.set_title(title) + if xlabel: + _axes.set_xlabel(xlabel) + if ylabel: + _axes.set_ylabel(ylabel) + _anno = _axes.annotate(annotation, xy=(.05, .5), xycoords='axes fraction') + _anno.set_bbox(bbox_props) + + return _axes + +def createAction(parent, text, slot=None, shortcut=None, icon=None, + tip=None, checkable=False): + """ + :rtype : ~PySide.QtGui.QAction + """ + action = QAction(text, parent) + if icon is not None: + action.setIcon(icon) + if shortcut is not None: + action.setShortcut(shortcut) + if tip is not None: + action.setToolTip(tip) + if slot is not None: + action.triggered.connect(slot) + if checkable: + action.setCheckable(True) + return action + +class ComparisonDialog(QDialog): + def __init__(self, c, parent=None): + self._data = c + self._stats = c.stations + self._canvas = PlotWidget(self) + self._widgets = dict(stationsComboBox=None, + phasesComboBox=None, + histCheckBox=None) + self._phases = 'PS' + self._plotprops = dict(station=self.stations[0], phase=self.phases[0]) + super(ComparisonDialog, self).__init__(parent) + self.setupUI() + self.plotcomparison() + + def setupUI(self): + + _outerlayout = QVBoxLayout(self) + _innerlayout = QVBoxLayout() + + _stats_combobox = QComboBox(self) + _stats_combobox.setObjectName('stationsComboBox') + _stats_combobox.setEditable(True) + _stats_combobox.setInsertPolicy(QComboBox.NoInsert) + _stats_combobox.addItems(sorted(self.stations)) + _stats_combobox.editTextChanged.connect(self.prepareplot) + self.widgets = _stats_combobox + + _phases_combobox = QComboBox(self) + _phases_combobox.setObjectName('phasesComboBox') + _phases_combobox.addItems(['P', 'S']) + _phases_combobox.currentIndexChanged.connect(self.prepareplot) + self.widgets = _phases_combobox + + _hist_checkbox = QCheckBox('Show histograms', self) + _hist_checkbox.setObjectName('histCheckBox') + _hist_checkbox.stateChanged.connect(self.plothist) + self.widgets = _hist_checkbox + + _toolbar = QToolBar(self) + _toolbar.addWidget(_stats_combobox) + _toolbar.addWidget(_phases_combobox) + _toolbar.addWidget(_hist_checkbox) + + _buttonbox = QDialogButtonBox(QDialogButtonBox.Close) + + _innerlayout.addWidget(self.canvas) + _innerlayout.addWidget(_buttonbox) + + _outerlayout.addWidget(_toolbar) + _outerlayout.addLayout(_innerlayout) + + _buttonbox.rejected.connect(self.reject) + + # finally layout the entire dialog + self.setLayout(_outerlayout) + + @property + def canvas(self): + return self._canvas + + @canvas.setter + def canvas(self, canvas_obj): + self._canvas = canvas_obj + + @property + def stations(self): + return self._stats + + @stations.setter + def stations(self, stations): + self._stats = stations + + @property + def phases(self): + return self._phases + + @phases.setter + def phases(self, value): + self._phases = value + + @property + def plotprops(self): + return self._plotprops + + @plotprops.setter + def plotprops(self, values): + try: + key, value = values + if key not in self.plotprops.keys(): + raise KeyError("'key' {0} not found in " + "ComparisonDialog.plotprops keys.".format(key)) + except ValueError: + raise ValueError("Pass an iterable with two items") + else: + self._plotprops[key] = value + + @property + def data(self): + return self._data + + @data.setter + def data(self, data): + assert not isinstance(data, Comparison) + self.stations = data.stations + self._data = data + + @property + def widgets(self): + return self._widgets + + @widgets.setter + def widgets(self, widget): + name = widget.objectName() + if name in self.widgets.keys(): + self._widgets[name] = widget + + def clf(self): + self.canvas.figure.clf() + + def hasvalue(self, sender): + text = sender.currentText() + index = sender.findText(text.upper()) + return index + + def prepareplot(self): + try: + _widget = self.sender() + name = _widget.objectName() + text = _widget.currentText().upper() + index = self.hasvalue(_widget) + if name == 'stationsComboBox' and index is not -1: + _widget.setCurrentIndex(index) + self.plotprops = ('station', text) + elif name == 'phasesComboBox': + self.plotprops = ('phase', text) + except ValueError: + raise ValueError('No sender widget given!') + finally: + self.plotcomparison() + + def plotcomparison(self): + from matplotlib import gridspec + + _gs = gridspec.GridSpec(3, 2) + self.clf() + _axes = self.canvas.figure.add_subplot(_gs[0:2, :]) + _ax1 = self.canvas.figure.add_subplot(_gs[2, 0]) + _ax2 = self.canvas.figure.add_subplot(_gs[2, 1]) + + #_axes.cla() + station = self.plotprops['station'] + phase = self.plotprops['phase'] + pdf = self.data.comparison[station][phase] + x, y, std, exp = pdf.axis, pdf.data, pdf.standard_deviation(), \ + pdf.expectation() + + annotation = "{phase} difference on {station}\n" \ + "expectation: {exp}\n" \ + "std: {std}".format(station=station, phase=phase, + std=std, exp=exp) + bbox_props = dict(boxstyle='round', facecolor='lightgrey', alpha=.7) + + plot_pdf(_axes, x, y, annotation, bbox_props, 'time difference [s]', + 'propability density [-]', phase) + + pdf_a = copy.deepcopy(self.data.get('auto')[station][phase]) + pdf_m = copy.deepcopy(self.data.get('manu')[station][phase]) + + xauto, yauto, stdauto, expauto, alim = pdf_a.axis, pdf_a.data, \ + pdf_a.standard_deviation(), \ + pdf_a.expectation(), \ + pdf_a.limits() + xmanu, ymanu, stdmanu, expmanu, mlim = pdf_m.axis, pdf_m.data, \ + pdf_m.standard_deviation(), \ + pdf_m.expectation(), \ + pdf_m.limits() + # find common limits + lims = clims(alim, mlim) + # relative x axis + x0 = lims[0] + xmanu -= x0 + xauto -= x0 + lims = [lim - x0 for lim in lims] + x0 = UTCDateTime(x0) + + # set annotation text + mannotation = "probability density for manual pick\n" \ + "expectation: {exp}\n" \ + "std: {std}".format(std=stdmanu, + exp=expmanu-x0.timestamp) + + aannotation = "probability density for automatic pick\n" \ + "expectation: {exp}\n" \ + "std: {std}".format(std=stdauto, + exp=expauto-x0.timestamp) + + _ax1 = plot_pdf(_ax1, xmanu, ymanu, mannotation, + bbox_props=bbox_props, xlabel='seconds since ' + '{0}'.format(x0), + ylabel='probability density [-]') + _ax1.set_xlim(lims) + + _ax2 = plot_pdf(_ax2, xauto, yauto, aannotation, + bbox_props=bbox_props, xlabel='seconds since ' + '{0}'.format(x0)) + _ax2.set_xlim(lims) + + _gs.update(wspace=0.5, hspace=0.5) + + self.canvas.draw() + + def plothist(self): + name = self.sender().objectName() + if self.widgets[name].isChecked(): + for wname, widget in self.widgets.items(): + if wname != name: + self.widgets[wname].setEnabled(False) + self.canvas.figure.clf() + _axPstd, _axPexp = self.canvas.figure.add_subplot(221), self.canvas.figure.add_subplot(223) + _axSstd, _axSexp = self.canvas.figure.add_subplot(222), self.canvas.figure.add_subplot(224) + axes_dict = dict(P=dict(std=_axPstd, exp=_axPexp), + S=dict(std=_axSstd, exp=_axSexp)) + bbox_props = dict(boxstyle='round', facecolor='lightgrey', alpha=.7) + for phase in self.phases: + std = self.data.get_std_array(phase) + std = std[np.isfinite(std)] + stdxlims = [0., 1.2 * max(std)] + exp = self.data.get_expectation_array(phase) + exp = exp[np.isfinite(exp)] + eps_exp = 0.05 * (max(exp) - min(exp)) + expxlims = [min(exp) - eps_exp, max(exp) + eps_exp] + axes_dict[phase]['std'].hist(std, range=stdxlims, bins=20, normed=False) + axes_dict[phase]['exp'].hist(exp, range=expxlims, bins=20, + normed=False) + std_annotation = "Distribution curve for {phase} differences'\n" \ + "standard deviations (all stations)\n" \ + "number of samples: {nsamples}".format(phase=phase, nsamples=len(std)) + _anno_std = axes_dict[phase]['std'].annotate(std_annotation, xy=(.05, .8), xycoords='axes fraction') + _anno_std.set_bbox(bbox_props) + exp_annotation = "Distribution curve for {phase} differences'\n" \ + "expectations (all stations)\n" \ + "number of samples: {nsamples}".format(phase=phase, nsamples=len(exp)) + _anno_exp = axes_dict[phase]['exp'].annotate(exp_annotation, xy=(.05, .8), xycoords='axes fraction') + _anno_exp.set_bbox(bbox_props) + axes_dict[phase]['exp'].set_xlabel('expectation [s]') + axes_dict[phase]['std'].set_xlabel('standard deviation [s]') + + for ax in axes_dict['P'].values(): + ax.set_ylabel('frequency [-]') + + self.canvas.draw() + else: + for wname, widget in self.widgets.items(): + if wname != name: + self.widgets[wname].setEnabled(True) + self.canvas.figure.clf() + self.plotcomparison() + + +class PlotWidget(FigureCanvas): + def __init__(self, parent=None, xlabel='x', ylabel='y', title='Title'): + self._parent = parent + self._fig = Figure() + self._xl = xlabel + self._yl = ylabel + self._title = title + super(PlotWidget, self).__init__(self.figure) + + @property + def figure(self): + return self._fig + + @figure.setter + def figure(self, fig): + self._fig = fig + + @property + def xlabel(self): + return self._xl + + @xlabel.setter + def xlabel(self, label): + self._xl = label + + @property + def ylabel(self): + return self._yl + + @ylabel.setter + def ylabel(self, label): + self._yl = label + + @property + def title(self): + return self._title + + @title.setter + def title(self, title): + self._title = title + + @property + def parent(self): + return self._parent + + +class WaveformWidget(FigureCanvas): + def __init__(self, parent=None, xlabel='x', ylabel='y', title='Title'): + + self._parent = None + self.setParent(parent) + self.figure = Figure() + self.figure.set_facecolor((.92, .92, .92)) + # attribute plotdict is an dictionary connecting position and a name + self.plotdict = dict() + # create axes + self.axes = self.figure.add_subplot(111) + # clear axes each time plot is called + self.axes.hold(True) + # initialize super class + super(WaveformWidget, self).__init__(self.figure) + # add an cursor for station selection + self.multiCursor = MultiCursor(self.figure.canvas, (self.axes,), + horizOn=True, + color='m', lw=1) + # update labels of the entire widget + self.updateWidget(xlabel, ylabel, title) + + def getPlotDict(self): + return self.plotdict + + def setPlotDict(self, key, value): + self.plotdict[key] = value + + def clearPlotDict(self): + self.plotdict = dict() + + def getParent(self): + return self._parent + + def setParent(self, parent): + self._parent = parent + + def plotWFData(self, wfdata, title=None, zoomx=None, zoomy=None, + noiselevel=None, scaleddata=False, mapping=True): + self.getAxes().cla() + self.clearPlotDict() + wfstart, wfend = full_range(wfdata) + nmax = 0 + for n, trace in enumerate(wfdata): + channel = trace.stats.channel + station = trace.stats.station + if mapping: + comp = channel[-1] + n = COMPPOSITION_MAP[comp] + if n > nmax: + nmax = n + msg = 'plotting %s channel of station %s' % (channel, station) + print(msg) + stime = trace.stats.starttime - wfstart + time_ax = prepTimeAxis(stime, trace) + if not scaleddata: + trace.normalize(np.max(np.abs(trace.data)) * 2) + self.getAxes().plot(time_ax, trace.data + n, 'k') + if noiselevel is not None: + for level in noiselevel: + self.getAxes().plot([time_ax[0], time_ax[-1]], + [level, level], '--k') + self.setPlotDict(n, (station, channel)) + xlabel = 'seconds since {0}'.format(wfstart) + ylabel = '' + self.updateWidget(xlabel, ylabel, title) + self.setXLims([0, wfend - wfstart]) + self.setYLims([-0.5, nmax + 0.5]) + if zoomx is not None: + self.setXLims(zoomx) + if zoomy is not None: + self.setYLims(zoomy) + self.draw() + + def getAxes(self): + return self.axes + + def getXLims(self): + return self.getAxes().get_xlim() + + def getYLims(self): + return self.getAxes().get_ylim() + + def setXLims(self, lims): + self.getAxes().set_xlim(lims) + + def setYLims(self, lims): + self.getAxes().set_ylim(lims) + + def setYTickLabels(self, pos, labels): + self.getAxes().set_yticks(pos) + self.getAxes().set_yticklabels(labels) + self.draw() + + def updateXLabel(self, text): + self.getAxes().set_xlabel(text) + self.draw() + + def updateYLabel(self, text): + self.getAxes().set_ylabel(text) + self.draw() + + def updateTitle(self, text): + self.getAxes().set_title(text) + self.draw() + + def updateWidget(self, xlabel, ylabel, title): + self.updateXLabel(xlabel) + self.updateYLabel(ylabel) + self.updateTitle(title) + + def insertLabel(self, pos, text): + pos = pos / max(self.getAxes().ylim) + axann = self.getAxes().annotate(text, xy=(.03, pos), + xycoords='axes fraction') + axann.set_bbox(dict(facecolor='lightgrey', alpha=.6)) + + +class PickDlg(QDialog): + def __init__(self, parent=None, data=None, station=None, picks=None, + rotate=False): + super(PickDlg, self).__init__(parent) + + # initialize attributes + self.station = station + self.rotate = rotate + self.components = 'ZNE' + settings = QSettings() + self._user = settings.value('user/Login', 'anonymous') + if picks: + self.picks = picks + else: + self.picks = {} + self.filteroptions = FILTERDEFAULTS + self.pick_block = False + + # initialize panning attributes + self.press = None + self.xpress = None + self.ypress = None + self.cur_xlim = None + self.cur_ylim = None + + # set attribute holding data + if data is None: + try: + data = parent.get_data().getWFData().copy() + self.data = data.select(station=station) + except AttributeError as e: + errmsg = 'You either have to put in a data or an appropriate ' \ + 'parent (PyLoT MainWindow) object: {0}'.format(e) + raise Exception(errmsg) + else: + self.data = data + + self.stime, self.etime = full_range(self.getWFData()) + + # initialize plotting widget + self.multicompfig = WaveformWidget(self) + + # setup ui + self.setupUi() + + # plot data + self.getPlotWidget().plotWFData(wfdata=self.getWFData(), + title=self.getStation()) + + xlims = self.getPlotWidget().getXLims() + ylims = self.getPlotWidget().getYLims() + + self.limits = {'x': xlims, + 'y': ylims} + + self.updateCurrentLimits() + + # set plot labels + self.setPlotLabels() + + # draw picks if present + self.drawPicks() + + # connect button press event to an action + self.cidpress = self.connectPressEvent(self.panPress) + self.cidmotion = self.connectMotionEvent(self.panMotion) + self.cidrelease = self.connectReleaseEvent(self.panRelease) + self.cidscroll = self.connectScrollEvent(self.scrollZoom) + + def setupUi(self): + + # create matplotlib toolbar to inherit functionality + self.figToolBar = NavigationToolbar2QT(self.getPlotWidget(), self) + self.figToolBar.hide() + + # create icons + filter_icon = QIcon() + filter_icon.addPixmap(QPixmap(':/icons/filter.png')) + zoom_icon = QIcon() + zoom_icon.addPixmap(QPixmap(':/icons/zoom_in.png')) + home_icon = QIcon() + home_icon.addPixmap(QPixmap(':/icons/zoom_0.png')) + del_icon = QIcon() + del_icon.addPixmap(QPixmap(':/icons/delete.png')) + + # create actions + self.filterAction = createAction(parent=self, text='Filter', + slot=self.filterWFData, + icon=filter_icon, + tip='Toggle filtered/original' + ' waveforms') + self.zoomAction = createAction(parent=self, text='Zoom', + slot=self.zoom, icon=zoom_icon, + tip='Zoom into waveform', + checkable=True) + self.resetZoomAction = createAction(parent=self, text='Home', + slot=self.resetZoom, icon=home_icon, + tip='Reset zoom to original limits') + self.resetPicksAction = createAction(parent=self, text='Delete Picks', + slot=self.delPicks, icon=del_icon, + tip='Delete current picks.') + + # create other widget elements + self.selectPhase = QComboBox() + phaseitems = [None] + FILTERDEFAULTS.keys() + self.selectPhase.addItems(phaseitems) + + # layout the outermost appearance of the Pick Dialog + _outerlayout = QVBoxLayout() + _dialtoolbar = QToolBar() + + # fill toolbar with content + _dialtoolbar.addAction(self.filterAction) + _dialtoolbar.addWidget(self.selectPhase) + _dialtoolbar.addAction(self.zoomAction) + _dialtoolbar.addSeparator() + _dialtoolbar.addAction(self.resetZoomAction) + _dialtoolbar.addSeparator() + _dialtoolbar.addAction(self.resetPicksAction) + + # layout the innermost widget + _innerlayout = QVBoxLayout() + _innerlayout.addWidget(self.multicompfig) + + # add button box to the dialog + _buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + + # merge widgets and layouts to establish the dialog + _innerlayout.addWidget(_buttonbox) + _outerlayout.addWidget(_dialtoolbar) + _outerlayout.addLayout(_innerlayout) + + # connect widget element signals with slots (methods to the dialog + # object + self.selectPhase.currentIndexChanged.connect(self.verifyPhaseSelection) + _buttonbox.accepted.connect(self.accept) + _buttonbox.rejected.connect(self.reject) + + # finally layout the entire dialog + self.setLayout(_outerlayout) + + def disconnectPressEvent(self): + widget = self.getPlotWidget() + widget.mpl_disconnect(self.cidpress) + self.cidpress = None + + def connectPressEvent(self, slot): + widget = self.getPlotWidget() + return widget.mpl_connect('button_press_event', slot) + + def disconnectScrollEvent(self): + widget = self.getPlotWidget() + widget.mpl_disconnect(self.cidscroll) + self.cidscroll = None + + def connectScrollEvent(self, slot): + widget = self.getPlotWidget() + return widget.mpl_connect('scroll_event', slot) + + def disconnectMotionEvent(self): + widget = self.getPlotWidget() + widget.mpl_disconnect(self.cidmotion) + self.cidmotion = None + + def connectMotionEvent(self, slot): + widget = self.getPlotWidget() + return widget.mpl_connect('motion_notify_event', slot) + + def disconnectReleaseEvent(self): + widget = self.getPlotWidget() + widget.mpl_disconnect(self.cidrelease) + self.cidrelease = None + + def connectReleaseEvent(self, slot): + widget = self.getPlotWidget() + return widget.mpl_connect('button_release_event', slot) + + def verifyPhaseSelection(self): + if self.pick_block: + self.pick_block = self.togglePickBlocker() + warnings.warn('Changed selection before phase was set!', + UserWarning) + phase = self.selectPhase.currentText() + self.updateCurrentLimits() + if phase: + if self.zoomAction.isChecked(): + self.zoomAction.trigger() + self.disconnectReleaseEvent() + self.disconnectScrollEvent() + self.disconnectMotionEvent() + self.disconnectPressEvent() + self.cidpress = self.connectPressEvent(self.setIniPick) + self.filterWFData() + self.pick_block = self.togglePickBlocker() + else: + self.disconnectPressEvent() + self.cidpress = self.connectPressEvent(self.panPress) + self.cidmotion = self.connectMotionEvent(self.panMotion) + self.cidrelease = self.connectReleaseEvent(self.panRelease) + self.cidscroll = self.connectScrollEvent(self.scrollZoom) + + def getStartTime(self): + return self.stime + + def getEndTime(self): + return self.etime + + def getComponents(self): + return self.components + + def getStation(self): + return self.station + + def getPlotWidget(self): + return self.multicompfig + + def getChannelID(self, key): + return self.getPlotWidget().getPlotDict()[int(key)][1] + + def getTraceID(self, channels): + plotDict = self.getPlotWidget().getPlotDict() + traceIDs = [] + for channel in channels: + channel = channel.upper() + for traceID, channelID in plotDict.items(): + if channelID[1].upper().endswith(channel): + traceIDs.append(traceID) + return traceIDs + + def getUser(self): + return self._user + + def getFilterOptions(self, phase): + options = self.filteroptions[phase] + return FilterOptions(**options) + + def getXLims(self): + return self.cur_xlim + + def getYLims(self): + return self.cur_ylim + + def setXLims(self, limits): + self.cur_xlim = limits + + def setYLims(self, limits): + self.cur_ylim = limits + + def getGlobalLimits(self, axis): + return self.limits[axis] + + def updateCurrentLimits(self): + self.setXLims(self.getPlotWidget().getXLims()) + self.setYLims(self.getPlotWidget().getYLims()) + + def getWFData(self): + return self.data + + def selectWFData(self, channel): + component = channel[-1].upper() + wfdata = Stream() + + def selectTrace(tr, components): + if tr.stats.channel[-1].upper() in components: + return tr + + if component == 'E' or component == 'N': + for trace in self.getWFData(): + trace = selectTrace(trace, 'NE') + if trace: + wfdata.append(trace) + elif component == '1' or component == '2': + for trace in self.getWFData(): + trace = selectTrace(trace, '12') + if trace: + wfdata.append(trace) + else: + wfdata = self.getWFData().select(component=component) + return wfdata + + def getPicks(self): + return self.picks + + def resetPicks(self): + self.picks = {} + + def delPicks(self): + self.resetPicks() + self.resetPlot() + + def setIniPick(self, gui_event): + + trace_number = round(gui_event.ydata) + + channel = self.getChannelID(trace_number) + wfdata = self.selectWFData(channel) + + self.disconnectScrollEvent() + self.disconnectPressEvent() + self.disconnectReleaseEvent() + self.disconnectMotionEvent() + self.cidpress = self.connectPressEvent(self.setPick) + + if self.selectPhase.currentText().upper().startswith('P'): + self.setIniPickP(gui_event, wfdata, trace_number) + elif self.selectPhase.currentText().upper().startswith('S'): + self.setIniPickS(gui_event, wfdata) + + self.zoomAction.setEnabled(False) + + # reset labels + self.setPlotLabels() + self.draw() + + def setIniPickP(self, gui_event, wfdata, trace_number): + + ini_pick = gui_event.xdata + + settings = QSettings() + + nfac = settings.value('picking/nfac_P', 1.3) + noise_win = settings.value('picking/noise_win_P', 5.) + gap_win = settings.value('picking/gap_win_P', .2) + signal_win = settings.value('picking/signal_win_P', 3.) + itrace = int(trace_number) + + while itrace > len(wfdata) - 1: + itrace -= 1 + + # copy data for plotting + data = self.getWFData().copy() + + # filter data and trace on which is picked prior to determination of SNR + phase = self.selectPhase.currentText() + filteroptions = self.getFilterOptions(phase).parseFilterOptions() + if filteroptions: + data.filter(**filteroptions) + wfdata.filter(**filteroptions) + + result = getSNR(wfdata, (noise_win, gap_win, signal_win), ini_pick, itrace) + + snr = result[0] + noiselevel = result[2] * nfac + + x_res = getResolutionWindow(snr) + + # remove mean noise level from waveforms + for trace in data: + t = prepTimeAxis(trace.stats.starttime - self.getStartTime(), trace) + inoise = getnoisewin(t, ini_pick, noise_win, gap_win) + trace = demeanTrace(trace=trace, window=inoise) + + self.setXLims([ini_pick - x_res, ini_pick + x_res]) + self.setYLims(np.array([-noiselevel * 2.5, noiselevel * 2.5]) + + trace_number) + self.getPlotWidget().plotWFData(wfdata=data, + title=self.getStation() + + ' picking mode', + zoomx=self.getXLims(), + zoomy=self.getYLims(), + noiselevel=(trace_number + noiselevel, + trace_number - noiselevel)) + + def setIniPickS(self, gui_event, wfdata): + + ini_pick = gui_event.xdata + + settings = QSettings() + + nfac = settings.value('picking/nfac_S', 1.5) + noise_win = settings.value('picking/noise_win_S', 5.) + gap_win = settings.value('picking/gap_win_S', .2) + signal_win = settings.value('picking/signal_win_S', 3.) + + # copy data for plotting + data = self.getWFData().copy() + + # filter data and trace on which is picked prior to determination of SNR + phase = self.selectPhase.currentText() + filteroptions = self.getFilterOptions(phase).parseFilterOptions() + if filteroptions: + data.filter(**filteroptions) + wfdata.filter(**filteroptions) + + # determine SNR and noiselevel + result = getSNR(wfdata, (noise_win, gap_win, signal_win), ini_pick) + snr = result[0] + noiselevel = result[2] * nfac + + # prepare plotting of data + for trace in data: + t = prepTimeAxis(trace.stats.starttime - self.getStartTime(), trace) + inoise = getnoisewin(t, ini_pick, noise_win, gap_win) + trace = demeanTrace(trace, inoise) + + # scale waveform for plotting + horiz_comp = find_horizontals(data) + data = scaleWFData(data, noiselevel * 2.5, horiz_comp) + + x_res = getResolutionWindow(snr) + + self.setXLims(tuple([ini_pick - x_res, ini_pick + x_res])) + traces = self.getTraceID(horiz_comp) + traces.sort() + self.setYLims(tuple(np.array([-0.5, +0.5]) + + np.array(traces))) + noiselevels = [trace + 1 / (2.5 * 2) for trace in traces] + \ + [trace - 1 / (2.5 * 2) for trace in traces] + + self.getPlotWidget().plotWFData(wfdata=data, + title=self.getStation() + + ' picking mode', + zoomx=self.getXLims(), + zoomy=self.getYLims(), + noiselevel=noiselevels, + scaleddata=True) + + def setPick(self, gui_event): + + # get axes limits + self.updateCurrentLimits() + + # setting pick + pick = gui_event.xdata # get pick time relative to the traces timeaxis not to the global + channel = self.getChannelID(round(gui_event.ydata)) + + # get name of phase actually picked + phase = self.selectPhase.currentText() + + # get filter parameter for the phase to be picked + filteroptions = self.getFilterOptions(phase).parseFilterOptions() + + # copy and filter data for earliest and latest possible picks + wfdata = self.getWFData().copy().select(channel=channel) + if filteroptions: + wfdata.filter(**filteroptions) + + # get earliest and latest possible pick and symmetric pick error + [epp, lpp, spe] = earllatepicker(wfdata, 1.5, (5., .5, 2.), pick) + + # return absolute time values for phases + stime = self.getStartTime() + epp = stime + epp + mpp = stime + pick + lpp = stime + lpp + + # save pick times for actual phase + phasepicks = dict(epp=epp, lpp=lpp, mpp=mpp, spe=spe, + picker=self.getUser()) + + try: + oldphasepick = self.picks[phase] + except KeyError: + self.picks[phase] = phasepicks + else: + self.picks[phase] = phasepicks + oepp = oldphasepick['epp'] + ompp = oldphasepick['mpp'] + olpp = oldphasepick['lpp'] + msg = """Warning old phase information for phase {phase} has been + altered.\n + New phase times:\n + earliest possible pick: {epp}\n + most probable pick: {mpp}\n + latest possible pick: {lpp}\n + \n + Old phase times (overwritten):\n + earliest possible pick: {oepp}\n + most probable pick: {ompp}\n + latest possible pick: {olpp}\n""".format(phase=phase, + epp=epp, + mpp=pick, + lpp=lpp, + oepp=oepp, + ompp=ompp, + olpp=olpp) + self.getPlotWidget().plotWFData(wfdata=self.getWFData(), + title=self.getStation()) + self.drawPicks() + self.disconnectPressEvent() + self.zoomAction.setEnabled(True) + self.pick_block = self.togglePickBlocker() + self.selectPhase.setCurrentIndex(-1) + self.setPlotLabels() + + def drawPicks(self, phase=None): + # plotting picks + ax = self.getPlotWidget().axes + ylims = self.getGlobalLimits('y') + phase_col = {'P': ('c', 'c--', 'b-'), + 'S': ('m', 'm--', 'r-')} + if self.getPicks(): + if phase is not None and type(self.getPicks()[phase]) is dict: + picks = self.getPicks()[phase] + colors = phase_col[phase[0].upper()] + elif phase is None: + for phase in self.getPicks(): + self.drawPicks(phase) + return + else: + return + else: + return + + mpp = picks['mpp'] - self.getStartTime() + epp = picks['epp'] - self.getStartTime() + lpp = picks['lpp'] - self.getStartTime() + spe = picks['spe'] + + ax.fill_between([epp, lpp], ylims[0], ylims[1], + alpha=.5, color=colors[0]) + ax.plot([mpp - spe, mpp - spe], ylims, colors[1], + [mpp, mpp], ylims, colors[2], + [mpp + spe, mpp + spe], ylims, colors[1]) + + def panPress(self, gui_event): + ax = self.getPlotWidget().axes + if gui_event.inaxes != ax: return + self.cur_xlim = ax.get_xlim() + self.cur_ylim = ax.get_ylim() + self.press = gui_event.xdata, gui_event.ydata + self.xpress, self.ypress = self.press + + def panRelease(self, gui_event): + ax = self.getPlotWidget().axes + self.press = None + ax.figure.canvas.draw() + + def panMotion(self, gui_event): + if self.press is None: return + ax = self.getPlotWidget().axes + if gui_event.inaxes != ax: return + dx = gui_event.xdata - self.xpress + dy = gui_event.ydata - self.ypress + self.cur_xlim -= dx + self.cur_ylim -= dy + ax.set_xlim(self.cur_xlim) + ax.set_ylim(self.cur_ylim) + + ax.figure.canvas.draw() + + def togglePickBlocker(self): + return not self.pick_block + + def filterWFData(self): + if self.pick_block: + return + self.updateCurrentLimits() + data = self.getWFData().copy() + old_title = self.getPlotWidget().getAxes().get_title() + title = None + phase = self.selectPhase.currentText() + filtoptions = None + if phase: + filtoptions = self.getFilterOptions(phase).parseFilterOptions() + if self.filterAction.isChecked(): + if not phase: + filtoptions = FilterOptionsDialog.getFilterObject() + filtoptions = filtoptions.parseFilterOptions() + if filtoptions is not None: + data.filter(**filtoptions) + if not old_title.endswith(')'): + title = old_title + ' (filtered)' + elif not old_title.endswith(' (filtered)') and not old_title.endswith(', filtered)'): + title = old_title[:-1] + ', filtered)' + else: + if old_title.endswith(' (filtered)'): + title = old_title.replace(' (filtered)', '') + elif old_title.endswith(', filtered)'): + title = old_title.replace(', filtered)', ')') + if title is None: + title = old_title + self.getPlotWidget().plotWFData(wfdata=data, title=title, + zoomx=self.getXLims(), + zoomy=self.getYLims()) + self.setPlotLabels() + self.drawPicks() + self.draw() + + def resetPlot(self): + self.updateCurrentLimits() + data = self.getWFData().copy() + title = self.getPlotWidget().getAxes().get_title() + self.getPlotWidget().plotWFData(wfdata=data, title=title, + zoomx=self.getXLims(), + zoomy=self.getYLims()) + self.setPlotLabels() + self.drawPicks() + self.draw() + + def setPlotLabels(self): + + # get channel labels + pos = self.getPlotWidget().getPlotDict().keys() + labels = [self.getPlotWidget().getPlotDict()[key][1] for key in pos] + + # set channel labels + self.getPlotWidget().setYTickLabels(pos, labels) + self.getPlotWidget().setXLims(self.getXLims()) + self.getPlotWidget().setYLims(self.getYLims()) + + def zoom(self): + if self.zoomAction.isChecked() and self.pick_block: + self.zoomAction.setChecked(False) + elif self.zoomAction.isChecked(): + self.disconnectPressEvent() + self.disconnectMotionEvent() + self.disconnectReleaseEvent() + self.disconnectScrollEvent() + self.figToolBar.zoom() + else: + self.figToolBar.zoom() + self.cidpress = self.connectPressEvent(self.panPress) + self.cidmotion = self.connectMotionEvent(self.panMotion) + self.cidrelease = self.connectReleaseEvent(self.panRelease) + self.cidscroll = self.connectScrollEvent(self.scrollZoom) + + def scrollZoom(self, gui_event, factor=2.): + + self.updateCurrentLimits() + + if gui_event.button == 'up': + scale_factor = 1 / factor + elif gui_event.button == 'down': + # deal with zoom out + scale_factor = factor + else: + # deal with something that should never happen + scale_factor = 1 + print(gui_event.button) + + new_xlim = gui_event.xdata - \ + scale_factor * (gui_event.xdata - self.getXLims()) + new_ylim = gui_event.ydata - \ + scale_factor * (gui_event.ydata - self.getYLims()) + + new_xlim.sort() + global_x = self.getGlobalLimits('x') + global_y = self.getGlobalLimits('y') + new_xlim[0] = max(new_xlim[0], global_x[0]) + new_xlim[1] = min(new_xlim[1], global_x[1]) + new_ylim.sort() + new_ylim[0] = max(new_ylim[0], global_y[0]) + new_ylim[1] = min(new_ylim[1], global_y[1]) + + self.getPlotWidget().setXLims(new_xlim) + self.getPlotWidget().setYLims(new_ylim) + self.draw() + + def resetZoom(self): + self.getPlotWidget().setXLims(self.getGlobalLimits('x')) + self.getPlotWidget().setYLims(self.getGlobalLimits('y')) + self.draw() + + def draw(self): + self.getPlotWidget().draw() + + def apply(self): + picks = self.getPicks() + for pick in picks: + print(pick, picks[pick]) + + def accept(self): + self.apply() + QDialog.accept(self) + + +class PropertiesDlg(QDialog): + def __init__(self, parent=None): + super(PropertiesDlg, self).__init__(parent) + + appName = QApplication.applicationName() + + self.setWindowTitle("{0} Properties".format(appName)) + + self.tabWidget = QTabWidget() + self.tabWidget.addTab(InputsTab(self), "Inputs") + self.tabWidget.addTab(OutputsTab(self), "Outputs") + self.tabWidget.addTab(PhasesTab(self), "Phases") + self.tabWidget.addTab(GraphicsTab(self), "Graphics") + self.tabWidget.addTab(LocalisationTab(self), "Loc Tools") + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Apply | + QDialogButtonBox.Close) + + layout = QVBoxLayout() + layout.addWidget(self.tabWidget) + layout.addWidget(self.buttonBox) + self.setLayout(layout) + + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + self.buttonBox.button(QDialogButtonBox.Apply).clicked.connect( + self.apply) + + def accept(self, *args, **kwargs): + self.apply() + QDialog.accept(self) + + def apply(self): + for widint in range(self.tabWidget.count()): + curwid = self.tabWidget.widget(widint) + values = curwid.getValues() + if values is not None: + self.setValues(values) + + @staticmethod + def setValues(tabValues): + settings = QSettings() + for setting, value in tabValues.items(): + settings.setValue(setting, value) + settings.sync() + + +class PropTab(QWidget): + def __init__(self, parent=None): + super(PropTab, self).__init__(parent) + + def getValues(self): + return None + + +class InputsTab(PropTab): + def __init__(self, parent): + super(InputsTab, self).__init__(parent) + + settings = QSettings() + fulluser = settings.value("user/FullName") + login = settings.value("user/Login") + + fullNameLabel = QLabel("Full name for user '{0}': ".format(login)) + + # get the full name of the actual user + self.fullNameEdit = QLineEdit() + try: + self.fullNameEdit.setText(fulluser) + except TypeError as e: + self.fullNameEdit.setText(fulluser[0]) + + # information about data structure + dataroot = settings.value("data/dataRoot") + curstructure = settings.value("data/Structure") + dataDirLabel = QLabel("data root directory: ") + self.dataDirEdit = QLineEdit() + self.dataDirEdit.setText(dataroot) + self.dataDirEdit.selectAll() + structureLabel = QLabel("data structure: ") + self.structureSelect = QComboBox() + + from pylot.core.util.structure import DATASTRUCTURE + + self.structureSelect.addItems(DATASTRUCTURE.keys()) + + dsind = findComboBoxIndex(self.structureSelect, curstructure) + + self.structureSelect.setCurrentIndex(dsind) + + layout = QGridLayout() + layout.addWidget(dataDirLabel, 0, 0) + layout.addWidget(self.dataDirEdit, 0, 1) + layout.addWidget(fullNameLabel, 1, 0) + layout.addWidget(self.fullNameEdit, 1, 1) + layout.addWidget(structureLabel, 2, 0) + layout.addWidget(self.structureSelect, 2, 1) + + self.setLayout(layout) + + def getValues(self): + values = {"data/dataRoot": self.dataDirEdit.text(), + "user/FullName": self.fullNameEdit.text(), + "data/Structure": self.structureSelect.currentText()} + return values + + +class OutputsTab(PropTab): + def __init__(self, parent=None): + super(OutputsTab, self).__init__(parent) + + settings = QSettings() + curval = settings.value("output/Format", None) + + eventOutputLabel = QLabel("event ouput format") + self.eventOutputComboBox = QComboBox() + eventoutputformats = OUTPUTFORMATS.keys() + self.eventOutputComboBox.addItems(eventoutputformats) + + ind = findComboBoxIndex(self.eventOutputComboBox, curval) + + self.eventOutputComboBox.setCurrentIndex(ind) + layout = QGridLayout() + layout.addWidget(eventOutputLabel, 0, 0) + layout.addWidget(self.eventOutputComboBox, 0, 1) + + self.setLayout(layout) + + def getValues(self): + values = {"output/Format": self.eventOutputComboBox.currentText()} + return values + + +class PhasesTab(PropTab): + def __init__(self, parent=None): + super(PhasesTab, self).__init__(parent) + + pass + + +class GraphicsTab(PropTab): + def __init__(self, parent=None): + super(GraphicsTab, self).__init__(parent) + + pass + + +class LocalisationTab(PropTab): + def __init__(self, parent=None): + super(LocalisationTab, self).__init__(parent) + + settings = QSettings() + curtool = settings.value("loc/tool", None) + + loctoollabel = QLabel("location tool") + self.locToolComboBox = QComboBox() + loctools = LOCTOOLS.keys() + self.locToolComboBox.addItems(loctools) + + toolind = findComboBoxIndex(self.locToolComboBox, curtool) + + self.locToolComboBox.setCurrentIndex(toolind) + + curroot = settings.value("{0}/rootPath".format(curtool), None) + curbin = settings.value("{0}/binPath".format(curtool), None) + + self.rootlabel = QLabel("root directory") + self.binlabel = QLabel("bin directory") + + self.rootedit = QLineEdit('') + self.binedit = QLineEdit('') + + if curroot is not None: + self.rootedit.setText(curroot) + if curbin is not None: + self.binedit.setText(curbin) + + rootBrowse = QPushButton('...', self) + rootBrowse.clicked.connect(lambda: self.selectDirectory(self.rootedit)) + + binBrowse = QPushButton('...', self) + binBrowse.clicked.connect(lambda: self.selectDirectory(self.binedit)) + + self.locToolComboBox.currentIndexChanged.connect(self.updateUi) + + self.updateUi() + + layout = QGridLayout() + layout.addWidget(loctoollabel, 0, 0) + layout.addWidget(self.locToolComboBox, 0, 1) + layout.addWidget(self.rootlabel, 1, 0) + layout.addWidget(self.rootedit, 1, 1) + layout.addWidget(rootBrowse, 1, 2) + layout.addWidget(self.binlabel, 2, 0) + layout.addWidget(self.binedit, 2, 1) + layout.addWidget(binBrowse, 2, 2) + + self.setLayout(layout) + + def updateUi(self): + curtool = self.locToolComboBox.currentText() + if curtool is not None: + self.rootlabel.setText("{0} root directory".format(curtool)) + self.binlabel.setText("{0} bin directory".format(curtool)) + + def selectDirectory(self, edit): + selected_directory = QFileDialog.getExistingDirectory() + # check if string is empty + if selected_directory: + edit.setText(selected_directory) + + def getValues(self): + loctool = self.locToolComboBox.currentText() + values = {"{0}/rootPath".format(loctool): self.rootedit.text(), + "{0}/binPath".format(loctool): self.binedit.text(), + "loc/tool": loctool} + return values + + +class NewEventDlg(QDialog): + def __init__(self, parent=None, titleString="Create a new event"): + """ + QDialog object utilized to create a new event manually. + """ + super(NewEventDlg, self).__init__() + + self.setupUI() + + now = datetime.datetime.now() + self.eventTimeEdit.setDateTime(now) + # event dates in the future are forbidden + self.eventTimeEdit.setMaximumDateTime(now) + + self.latEdit.setText("51.0000") + self.lonEdit.setText("7.0000") + self.depEdit.setText("10.0") + + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + def getValues(self): + return {'origintime': self.eventTimeEdit.dateTime().toPython(), + 'latitude': self.latEdit.text(), + 'longitude': self.lonEdit.text(), + 'depth': self.depEdit.text()} + + def setupUI(self): + # create widget objects + timeLabel = QLabel() + timeLabel.setText("Select time: ") + self.eventTimeEdit = QDateTimeEdit() + latLabel = QLabel() + latLabel.setText("Latitude: ") + self.latEdit = QLineEdit() + lonLabel = QLabel() + lonLabel.setText("Longitude: ") + self.lonEdit = QLineEdit() + depLabel = QLabel() + depLabel.setText("Depth: ") + self.depEdit = QLineEdit() + + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + + grid = QGridLayout() + grid.addWidget(timeLabel, 0, 0) + grid.addWidget(self.eventTimeEdit, 0, 1) + grid.addWidget(latLabel, 1, 0) + grid.addWidget(self.latEdit, 1, 1) + grid.addWidget(lonLabel, 2, 0) + grid.addWidget(self.lonEdit, 2, 1) + grid.addWidget(depLabel, 3, 0) + grid.addWidget(self.depEdit, 3, 1) + grid.addWidget(self.buttonBox, 4, 1) + + self.setLayout(grid) + + +class FilterOptionsDialog(QDialog): + def __init__(self, parent=None, titleString="Filter options", + filterOptions=None): + """ + PyLoT widget FilterOptionsDialog is a QDialog object. It is an UI to + adjust parameters for filtering seismic data. + """ + super(FilterOptionsDialog, self).__init__() + + if parent is not None and parent.getFilterOptions(): + self.filterOptions = parent.getFilterOptions() + elif filterOptions is not None: + self.filterOptions = FilterOptions(filterOptions) + else: + self.filterOptions = FilterOptions() + + _enable = True + if self.getFilterOptions().getFilterType() is None: + _enable = False + + self.freqminLabel = QLabel() + self.freqminLabel.setText("minimum:") + self.freqminSpinBox = QDoubleSpinBox() + self.freqminSpinBox.setRange(5e-7, 1e6) + self.freqminSpinBox.setDecimals(2) + self.freqminSpinBox.setSuffix(' Hz') + self.freqminSpinBox.setEnabled(_enable) + + self.freqmaxLabel = QLabel() + self.freqmaxLabel.setText("maximum:") + self.freqmaxSpinBox = QDoubleSpinBox() + self.freqmaxSpinBox.setRange(5e-7, 1e6) + self.freqmaxSpinBox.setDecimals(2) + self.freqmaxSpinBox.setSuffix(' Hz') + + if _enable: + self.freqminSpinBox.setValue(self.getFilterOptions().getFreq()[0]) + if self.getFilterOptions().getFilterType() in ['bandpass', + 'bandstop']: + self.freqmaxSpinBox.setValue( + self.getFilterOptions().getFreq()[1]) + else: + try: + self.freqmaxSpinBox.setValue(self.getFilterOptions().getFreq()) + self.freqminSpinBox.setValue(self.getFilterOptions().getFreq()) + except TypeError as e: + print(e) + self.freqmaxSpinBox.setValue(1.) + self.freqminSpinBox.setValue(.1) + + typeOptions = [None, "bandpass", "bandstop", "lowpass", "highpass"] + + self.orderLabel = QLabel() + self.orderLabel.setText("Order:") + self.orderSpinBox = QSpinBox() + self.orderSpinBox.setRange(2, 10) + self.orderSpinBox.setEnabled(_enable) + self.selectTypeLabel = QLabel() + self.selectTypeLabel.setText("Select filter type:") + self.selectTypeCombo = QComboBox() + self.selectTypeCombo.addItems(typeOptions) + self.selectTypeCombo.setCurrentIndex(typeOptions.index(self.getFilterOptions().getFilterType())) + self.selectTypeLayout = QVBoxLayout() + self.selectTypeLayout.addWidget(self.orderLabel) + self.selectTypeLayout.addWidget(self.orderSpinBox) + self.selectTypeLayout.addWidget(self.selectTypeLabel) + self.selectTypeLayout.addWidget(self.selectTypeCombo) + + self.freqGroupBox = QGroupBox("Frequency range") + self.freqGroupLayout = QGridLayout() + self.freqGroupLayout.addWidget(self.freqminLabel, 0, 0) + self.freqGroupLayout.addWidget(self.freqminSpinBox, 0, 1) + self.freqGroupLayout.addWidget(self.freqmaxLabel, 1, 0) + self.freqGroupLayout.addWidget(self.freqmaxSpinBox, 1, 1) + self.freqGroupBox.setLayout(self.freqGroupLayout) + + self.freqmaxSpinBox.setEnabled(_enable) + + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + + grid = QGridLayout() + grid.addWidget(self.freqGroupBox, 0, 2, 1, 2) + grid.addLayout(self.selectTypeLayout, 1, 2, 1, 2) + grid.addWidget(self.buttonBox, 2, 2, 1, 2) + + self.setLayout(grid) + + self.freqminSpinBox.valueChanged.connect(self.updateUi) + self.freqmaxSpinBox.valueChanged.connect(self.updateUi) + self.orderSpinBox.valueChanged.connect(self.updateUi) + self.selectTypeCombo.currentIndexChanged.connect(self.updateUi) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + def updateUi(self): + type = self.selectTypeCombo.currentText() + _enable = type in ['bandpass', 'bandstop'] + freq = [self.freqminSpinBox.value(), self.freqmaxSpinBox.value()] + self.freqmaxLabel.setEnabled(_enable) + self.freqmaxSpinBox.setEnabled(_enable) + + if not _enable: + self.freqminLabel.setText("cutoff:") + self.freqmaxSpinBox.setValue(freq[0]) + freq.remove(freq[1]) + else: + self.freqminLabel.setText("minimum:") + if not isSorted(freq): + QMessageBox.warning(self, "Value error", + "Maximum frequency must be at least the " + "same value as minimum frequency (notch)!") + self.freqmaxSpinBox.setValue(freq[0]) + self.freqmaxSpinBox.selectAll() + self.freqmaxSpinBox.setFocus() + return + + self.getFilterOptions().setFilterType(type) + self.getFilterOptions().setFreq(freq) + self.getFilterOptions().setOrder(self.orderSpinBox.value()) + + def getFilterOptions(self): + return self.filterOptions + + @staticmethod + def getFilterObject(): + dlg = FilterOptionsDialog() + if dlg.exec_(): + return dlg.getFilterOptions() + return None + + def accept(self): + self.updateUi() + QDialog.accept(self) + + +class LoadDataDlg(QDialog): + def __init__(self, parent=None): + super(LoadDataDlg, self).__init__(parent) + + pass + + +class HelpForm(QDialog): + def __init__(self, page=QUrl('https://ariadne.geophysik.rub.de/trac/PyLoT'), + parent=None): + super(HelpForm, self).__init__(parent) + self.setAttribute(Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WA_GroupLeader) + + backAction = QAction(QIcon(":/back.png"), "&Back", self) + backAction.setShortcut(QKeySequence.Back) + homeAction = QAction(QIcon(":/home.png"), "&Home", self) + homeAction.setShortcut("Home") + self.pageLabel = QLabel() + + toolBar = QToolBar() + toolBar.addAction(backAction) + toolBar.addAction(homeAction) + toolBar.addWidget(self.pageLabel) + self.webBrowser = QWebView() + self.webBrowser.load(page) + + layout = QVBoxLayout() + layout.addWidget(toolBar) + layout.addWidget(self.webBrowser, 1) + self.setLayout(layout) + + self.connect(backAction, Signal("triggered()"), + self.webBrowser, Slot("backward()")) + self.connect(homeAction, Signal("triggered()"), + self.webBrowser, Slot("home()")) + self.connect(self.webBrowser, Signal("sourceChanged(QUrl)"), + self.updatePageTitle) + + self.resize(400, 600) + self.setWindowTitle("{0} Help".format(QApplication.applicationName())) + + def updatePageTitle(self): + self.pageLabel.setText(self.webBrowser.documentTitle()) + + +if __name__ == '__main__': + import doctest + + doctest.testmod() diff --git a/pylot/testing/__init__.py b/pylot/testing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pylot/testing/testHelpForm.py b/pylot/testing/testHelpForm.py new file mode 100755 index 00000000..2da58745 --- /dev/null +++ b/pylot/testing/testHelpForm.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys, time +from PySide.QtGui import QApplication +from pylot.core.util.widgets import HelpForm + +app = QApplication(sys.argv) + +win = HelpForm() +win.show() +app.exec_() diff --git a/pylot/testing/testPickDlg.py b/pylot/testing/testPickDlg.py new file mode 100755 index 00000000..5f8979b7 --- /dev/null +++ b/pylot/testing/testPickDlg.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import matplotlib + +matplotlib.use('Qt4Agg') +matplotlib.rcParams['backend.qt4'] = 'PySide' + +from PySide.QtGui import QApplication +from obspy.core import read +from pylot.core.util.widgets import PickDlg +import icons_rc + +app = QApplication(sys.argv) + +data = read() +win = PickDlg(data=data) +win.show() +app.exec_() diff --git a/pylot/testing/testPropDlg.py b/pylot/testing/testPropDlg.py new file mode 100755 index 00000000..b77de3b6 --- /dev/null +++ b/pylot/testing/testPropDlg.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys, time +from PySide.QtGui import QApplication +from pylot.core.util.widgets import PropertiesDlg + +app = QApplication(sys.argv) + +win = PropertiesDlg() +win.show() +app.exec_() diff --git a/pylot/testing/testUIcomponents.py b/pylot/testing/testUIcomponents.py new file mode 100755 index 00000000..920e9fd4 --- /dev/null +++ b/pylot/testing/testUIcomponents.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +import sys, time +from PySide.QtGui import QApplication +from pylot.core.util.widgets import FilterOptionsDialog, PropertiesDlg, HelpForm + +dialogs = [FilterOptionsDialog, PropertiesDlg, HelpForm] + +app = QApplication(sys.argv) + +for dlg in dialogs: + win = dlg() + win.show() + time.sleep(1) + win.destroy() diff --git a/pylot/testing/testUtils.py b/pylot/testing/testUtils.py new file mode 100644 index 00000000..07d64925 --- /dev/null +++ b/pylot/testing/testUtils.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +''' +Created on 10.11.2014 + +@author: sebastianw +''' +import unittest + + +class Test(unittest.TestCase): + + + def setUp(self): + pass + + + def tearDown(self): + pass + + + def testName(self): + pass + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() diff --git a/pylot/testing/testWidgets.py b/pylot/testing/testWidgets.py new file mode 100644 index 00000000..b20190e1 --- /dev/null +++ b/pylot/testing/testWidgets.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +''' +Created on 10.11.2014 + +@author: sebastianw +''' +import unittest + + +class Test(unittest.TestCase): + + + def testName(self): + pass + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() diff --git a/pylot/testing/test_autopick.py b/pylot/testing/test_autopick.py new file mode 100755 index 00000000..1b668cf7 --- /dev/null +++ b/pylot/testing/test_autopick.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + Script to run autoPyLoT-script "makeCF.py". + Only for test purposes! +""" + +from obspy.core import read +import matplotlib.pyplot as plt +import numpy as np +from pylot.core.pick.charfuns import * +from pylot.core.pick.picker import * +import glob +import argparse + +def run_makeCF(project, database, event, iplot, station=None): + #parameters for CF calculation + t2 = 7 #length of moving window for HOS calculation [sec] + p = 4 #order of HOS + cuttimes = [10, 50] #start and end time for CF calculation + bpz = [2, 30] #corner frequencies of bandpass filter, vertical component + bph = [2, 15] #corner frequencies of bandpass filter, horizontal components + tdetz= 1.2 #length of AR-determination window [sec], vertical component + tdeth= 0.8 #length of AR-determination window [sec], horizontal components + tpredz = 0.4 #length of AR-prediction window [sec], vertical component + tpredh = 0.4 #length of AR-prediction window [sec], horizontal components + addnoise = 0.001 #add noise to seismogram for stable AR prediction + arzorder = 2 #chosen order of AR process, vertical component + arhorder = 4 #chosen order of AR process, horizontal components + TSNRhos = [5, 0.5, 1, 0.1] #window lengths [s] for calculating SNR for earliest/latest pick and quality assessment + #from HOS-CF [noise window, safety gap, signal window, slope determination window] + TSNRarz = [5, 0.5, 1, 0.5] #window lengths [s] for calculating SNR for earliest/lates pick and quality assessment + #from ARZ-CF + #get waveform data + if station: + dpz = '/data/%s/EVENT_DATA/LOCAL/%s/%s/%s*HZ.msd' % (project, database, event, station) + dpe = '/data/%s/EVENT_DATA/LOCAL/%s/%s/%s*HE.msd' % (project, database, event, station) + dpn = '/data/%s/EVENT_DATA/LOCAL/%s/%s/%s*HN.msd' % (project, database, event, station) + #dpz = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*_z.gse' % (project, database, event, station) + #dpe = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*_e.gse' % (project, database, event, station) + #dpn = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*_n.gse' % (project, database, event, station) + else: + # dpz = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/*_z.gse' % (project, database, event) + # dpe = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/*_e.gse' % (project, database, event) + # dpn = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/*_n.gse' % (project, database, event) + dpz = '/data/%s/EVENT_DATA/LOCAL/%s/%s/*HZ.msd' % (project, database, event) + dpe = '/data/%s/EVENT_DATA/LOCAL/%s/%s/*HE.msd' % (project, database, event) + dpn = '/data/%s/EVENT_DATA/LOCAL/%s/%s/*HN.msd' % (project, database, event) + wfzfiles = glob.glob(dpz) + wfefiles = glob.glob(dpe) + wfnfiles = glob.glob(dpn) + if wfzfiles: + for i in range(len(wfzfiles)): + print 'Vertical component data found ...' + print wfzfiles[i] + st = read('%s' % wfzfiles[i]) + st_copy = st.copy() + #filter and taper data + tr_filt = st[0].copy() + tr_filt.filter('bandpass', freqmin=bpz[0], freqmax=bpz[1], zerophase=False) + tr_filt.taper(max_percentage=0.05, type='hann') + st_copy[0].data = tr_filt.data + ############################################################## + #calculate HOS-CF using subclass HOScf of class CharacteristicFunction + hoscf = HOScf(st_copy, cuttimes, t2, p) #instance of HOScf + ############################################################## + #calculate AIC-HOS-CF using subclass AICcf of class CharacteristicFunction + #class needs stream object => build it + tr_aic = tr_filt.copy() + tr_aic.data = hoscf.getCF() + st_copy[0].data = tr_aic.data + aiccf = AICcf(st_copy, cuttimes) #instance of AICcf + ############################################################## + #get prelimenary onset time from AIC-HOS-CF using subclass AICPicker of class AutoPicking + aicpick = AICPicker(aiccf, None, TSNRhos, 3, 10, None, 0.1) + ############################################################## + #get refined onset time from HOS-CF using class Picker + hospick = PragPicker(hoscf, None, TSNRhos, 2, 10, 0.001, 0.2, aicpick.getpick()) + #get earliest and latest possible picks + hosELpick = EarlLatePicker(hoscf, 1.5, TSNRhos, None, 10, None, None, hospick.getpick()) + ############################################################## + #calculate ARZ-CF using subclass ARZcf of class CharcteristicFunction + #get stream object of filtered data + st_copy[0].data = tr_filt.data + arzcf = ARZcf(st_copy, cuttimes, tpredz, arzorder, tdetz, addnoise) #instance of ARZcf + ############################################################## + #calculate AIC-ARZ-CF using subclass AICcf of class CharacteristicFunction + #class needs stream object => build it + tr_arzaic = tr_filt.copy() + tr_arzaic.data = arzcf.getCF() + st_copy[0].data = tr_arzaic.data + araiccf = AICcf(st_copy, cuttimes, tpredz, 0, tdetz) #instance of AICcf + ############################################################## + #get onset time from AIC-ARZ-CF using subclass AICPicker of class AutoPicking + aicarzpick = AICPicker(araiccf, 1.5, TSNRarz, 2, 10, None, 0.1) + ############################################################## + #get refined onset time from ARZ-CF using class Picker + arzpick = PragPicker(arzcf, 1.5, TSNRarz, 2.0, 10, 0.1, 0.05, aicarzpick.getpick()) + #get earliest and latest possible picks + arzELpick = EarlLatePicker(arzcf, 1.5, TSNRarz, None, 10, None, None, arzpick.getpick()) + elif not wfzfiles: + print 'No vertical component data found!' + + if wfefiles and wfnfiles: + for i in range(len(wfefiles)): + print 'Horizontal component data found ...' + print wfefiles[i] + print wfnfiles[i] + #merge streams + H = read('%s' % wfefiles[i]) + H += read('%s' % wfnfiles[i]) + H_copy = H.copy() + #filter and taper data + trH1_filt = H[0].copy() + trH2_filt = H[1].copy() + trH1_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + H_copy[0].data = trH1_filt.data + H_copy[1].data = trH2_filt.data + + ############################################################## + #calculate ARH-CF using subclass ARHcf of class CharcteristicFunction + arhcf = ARHcf(H_copy, cuttimes, tpredh, arhorder, tdeth, addnoise) #instance of ARHcf + ############################################################## + #calculate AIC-ARH-CF using subclass AICcf of class CharacteristicFunction + #class needs stream object => build it + tr_arhaic = trH1_filt.copy() + tr_arhaic.data = arhcf.getCF() + H_copy[0].data = tr_arhaic.data + #calculate ARH-AIC-CF + arhaiccf = AICcf(H_copy, cuttimes, tpredh, 0, tdeth) #instance of AICcf + ############################################################## + #get onset time from AIC-ARH-CF using subclass AICPicker of class AutoPicking + aicarhpick = AICPicker(arhaiccf, 1.5, TSNRarz, 4, 10, None, 0.1) + ############################################################### + #get refined onset time from ARH-CF using class Picker + arhpick = PragPicker(arhcf, 1.5, TSNRarz, 2.5, 10, 0.1, 0.05, aicarhpick.getpick()) + #get earliest and latest possible picks + arhELpick = EarlLatePicker(arhcf, 1.5, TSNRarz, None, 10, None, None, arhpick.getpick()) + + #create stream with 3 traces + #merge streams + AllC = read('%s' % wfefiles[i]) + AllC += read('%s' % wfnfiles[i]) + AllC += read('%s' % wfzfiles[i]) + #filter and taper data + All1_filt = AllC[0].copy() + All2_filt = AllC[1].copy() + All3_filt = AllC[2].copy() + All1_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + All2_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + All3_filt.filter('bandpass', freqmin=bpz[0], freqmax=bpz[1], zerophase=False) + All1_filt.taper(max_percentage=0.05, type='hann') + All2_filt.taper(max_percentage=0.05, type='hann') + All3_filt.taper(max_percentage=0.05, type='hann') + AllC[0].data = All1_filt.data + AllC[1].data = All2_filt.data + AllC[2].data = All3_filt.data + #calculate AR3C-CF using subclass AR3Ccf of class CharacteristicFunction + ar3ccf = AR3Ccf(AllC, cuttimes, tpredz, arhorder, tdetz, addnoise) #instance of AR3Ccf + #get earliest and latest possible pick from initial ARH-pick + ar3cELpick = EarlLatePicker(ar3ccf, 1.5, TSNRarz, None, 10, None, None, arhpick.getpick()) + ############################################################## + if iplot: + #plot vertical trace + plt.figure() + tr = st[0] + tdata = np.arange(0, tr.stats.npts / tr.stats.sampling_rate, tr.stats.delta) + p1, = plt.plot(tdata, tr_filt.data/max(tr_filt.data), 'k') + p2, = plt.plot(hoscf.getTimeArray(), hoscf.getCF() / max(hoscf.getCF()), 'r') + p3, = plt.plot(aiccf.getTimeArray(), aiccf.getCF()/max(aiccf.getCF()), 'b') + p4, = plt.plot(arzcf.getTimeArray(), arzcf.getCF()/max(arzcf.getCF()), 'g') + p5, = plt.plot(araiccf.getTimeArray(), araiccf.getCF()/max(araiccf.getCF()), 'y') + plt.plot([aicpick.getpick(), aicpick.getpick()], [-1, 1], 'b--') + plt.plot([aicpick.getpick()-0.5, aicpick.getpick()+0.5], [1, 1], 'b') + plt.plot([aicpick.getpick()-0.5, aicpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([hospick.getpick(), hospick.getpick()], [-1.3, 1.3], 'r', linewidth=2) + plt.plot([hospick.getpick()-0.5, hospick.getpick()+0.5], [1.3, 1.3], 'r') + plt.plot([hospick.getpick()-0.5, hospick.getpick()+0.5], [-1.3, -1.3], 'r') + plt.plot([hosELpick.getLpick(), hosELpick.getLpick()], [-1.1, 1.1], 'r--') + plt.plot([hosELpick.getEpick(), hosELpick.getEpick()], [-1.1, 1.1], 'r--') + plt.plot([aicarzpick.getpick(), aicarzpick.getpick()], [-1.2, 1.2], 'y', linewidth=2) + plt.plot([aicarzpick.getpick()-0.5, aicarzpick.getpick()+0.5], [1.2, 1.2], 'y') + plt.plot([aicarzpick.getpick()-0.5, aicarzpick.getpick()+0.5], [-1.2, -1.2], 'y') + plt.plot([arzpick.getpick(), arzpick.getpick()], [-1.4, 1.4], 'g', linewidth=2) + plt.plot([arzpick.getpick()-0.5, arzpick.getpick()+0.5], [1.4, 1.4], 'g') + plt.plot([arzpick.getpick()-0.5, arzpick.getpick()+0.5], [-1.4, -1.4], 'g') + plt.plot([arzELpick.getLpick(), arzELpick.getLpick()], [-1.2, 1.2], 'g--') + plt.plot([arzELpick.getEpick(), arzELpick.getEpick()], [-1.2, 1.2], 'g--') + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.xlabel('Time [s]') + plt.ylabel('Normalized Counts') + plt.title('%s, %s, CF-SNR=%7.2f, CF-Slope=%12.2f' % (tr.stats.station, \ + tr.stats.channel, aicpick.getSNR(), aicpick.getSlope())) + plt.suptitle(tr.stats.starttime) + plt.legend([p1, p2, p3, p4, p5], ['Data', 'HOS-CF', 'HOSAIC-CF', 'ARZ-CF', 'ARZAIC-CF']) + #plot horizontal traces + plt.figure(2) + plt.subplot(2,1,1) + tsteph = tpredh / 4 + th1data = np.arange(0, trH1_filt.stats.npts / trH1_filt.stats.sampling_rate, trH1_filt.stats.delta) + th2data = np.arange(0, trH2_filt.stats.npts / trH2_filt.stats.sampling_rate, trH2_filt.stats.delta) + tarhcf = np.arange(0, len(arhcf.getCF()) * tsteph, tsteph) + cuttimes[0] + tdeth +tpredh + p21, = plt.plot(th1data, trH1_filt.data/max(trH1_filt.data), 'k') + p22, = plt.plot(arhcf.getTimeArray(), arhcf.getCF()/max(arhcf.getCF()), 'r') + p23, = plt.plot(arhaiccf.getTimeArray(), arhaiccf.getCF()/max(arhaiccf.getCF())) + plt.plot([aicarhpick.getpick(), aicarhpick.getpick()], [-1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'r') + plt.plot([arhELpick.getLpick(), arhELpick.getLpick()], [-0.8, 0.8], 'r--') + plt.plot([arhELpick.getEpick(), arhELpick.getEpick()], [-0.8, 0.8], 'r--') + plt.plot([arhpick.getpick() + arhELpick.getPickError(), arhpick.getpick() + arhELpick.getPickError()], \ + [-0.2, 0.2], 'r--') + plt.plot([arhpick.getpick() - arhELpick.getPickError(), arhpick.getpick() - arhELpick.getPickError()], \ + [-0.2, 0.2], 'r--') + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.ylabel('Normalized Counts') + plt.title([trH1_filt.stats.station, trH1_filt.stats.channel]) + plt.suptitle(trH1_filt.stats.starttime) + plt.legend([p21, p22, p23], ['Data', 'ARH-CF', 'ARHAIC-CF']) + plt.subplot(2,1,2) + plt.plot(th2data, trH2_filt.data/max(trH2_filt.data), 'k') + plt.plot(arhcf.getTimeArray(), arhcf.getCF()/max(arhcf.getCF()), 'r') + plt.plot(arhaiccf.getTimeArray(), arhaiccf.getCF()/max(arhaiccf.getCF())) + plt.plot([aicarhpick.getpick(), aicarhpick.getpick()], [-1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'r') + plt.plot([arhELpick.getLpick(), arhELpick.getLpick()], [-0.8, 0.8], 'r--') + plt.plot([arhELpick.getEpick(), arhELpick.getEpick()], [-0.8, 0.8], 'r--') + plt.plot([arhpick.getpick() + arhELpick.getPickError(), arhpick.getpick() + arhELpick.getPickError()], \ + [-0.2, 0.2], 'r--') + plt.plot([arhpick.getpick() - arhELpick.getPickError(), arhpick.getpick() - arhELpick.getPickError()], \ + [-0.2, 0.2], 'r--') + plt.title([trH2_filt.stats.station, trH2_filt.stats.channel]) + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.xlabel('Time [s]') + plt.ylabel('Normalized Counts') + #plot 3-component window + plt.figure(3) + plt.subplot(3,1,1) + p31, = plt.plot(tdata, tr_filt.data/max(tr_filt.data), 'k') + p32, = plt.plot(ar3ccf.getTimeArray(), ar3ccf.getCF()/max(ar3ccf.getCF()), 'r') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([ar3cELpick.getLpick(), ar3cELpick.getLpick()], [-0.8, 0.8], 'b--') + plt.plot([ar3cELpick.getEpick(), ar3cELpick.getEpick()], [-0.8, 0.8], 'b--') + plt.yticks([]) + plt.xticks([]) + plt.ylabel('Normalized Counts') + plt.title([tr.stats.station, tr.stats.channel]) + plt.suptitle(trH1_filt.stats.starttime) + plt.legend([p31, p32], ['Data', 'AR3C-CF']) + plt.subplot(3,1,2) + plt.plot(th1data, trH1_filt.data/max(trH1_filt.data), 'k') + plt.plot(ar3ccf.getTimeArray(), ar3ccf.getCF()/max(ar3ccf.getCF()), 'r') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([ar3cELpick.getLpick(), ar3cELpick.getLpick()], [-0.8, 0.8], 'b--') + plt.plot([ar3cELpick.getEpick(), ar3cELpick.getEpick()], [-0.8, 0.8], 'b--') + plt.yticks([]) + plt.xticks([]) + plt.ylabel('Normalized Counts') + plt.title([trH1_filt.stats.station, trH1_filt.stats.channel]) + plt.subplot(3,1,3) + plt.plot(th2data, trH2_filt.data/max(trH2_filt.data), 'k') + plt.plot(ar3ccf.getTimeArray(), ar3ccf.getCF()/max(ar3ccf.getCF()), 'r') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([ar3cELpick.getLpick(), ar3cELpick.getLpick()], [-0.8, 0.8], 'b--') + plt.plot([ar3cELpick.getEpick(), ar3cELpick.getEpick()], [-0.8, 0.8], 'b--') + plt.yticks([]) + plt.ylabel('Normalized Counts') + plt.title([trH2_filt.stats.station, trH2_filt.stats.channel]) + plt.xlabel('Time [s]') + plt.show() + raw_input() + plt.close() + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--project', type=str, help='project name (e.g. Insheim)') + parser.add_argument('--database', type=str, help='event data base (e.g. 2014.09_Insheim)') + parser.add_argument('--event', type=str, help='event ID (e.g. e0010.015.14)') + parser.add_argument('--iplot', help='anything, if set, figure occurs') + parser.add_argument('--station', type=str, help='Station ID (e.g. INS3) (optional)') + args = parser.parse_args() + + run_makeCF(args.project, args.database, args.event, args.iplot, args.station) diff --git a/pylot/testing/test_pdf.py b/pylot/testing/test_pdf.py new file mode 100644 index 00000000..9da53bc5 --- /dev/null +++ b/pylot/testing/test_pdf.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pylot.core.util.pdf import ProbabilityDensityFunction +pdf = ProbabilityDensityFunction.from_pick(0.34, 0.5, 0.54, type='exp') +pdf2 = ProbabilityDensityFunction.from_pick(0.34, 0.5, 0.54, type='exp') +diff = pdf - pdf2 \ No newline at end of file diff --git a/scripts/pylot-noisewindow.py b/scripts/pylot-noisewindow.py new file mode 100755 index 00000000..9a4cb00c --- /dev/null +++ b/scripts/pylot-noisewindow.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse +import numpy +from pylot.core.pick.utils import getnoisewin + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--t', type=numpy.array, help='numpy array of time stamps') + parser.add_argument('--t1', type=float, help='time from which relativ to it noise window is extracted') + parser.add_argument('--tnoise', type=float, help='length of time window [s] for noise part extraction') + parser.add_argument('--tgap', type=float, help='safety gap between signal (t1=onset) and noise') + args = parser.parse_args() + getnoisewin(args.t, args.t1, args.tnoise, args.tgap) diff --git a/scripts/pylot-pick-earliest-latest.py b/scripts/pylot-pick-earliest-latest.py new file mode 100755 index 00000000..b17efd01 --- /dev/null +++ b/scripts/pylot-pick-earliest-latest.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Created Mar 2015 + Transcription of the rezipe of Diehl et al. (2009) for consistent phase + picking. For a given inital (the most likely) pick, the corresponding earliest + and latest possible pick is calculated based on noise measurements in front of + the most likely pick and signal wavelength derived from zero crossings. + + :author: Ludger Kueperkoch / MAGS2 EP3 working group +""" + +import argparse +import obspy +from pylot.core.pick.utils import earllatepicker + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--X', type=~obspy.core.stream.Stream, + help='time series (seismogram) read with obspy module read') + parser.add_argument('--nfac', type=int, + help='(noise factor), nfac times noise level to calculate latest possible pick') + parser.add_argument('--TSNR', type=tuple, help='length of time windows around pick used to determine SNR \ + [s] (Tnoise, Tgap, Tsignal)') + parser.add_argument('--Pick1', type=float, help='Onset time of most likely pick') + parser.add_argument('--iplot', type=int, help='if set, figure no. iplot occurs') + args = parser.parse_args() + earllatepicker(args.X, args.nfac, args.TSNR, args.Pick1, args.iplot) diff --git a/scripts/pylot-pick-firstmotion.py b/scripts/pylot-pick-firstmotion.py new file mode 100755 index 00000000..a62d7bb4 --- /dev/null +++ b/scripts/pylot-pick-firstmotion.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Created Mar 2015 + Function to derive first motion (polarity) for given phase onset based on zero crossings. + + :author: MAGS2 EP3 working group / Ludger Kueperkoch +""" + +import argparse +import obspy +from pylot.core.pick.utils import fmpicker + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--Xraw', type=obspy.core.stream.Stream, + help='unfiltered time series (seismogram) read with obspy module read') + parser.add_argument('--Xfilt', type=obspy.core.stream.Stream, + help='filtered time series (seismogram) read with obspy module read') + parser.add_argument('--pickwin', type=float, help='length of pick window [s] for first motion determination') + parser.add_argument('--Pick', type=float, help='Onset time of most likely pick') + parser.add_argument('--iplot', type=int, help='if set, figure no. iplot occurs') + args = parser.parse_args() + fmpicker(args.Xraw, args.Xfilt, args.pickwin, args.Pick, args.iplot) diff --git a/scripts/pylot-reasses-pilot-db.py b/scripts/pylot-reasses-pilot-db.py new file mode 100755 index 00000000..7b55ce6a --- /dev/null +++ b/scripts/pylot-reasses-pilot-db.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse + +from pylot.core.util.version import get_git_version as _getVersionString +from pylot.core.io.phases import reassess_pilot_db + +__version__ = _getVersionString() +__author__ = 'S. Wehling-Benatelli' + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='reassess old PILOT event data base in terms of consistent ' + 'automatic uncertainty estimation', + epilog='Script written by {author} belonging to PyLoT version' + ' {version}\n'.format(author=__author__, + version=__version__) + ) + + parser.add_argument( + 'root', type=str, help='specifies the root directory' + ) + parser.add_argument( + 'db', type=str, help='specifies the database name' + ) + parser.add_argument( + '--output', '-o', type=str, help='path to the output directory', + dest='output' + ) + parser.add_argument( + '--parameterfile', '-p', type=str, + help='full path to the parameterfile', dest='parfile' + ) + parser.add_argument( + '--verbosity', '-v', action='count', help='increase output verbosity', + default=0, dest='verbosity' + ) + + args = parser.parse_args() + reassess_pilot_db(args.root, args.db, args.output, args.parfile, args.verbosity) diff --git a/scripts/pylot-reasses-pilot-event.py b/scripts/pylot-reasses-pilot-event.py new file mode 100755 index 00000000..b9fd3a39 --- /dev/null +++ b/scripts/pylot-reasses-pilot-event.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse + +from pylot.core.util.version import get_git_version as _getVersionString +from pylot.core.io.phases import reassess_pilot_event + +__version__ = _getVersionString() +__author__ = 'S. Wehling-Benatelli' + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='reassess old PILOT event data in terms of consistent ' + 'automatic uncertainty estimation', + epilog='Script written by {author} belonging to PyLoT version' + ' {version}\n'.format(author=__author__, + version=__version__) + ) + + parser.add_argument( + 'root', type=str, help='specifies the root directory' + ) + parser.add_argument( + 'db', type=str, help='specifies the database name' + ) + parser.add_argument( + 'id', type=str, help='PILOT event identifier' + ) + parser.add_argument( + '--output', '-o', type=str, help='path to the output directory', dest='output' + ) + parser.add_argument( + '--parameterfile', '-p', type=str, help='full path to the parameterfile', dest='parfile' + ) + + args = parser.parse_args() + reassess_pilot_event(args.root, args.db, args.id, args.output, args.parfile) diff --git a/scripts/pylot-signalwindow.py b/scripts/pylot-signalwindow.py new file mode 100755 index 00000000..2655c6ea --- /dev/null +++ b/scripts/pylot-signalwindow.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse +import numpy +from pylot.core.pick.utils import getsignalwin + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--t', type=numpy.array, help='numpy array of time stamps') + parser.add_argument('--t1', type=float, help='time from which relativ to it signal window is extracted') + parser.add_argument('--tsignal', type=float, help='length of time window [s] for signal part extraction') + args = parser.parse_args() + getsignalwin(args.t, args.t1, args.tsignal) diff --git a/scripts/pylot-snr.py b/scripts/pylot-snr.py new file mode 100755 index 00000000..5c932e87 --- /dev/null +++ b/scripts/pylot-snr.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Created Mar/Apr 2015 + Function to calculate SNR of certain part of seismogram relative + to given time. Returns SNR and SNR [dB]. + + :author: Ludger Kueperkoch /MAGS EP3 working group +""" + +import argparse +import obspy +from pylot.core.pick.utils import getSNR + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--data', '-d', type=obspy.core.stream.Stream, + help='time series (seismogram) read with obspy module ' + 'read', + dest='data') + parser.add_argument('--tsnr', '-s', type=tuple, + help='length of time windows around pick used to ' + 'determine SNR [s] (Tnoise, Tgap, Tsignal)', + dest='tsnr') + parser.add_argument('--time', '-t', type=float, + help='initial time from which noise and signal windows ' + 'are calculated', + dest='time') + args = parser.parse_args() + print getSNR(args.data, args.tsnr, args.time) diff --git a/scripts/run_makeCF.py b/scripts/run_makeCF.py new file mode 100755 index 00000000..b57172a6 --- /dev/null +++ b/scripts/run_makeCF.py @@ -0,0 +1,307 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" + Script to run autoPyLoT-script "run_makeCF.py". + Only for test purposes! +""" + +from obspy.core import read +import matplotlib.pyplot as plt +import numpy as np +from pylot.core.pick.charfuns import CharacteristicFunction +from pylot.core.pick.picker import AutoPicker +from pylot.core.pick.utils import * +import glob +import argparse + +def run_makeCF(project, database, event, iplot, station=None): + #parameters for CF calculation + t2 = 7 #length of moving window for HOS calculation [sec] + p = 4 #order of HOS + cuttimes = [10, 50] #start and end time for CF calculation + bpz = [2, 30] #corner frequencies of bandpass filter, vertical component + bph = [2, 15] #corner frequencies of bandpass filter, horizontal components + tdetz= 1.2 #length of AR-determination window [sec], vertical component + tdeth= 0.8 #length of AR-determination window [sec], horizontal components + tpredz = 0.4 #length of AR-prediction window [sec], vertical component + tpredh = 0.4 #length of AR-prediction window [sec], horizontal components + addnoise = 0.001 #add noise to seismogram for stable AR prediction + arzorder = 2 #chosen order of AR process, vertical component + arhorder = 4 #chosen order of AR process, horizontal components + TSNRhos = [5, 0.5, 1, .6] #window lengths [s] for calculating SNR for earliest/latest pick and quality assessment + #from HOS-CF [noise window, safety gap, signal window, slope determination window] + TSNRarz = [5, 0.5, 1, 1.0] #window lengths [s] for calculating SNR for earliest/lates pick and quality assessment + #from ARZ-CF + #get waveform data + if station: + dpz = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*HZ.msd' % (project, database, event, station) + dpe = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*HE.msd' % (project, database, event, station) + dpn = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*HN.msd' % (project, database, event, station) + #dpz = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*_z.gse' % (project, database, event, station) + #dpe = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*_e.gse' % (project, database, event, station) + #dpn = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/%s*_n.gse' % (project, database, event, station) + else: + dpz = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/*HZ.msd' % (project, database, event) + dpe = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/*HE.msd' % (project, database, event) + dpn = '/DATA/%s/EVENT_DATA/LOCAL/%s/%s/*HN.msd' % (project, database, event) + wfzfiles = glob.glob(dpz) + wfefiles = glob.glob(dpe) + wfnfiles = glob.glob(dpn) + if wfzfiles: + for i in range(len(wfzfiles)): + print 'Vertical component data found ...' + print wfzfiles[i] + st = read('%s' % wfzfiles[i]) + st_copy = st.copy() + #filter and taper data + tr_filt = st[0].copy() + tr_filt.filter('bandpass', freqmin=bpz[0], freqmax=bpz[1], zerophase=False) + tr_filt.taper(max_percentage=0.05, type='hann') + st_copy[0].data = tr_filt.data + ############################################################## + #calculate HOS-CF using subclass HOScf of class CharacteristicFunction + hoscf = HOScf(st_copy, cuttimes, t2, p) #instance of HOScf + ############################################################## + #calculate AIC-HOS-CF using subclass AICcf of class CharacteristicFunction + #class needs stream object => build it + tr_aic = tr_filt.copy() + tr_aic.data = hoscf.getCF() + st_copy[0].data = tr_aic.data + aiccf = AICcf(st_copy, cuttimes) #instance of AICcf + ############################################################## + #get prelimenary onset time from AIC-HOS-CF using subclass AICPicker of class AutoPicking + aicpick = AICPicker(aiccf, TSNRhos, 3, 10, None, 0.1) + ############################################################## + #get refined onset time from HOS-CF using class Picker + hospick = PragPicker(hoscf, TSNRhos, 2, 10, 0.001, 0.2, aicpick.getpick()) + ############################################################# + #get earliest and latest possible picks + st_copy[0].data = tr_filt.data + [lpickhos, epickhos, pickerrhos] = earllatepicker(st_copy, 1.5, TSNRhos, hospick.getpick(), 10) + ############################################################# + #get SNR + [SNR, SNRdB] = getSNR(st_copy, TSNRhos, hospick.getpick()) + print 'SNR:', SNR, 'SNR[dB]:', SNRdB + ########################################################## + #get first motion of onset + hosfm = fmpicker(st, st_copy, 0.2, hospick.getpick(), 11) + ############################################################## + #calculate ARZ-CF using subclass ARZcf of class CharcteristicFunction + arzcf = ARZcf(st, cuttimes, tpredz, arzorder, tdetz, addnoise) #instance of ARZcf + ############################################################## + #calculate AIC-ARZ-CF using subclass AICcf of class CharacteristicFunction + #class needs stream object => build it + tr_arzaic = tr_filt.copy() + tr_arzaic.data = arzcf.getCF() + st_copy[0].data = tr_arzaic.data + araiccf = AICcf(st_copy, cuttimes, tpredz, 0, tdetz) #instance of AICcf + ############################################################## + #get onset time from AIC-ARZ-CF using subclass AICPicker of class AutoPicking + aicarzpick = AICPicker(araiccf, TSNRarz, 2, 10, None, 0.1) + ############################################################## + #get refined onset time from ARZ-CF using class Picker + arzpick = PragPicker(arzcf, TSNRarz, 2.0, 10, 0.1, 0.05, aicarzpick.getpick()) + #get earliest and latest possible picks + st_copy[0].data = tr_filt.data + [lpickarz, epickarz, pickerrarz] = earllatepicker(st_copy, 1.5, TSNRarz, arzpick.getpick(), 10) + elif not wfzfiles: + print 'No vertical component data found!' + + if wfefiles and wfnfiles: + for i in range(len(wfefiles)): + print 'Horizontal component data found ...' + print wfefiles[i] + print wfnfiles[i] + #merge streams + H = read('%s' % wfefiles[i]) + H += read('%s' % wfnfiles[i]) + H_copy = H.copy() + #filter and taper data + trH1_filt = H[0].copy() + trH2_filt = H[1].copy() + trH1_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + trH2_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + trH1_filt.taper(max_percentage=0.05, type='hann') + trH2_filt.taper(max_percentage=0.05, type='hann') + H_copy[0].data = trH1_filt.data + H_copy[1].data = trH2_filt.data + + ############################################################## + #calculate ARH-CF using subclass ARHcf of class CharcteristicFunction + arhcf = ARHcf(H_copy, cuttimes, tpredh, arhorder, tdeth, addnoise) #instance of ARHcf + ############################################################## + #calculate AIC-ARH-CF using subclass AICcf of class CharacteristicFunction + #class needs stream object => build it + tr_arhaic = trH1_filt.copy() + tr_arhaic.data = arhcf.getCF() + H_copy[0].data = tr_arhaic.data + #calculate ARH-AIC-CF + arhaiccf = AICcf(H_copy, cuttimes, tpredh, 0, tdeth) #instance of AICcf + ############################################################## + #get onset time from AIC-ARH-CF using subclass AICPicker of class AutoPicking + aicarhpick = AICPicker(arhaiccf, TSNRarz, 4, 10, None, 0.1) + ############################################################### + #get refined onset time from ARH-CF using class Picker + arhpick = PragPicker(arhcf, TSNRarz, 2.5, 10, 0.1, 0.05, aicarhpick.getpick()) + #get earliest and latest possible picks + H_copy[0].data = trH1_filt.data + [lpickarh1, epickarh1, pickerrarh1] = earllatepicker(H_copy, 1.5, TSNRarz, arhpick.getpick(), 10) + H_copy[0].data = trH2_filt.data + [lpickarh2, epickarh2, pickerrarh2] = earllatepicker(H_copy, 1.5, TSNRarz, arhpick.getpick(), 10) + #get earliest pick of both earliest possible picks + epick = [epickarh1, epickarh2] + lpick = [lpickarh1, lpickarh2] + pickerr = [pickerrarh1, pickerrarh2] + ipick =np.argmin([epickarh1, epickarh2]) + epickarh = epick[ipick] + lpickarh = lpick[ipick] + pickerrarh = pickerr[ipick] + + #create stream with 3 traces + #merge streams + AllC = read('%s' % wfefiles[i]) + AllC += read('%s' % wfnfiles[i]) + AllC += read('%s' % wfzfiles[i]) + #filter and taper data + All1_filt = AllC[0].copy() + All2_filt = AllC[1].copy() + All3_filt = AllC[2].copy() + All1_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + All2_filt.filter('bandpass', freqmin=bph[0], freqmax=bph[1], zerophase=False) + All3_filt.filter('bandpass', freqmin=bpz[0], freqmax=bpz[1], zerophase=False) + All1_filt.taper(max_percentage=0.05, type='hann') + All2_filt.taper(max_percentage=0.05, type='hann') + All3_filt.taper(max_percentage=0.05, type='hann') + AllC[0].data = All1_filt.data + AllC[1].data = All2_filt.data + AllC[2].data = All3_filt.data + #calculate AR3C-CF using subclass AR3Ccf of class CharacteristicFunction + ar3ccf = AR3Ccf(AllC, cuttimes, tpredz, arhorder, tdetz, addnoise) #instance of AR3Ccf + ############################################################## + if iplot: + #plot vertical trace + plt.figure() + tr = st[0] + tdata = np.arange(0, tr.stats.npts / tr.stats.sampling_rate, tr.stats.delta) + p1, = plt.plot(tdata, tr_filt.data/max(tr_filt.data), 'k') + p2, = plt.plot(hoscf.getTimeArray(), hoscf.getCF() / max(hoscf.getCF()), 'r') + p3, = plt.plot(aiccf.getTimeArray(), aiccf.getCF()/max(aiccf.getCF()), 'b') + p4, = plt.plot(arzcf.getTimeArray(), arzcf.getCF()/max(arzcf.getCF()), 'g') + p5, = plt.plot(araiccf.getTimeArray(), araiccf.getCF()/max(araiccf.getCF()), 'y') + plt.plot([aicpick.getpick(), aicpick.getpick()], [-1, 1], 'b--') + plt.plot([aicpick.getpick()-0.5, aicpick.getpick()+0.5], [1, 1], 'b') + plt.plot([aicpick.getpick()-0.5, aicpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([hospick.getpick(), hospick.getpick()], [-1.3, 1.3], 'r', linewidth=2) + plt.plot([hospick.getpick()-0.5, hospick.getpick()+0.5], [1.3, 1.3], 'r') + plt.plot([hospick.getpick()-0.5, hospick.getpick()+0.5], [-1.3, -1.3], 'r') + plt.plot([lpickhos, lpickhos], [-1.1, 1.1], 'r--') + plt.plot([epickhos, epickhos], [-1.1, 1.1], 'r--') + plt.plot([aicarzpick.getpick(), aicarzpick.getpick()], [-1.2, 1.2], 'y', linewidth=2) + plt.plot([aicarzpick.getpick()-0.5, aicarzpick.getpick()+0.5], [1.2, 1.2], 'y') + plt.plot([aicarzpick.getpick()-0.5, aicarzpick.getpick()+0.5], [-1.2, -1.2], 'y') + plt.plot([arzpick.getpick(), arzpick.getpick()], [-1.4, 1.4], 'g', linewidth=2) + plt.plot([arzpick.getpick()-0.5, arzpick.getpick()+0.5], [1.4, 1.4], 'g') + plt.plot([arzpick.getpick()-0.5, arzpick.getpick()+0.5], [-1.4, -1.4], 'g') + plt.plot([lpickarz, lpickarz], [-1.2, 1.2], 'g--') + plt.plot([epickarz, epickarz], [-1.2, 1.2], 'g--') + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.xlabel('Time [s]') + plt.ylabel('Normalized Counts') + plt.title('%s, %s, CF-SNR=%7.2f, CF-Slope=%12.2f' % (tr.stats.station, + tr.stats.channel, aicpick.getSNR(), aicpick.getSlope())) + plt.suptitle(tr.stats.starttime) + plt.legend([p1, p2, p3, p4, p5], ['Data', 'HOS-CF', 'HOSAIC-CF', 'ARZ-CF', 'ARZAIC-CF']) + #plot horizontal traces + plt.figure(2) + plt.subplot(2,1,1) + tsteph = tpredh / 4 + th1data = np.arange(0, trH1_filt.stats.npts / trH1_filt.stats.sampling_rate, trH1_filt.stats.delta) + th2data = np.arange(0, trH2_filt.stats.npts / trH2_filt.stats.sampling_rate, trH2_filt.stats.delta) + tarhcf = np.arange(0, len(arhcf.getCF()) * tsteph, tsteph) + cuttimes[0] + tdeth +tpredh + p21, = plt.plot(th1data, trH1_filt.data/max(trH1_filt.data), 'k') + p22, = plt.plot(arhcf.getTimeArray(), arhcf.getCF()/max(arhcf.getCF()), 'r') + p23, = plt.plot(arhaiccf.getTimeArray(), arhaiccf.getCF()/max(arhaiccf.getCF())) + plt.plot([aicarhpick.getpick(), aicarhpick.getpick()], [-1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'r') + plt.plot([lpickarh, lpickarh], [-0.8, 0.8], 'r--') + plt.plot([epickarh, epickarh], [-0.8, 0.8], 'r--') + plt.plot([arhpick.getpick() + pickerrarh, arhpick.getpick() + pickerrarh], [-0.2, 0.2], 'r--') + plt.plot([arhpick.getpick() - pickerrarh, arhpick.getpick() - pickerrarh], [-0.2, 0.2], 'r--') + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.ylabel('Normalized Counts') + plt.title([trH1_filt.stats.station, trH1_filt.stats.channel]) + plt.suptitle(trH1_filt.stats.starttime) + plt.legend([p21, p22, p23], ['Data', 'ARH-CF', 'ARHAIC-CF']) + plt.subplot(2,1,2) + plt.plot(th2data, trH2_filt.data/max(trH2_filt.data), 'k') + plt.plot(arhcf.getTimeArray(), arhcf.getCF()/max(arhcf.getCF()), 'r') + plt.plot(arhaiccf.getTimeArray(), arhaiccf.getCF()/max(arhaiccf.getCF())) + plt.plot([aicarhpick.getpick(), aicarhpick.getpick()], [-1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [1, 1], 'b') + plt.plot([aicarhpick.getpick()-0.5, aicarhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'r') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'r') + plt.plot([lpickarh, lpickarh], [-0.8, 0.8], 'r--') + plt.plot([epickarh, epickarh], [-0.8, 0.8], 'r--') + plt.plot([arhpick.getpick() + pickerrarh, arhpick.getpick() + pickerrarh], [-0.2, 0.2], 'r--') + plt.plot([arhpick.getpick() - pickerrarh, arhpick.getpick() - pickerrarh], [-0.2, 0.2], 'r--') + plt.title([trH2_filt.stats.station, trH2_filt.stats.channel]) + plt.yticks([]) + plt.ylim([-1.5, 1.5]) + plt.xlabel('Time [s]') + plt.ylabel('Normalized Counts') + #plot 3-component window + plt.figure(3) + plt.subplot(3,1,1) + p31, = plt.plot(tdata, tr_filt.data/max(tr_filt.data), 'k') + p32, = plt.plot(ar3ccf.getTimeArray(), ar3ccf.getCF()/max(ar3ccf.getCF()), 'r') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'b') + plt.yticks([]) + plt.xticks([]) + plt.ylabel('Normalized Counts') + plt.title([tr.stats.station, tr.stats.channel]) + plt.suptitle(trH1_filt.stats.starttime) + plt.legend([p31, p32], ['Data', 'AR3C-CF']) + plt.subplot(3,1,2) + plt.plot(th1data, trH1_filt.data/max(trH1_filt.data), 'k') + plt.plot(ar3ccf.getTimeArray(), ar3ccf.getCF()/max(ar3ccf.getCF()), 'r') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'b') + plt.yticks([]) + plt.xticks([]) + plt.ylabel('Normalized Counts') + plt.title([trH1_filt.stats.station, trH1_filt.stats.channel]) + plt.subplot(3,1,3) + plt.plot(th2data, trH2_filt.data/max(trH2_filt.data), 'k') + plt.plot(ar3ccf.getTimeArray(), ar3ccf.getCF()/max(ar3ccf.getCF()), 'r') + plt.plot([arhpick.getpick(), arhpick.getpick()], [-1, 1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [-1, -1], 'b') + plt.plot([arhpick.getpick()-0.5, arhpick.getpick()+0.5], [1, 1], 'b') + plt.yticks([]) + plt.ylabel('Normalized Counts') + plt.title([trH2_filt.stats.station, trH2_filt.stats.channel]) + plt.xlabel('Time [s]') + plt.show() + raw_input() + plt.close() + +parser = argparse.ArgumentParser() +parser.add_argument('--project', type=str, help='project name (e.g. Insheim)') +parser.add_argument('--database', type=str, help='event data base (e.g. 2014.09_Insheim)') +parser.add_argument('--event', type=str, help='event ID (e.g. e0010.015.14)') +parser.add_argument('--iplot', help='anything, if set, figure occurs') +parser.add_argument('--station', type=str, help='Station ID (e.g. INS3) (optional)') +args = parser.parse_args() + +run_makeCF(args.project, args.database, args.event, args.iplot, args.station) diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..e2a9b8b4 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from distutils.core import setup + +setup( + name='PyLoT', + version='0.1a1', + packages=['pylot', 'pylot.core', 'pylot.core.loc', 'pylot.core.pick', + 'pylot.core.io', 'pylot.core.util', 'pylot.core.active', + 'pylot.core.analysis', 'pylot.testing'], + requires=['obspy', 'PySide'], + url='dummy', + license='LGPLv3', + author='Sebastian Wehling-Benatelli', + author_email='sebastian.wehling@rub.de', + description='Comprehensive Python picking and Location Toolbox for seismological data.' +) diff --git a/splash/splash.png b/splash/splash.png new file mode 100644 index 00000000..7f843a12 Binary files /dev/null and b/splash/splash.png differ