├── .travis.yml ├── LICENSE ├── LaTeX in QTableWidget └── LaTeX in QTableWidget.py ├── README.md ├── findpeaks ├── Findpeaks example.ipynb └── findpeaks.py ├── mkl └── mkl_test.py ├── nosetests.py ├── rainflow ├── README.md ├── compile_dll.bat ├── librainflow.a ├── rainflow.c ├── rainflow.dll ├── rainflow.h ├── rainflow.o └── rainflow.py ├── sklearn └── plot_segmentation_toy.py └── wrapping C code ├── Compiling C with mingw-w64 and wrapping it with ctypes in Python 3.4.ipynb ├── README.md ├── compile.bat ├── compile_exe.bat ├── libsample.a ├── sample.c ├── sample.dll ├── sample.h ├── sample.o ├── sample.py ├── sample_exe.c ├── sample_exe.exe └── sample_exe.o /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | # command to install dependencies 5 | #install: 6 | # - pip install numpy 7 | # - pip install -r requirements.txt 8 | # command to run tests 9 | script: nosetests nosetests.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Janko Slavič 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /LaTeX in QTableWidget/LaTeX in QTableWidget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf8 -*- 3 | # see: http://stackoverflow.com/questions/32035251/displaying-latex-in-pyqt-pyside-qtablewidget 4 | # by: Jean-Sébastien 5 | 6 | import sys 7 | import matplotlib as mpl 8 | from matplotlib.backends.backend_agg import FigureCanvasAgg 9 | from PySide import QtGui, QtCore 10 | 11 | def mathTex_to_QPixmap(mathTex, fs): 12 | 13 | #---- set up a mpl figure instance ---- 14 | 15 | fig = mpl.figure.Figure() 16 | fig.patch.set_facecolor('none') 17 | fig.set_canvas(FigureCanvasAgg(fig)) 18 | renderer = fig.canvas.get_renderer() 19 | 20 | #---- plot the mathTex expression ---- 21 | 22 | ax = fig.add_axes([0, 0, 1, 1]) 23 | ax.axis('off') 24 | ax.patch.set_facecolor('none') 25 | t = ax.text(0, 0, mathTex, ha='left', va='bottom', fontsize=fs) 26 | 27 | #---- fit figure size to text artist ---- 28 | 29 | fwidth, fheight = fig.get_size_inches() 30 | fig_bbox = fig.get_window_extent(renderer) 31 | 32 | text_bbox = t.get_window_extent(renderer) 33 | 34 | tight_fwidth = text_bbox.width * fwidth / fig_bbox.width 35 | tight_fheight = text_bbox.height * fheight / fig_bbox.height 36 | 37 | fig.set_size_inches(tight_fwidth, tight_fheight) 38 | 39 | #---- convert mpl figure to QPixmap ---- 40 | 41 | buf, size = fig.canvas.print_to_buffer() 42 | qimage = QtGui.QImage.rgbSwapped(QtGui.QImage(buf, size[0], size[1], 43 | QtGui.QImage.Format_ARGB32)) 44 | qpixmap = QtGui.QPixmap(qimage) 45 | 46 | return qpixmap 47 | 48 | 49 | class MyQTableWidget(QtGui.QTableWidget): 50 | def __init__(self, parent=None): 51 | super(MyQTableWidget, self).__init__(parent) 52 | 53 | self.setHorizontalHeader(MyHorizHeader(self)) 54 | 55 | def setHorizontalHeaderLabels(self, headerLabels, fontsize): 56 | 57 | qpixmaps = [] 58 | for indx, labels in enumerate(headerLabels): 59 | qpixmaps.append(mathTex_to_QPixmap(labels, fontsize)) 60 | self.setColumnWidth(indx, qpixmaps[indx].size().width() + 16) 61 | 62 | self.horizontalHeader().qpixmaps = qpixmaps 63 | 64 | super(MyQTableWidget, self).setHorizontalHeaderLabels(headerLabels) 65 | 66 | 67 | class MyHorizHeader(QtGui.QHeaderView): 68 | def __init__(self, parent): 69 | super(MyHorizHeader, self).__init__(QtCore.Qt.Horizontal, parent) 70 | 71 | self.setClickable(True) 72 | self.setStretchLastSection(True) 73 | 74 | self.qpixmaps = [] 75 | 76 | def paintSection(self, painter, rect, logicalIndex): 77 | 78 | if not rect.isValid(): 79 | return 80 | 81 | #------------------------------ paint section (without the label) ---- 82 | 83 | opt = QtGui.QStyleOptionHeader() 84 | self.initStyleOption(opt) 85 | 86 | opt.rect = rect 87 | opt.section = logicalIndex 88 | opt.text = "" 89 | 90 | #---- mouse over highlight ---- 91 | 92 | mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos()) 93 | if rect.contains(mouse_pos): 94 | opt.state |= QtGui.QStyle.State_MouseOver 95 | 96 | #---- paint ---- 97 | 98 | painter.save() 99 | self.style().drawControl(QtGui.QStyle.CE_Header, opt, painter, self) 100 | painter.restore() 101 | 102 | #------------------------------------------- paint mathText label ---- 103 | 104 | qpixmap = self.qpixmaps[logicalIndex] 105 | 106 | #---- centering ---- 107 | 108 | xpix = (rect.width() - qpixmap.size().width()) / 2. + rect.x() 109 | ypix = (rect.height() - qpixmap.size().height()) / 2. 110 | 111 | #---- paint ---- 112 | 113 | rect = QtCore.QRect(xpix, ypix, qpixmap.size().width(), 114 | qpixmap.size().height()) 115 | painter.drawPixmap(rect, qpixmap) 116 | 117 | def sizeHint(self): 118 | 119 | baseSize = QtGui.QHeaderView.sizeHint(self) 120 | 121 | baseHeight = baseSize.height() 122 | if len(self.qpixmaps): 123 | for pixmap in self.qpixmaps: 124 | baseHeight = max(pixmap.height() + 8, baseHeight) 125 | baseSize.setHeight(baseHeight) 126 | 127 | self.parentWidget().repaint() 128 | 129 | return baseSize 130 | 131 | 132 | if __name__ == '__main__': 133 | 134 | app = QtGui.QApplication(sys.argv) 135 | 136 | w = MyQTableWidget() 137 | w.verticalHeader().hide() 138 | 139 | headerLabels = [ 140 | '$C_{soil}=(1 - n) C_m + \\theta_w C_w$', 141 | '$k_{soil}=\\frac{\\sum f_j k_j \\theta_j}{\\sum f_j \\theta_j}$', 142 | '$\\lambda_{soil}=k_{soil} / C_{soil}$', 143 | '$H_1$ [g/N]'] 144 | 145 | w.setColumnCount(len(headerLabels)) 146 | w.setHorizontalHeaderLabels(headerLabels, 18) 147 | w.setRowCount(3) 148 | w.setAlternatingRowColors(True) 149 | 150 | k = 1 151 | for j,_ in enumerate(headerLabels): 152 | for i in range(3): 153 | w.setItem(i, j, QtGui.QTableWidgetItem('Value %i' % (k))) 154 | k += 1 155 | 156 | w.show() 157 | w.resize(800, 200) 158 | 159 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-tools 2 | Python tools 3 | -------------------------------------------------------------------------------- /findpeaks/Findpeaks example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "%matplotlib inline" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "This is a response to the blog on finding peaks in Python:\n", 19 | "[Peak detection in the Python world](http://blog.ytotech.com/2015/11/01/findpeaks-in-python/?utm_source=pulsenews&utm_medium=referral)\n", 20 | "\n", 21 | "The findpeaks.py can be found here: [https://github.com/jankoslavic/py-tools/findpeaks](https://github.com/jankoslavic/py-tools/tree/master/findpeaks/findpeaks.py)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "from findpeaks import findpeaks" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 3, 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "data": { 40 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXIAAAEICAYAAABCnX+uAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAABXLklEQVR4nO2dd5gkV3mv39M5Tg4bZ/NKWgmtwioBAgkJAUKW4BLNYpJBBhuMr+2LwOJiG1sXDL44AebKBBssjMBkISSSJASKq7y7CpvzTt7p6ZzO/aOqenpmOlR3V4eaPe/z7LPT3dXVp9JXX/3OF4SUEoVCoVDYF0e7B6BQKBSKxlCGXKFQKGyOMuQKhUJhc5QhVygUCpujDLlCoVDYHGXIFQqFwuacdoZcCHGFEOJom8dwuRDieauXtQudcAwaRQgxIoSICiGcVi671BFC/FQI8c52j6MRhBC7hBBXtHscxXS0IRdCHBRCJPSLYFQI8e9CiFC7x9UoUsr7pZRnWL2sAoQQa4UQ9wgh4kKI54QQVzfjd6SUh6WUISllzspllzpSytdIKf/D6vUKIbYIIXYIIab1f78QQmyx+ncApJRnSynvNTmug806B4vpaEOu8ztSyhBwAbAN+Hibx6PobP4LeALoB24G/lsIMdjeISlawHHgjUAfMAD8CPhWW0fUSqSUHfsPOAhcXfT6s8Ad+t+XAg8Ap4CngCuKlns38CwwC+wH/qDosyuAo0Wv/xjYDaxCOwHu0Nc5BdwPOEyOVQJ/COzRf/dvgA36GCPAtwFPmTEcBP4ceBqYAW4HfBWW/V/6sjHgK8Aw8FP9d38B9Jb67sJ9CvwV8B3gP/XvPgNsBj4GjAFHgGvKbO9NwH8veO+fgH+u4xhIYGPR638H/rbo9XXAk/pxeQA4t8yYNgMpIFz03v3A+2s438zu27X6uF3663v1Y/5bfdmfAQMVlv1bfVuiwI/Rbjy36efKo8DaUt8t+v579b/fpf/mP+j7Zz/wYv39I/pxfGeFbX6X/p1Z4ACwfcF6P492Tj4HXGXmGtM/v0E/ZhFgH/DqMmP/DfD3wLT++68pWsc64NdF+/4LwH+aOI4u4I+AeIVl7gU+BTyij/GHQF/R59cDu/R9ei9wVoVr6NvA1/Vx7gK26Z99A8gDCf04fwTwoV1vk/q6HwWGG7aVja6gmf8W7LDV+k76G2ClviOuRXuqeKX+elBf9rVoRlQALwfiwAULjQjwCeDxou99CvgS4Nb/XQ4I/bMvAl+sMFapnwxdwNloBuWXwHqgG+1m8c6FYyjazkeAFWgexbPoxqfMsg+hGZiVaBfq48D5+knyK+AvS323zEmYBF6FdvJ/He1iulnf/vcBB8ps7xp9v4b1107gBHBpLcegaN+VNOT6do0Bl+i/8U59G7wlxvR64NkF730e+Jcazjez+3Yti43zPrSbiV9//ekKy+7V949xbrwAXF10HL5W6rtF3y82hlk0w+pEu0EcRjN6XuAaNAMTKrG9QTQjdob+ejlw9oL1/k/9XHgLmkHvM3F8L9aXfSXa9bkSOLPM2DNo55kT+ACaZ21ccw+iGXkP8FJ9rBUNOZpxzKIZ0I9XWO5e4Bhwjr4fvmusWz+GMX38bjQDvJc5R+wgi6+ha/Vt+BTwUKnrTX/9B2g37oC+/IVAV6O20g7Syg+EEKfQ7tz3Af8HeDtwp5TyTillXkr5c2AH2s5ESvkTKeU+qXEfmnd0edE6hRDic2gn+ZVSynH9/QzaybxGSpmRmj4t9XX+oZTyD6uM9TNSyoiUchewE/iZlHK/lHIGzas7v8J3/1lKeVxKOYV2oM+rsOy/SClHpZTH0DzOh6WUT0gpk8D3q/zOQu6XUt4tpcyieeeDaAYog/ZoulYI0bPwS1LKQ2hG7vX6W69A84Ae0j+vdgzMciPw/6SUD0spc1LTV1NoT2QLCaEZkGJmgHANv9fIvv2alPIFKWUCzUs7r8qy+4rOjX1Syl8UHYdajuEBKeXXpKbB347m9HxSSpmSUv4MSAMby3w3D5wjhPBLKU/o567BGPCP+rVwO/A8mgGvdnx/H/iqlPLn+vV5TEr5XJnfPySl/Dd97P+Bdv0NCyFGgIuAT0gp01LK36DJJRWRUvag3Rw/iCaxVeIbUsqdUsoY8L+BN+sT0m8BfqKPP4N2M/GjPemU4je6LcqheeFbK/xmBu3pa6N+Pj8mpYxU265q2MGQv05K2SOlXKMb0wSaN/gmIcQp4x/aHXs5gBDiNUKIh4QQU/pn16LJJgY9aAbiU/qFZPBZtDvvz4QQ+4UQH61xrKNFfydKvK40UXuy6O94lWUb+Z1q65qQc5NyCf3/cuv7JvC7+t9v018Dpo6BWdYAf7bgWK9Ge3pZSBTtiaiYLjSP1CxL4Rgipay6Pt2AvQV4P3BCCPETIcSZRYscMxwZnUPo+73K8V2N9nRihsI+k1LG9T9D+u9MFb0HmlRUFX27vgR8XQgxVGHR4vUdQvO+B/TfPlS0vry+7Mpq24B23H1CCFeZZb8B3A18SwhxXAjxGSGEu9L2mMEOhrwUR9Dupj1F/4JSyk8LIbxoj0l/j6Y99QB3oj0CGkyj6a5fE0K8xHhTSjkrpfwzKeV6NI3sT4UQV7Vqo5pADO0RDgDd27By4u87wBVCiFVonvk39d8xcwyKiRePE1hW9PcR4JYFxzogpfyvEuvZBawXQhR74Fv19+1KTP+/3P5pCP1p7JVoTtBzwL8VfbxSCFF8zEaA4yaO7xE02aURTgB9Qoji7V5dw/cdaPusnPFduL4RNG95Ak3eWWN8oO+D1WhSTK3IeS+0p5u/llJuQfPwrwPeUcd652FXQ/6fwO8IIV4lhHAKIXx6bPIqND3NC4wDWSHEa9AklHlILXxoO/A9IcTFAEKI64QQG/UDNwPk0B497coLaN7Ba/W7/sfR9o0l6JLUvcDX0B7vn9U/MnUMingSeJt+LF+Nprka/BvwfiHEJUIjqG/PIrlESvmCvq6/1M+J1wPnohkdI35dLvxeJ6Pv42PA2/X98x4aN5IACCGGhRA3CCGCaHJVlPnn+xDwx0IItxDiTcBZaAa72vH9CvBuIcRVQgiHEGLlAk+/Krp0twP4KyGERwhxGfA7FbbllUKI8/V91AV8Ds1he7bcd9D26Rb9ZvFJtMn7HJos9lp9/G7gz9D2zwO1bIPOKNo8mTHOK4UQL9KdqgjazaNhG2NLQy6lPII2K/4XaCfTEbRoA4eUchYtEuXbaAfybZTR1qSmrb8H+LEQ4gJgE9rseBRtouWLUsp7AIQQXxJCfKmZ22U1umz0h8CX0YxBDLA6EeebaJN0BVmllmOg82G0i/QU2s31B0Xr2oE2GfZ5fV170SbJyvFWtDDVaeDTwBvl3BzIauq7GNvN+9DO70m0iXSrtsEB/CmaBzqFdgP9QNHnD6NdExPALWj7crLa8ZVSPoI2+foPaA7RfRR5uDWwHbgMbbv/Fk3/T5VZtgct9HQGTdbZgBYpk6yw/m+gTayfRJvM/mN9/M+jzcP9C9q2/w5aGHS6jm34FPBxXRb8c7Snqf9GM+LPou2bb9Sx3nkYs8MKxZJHCPFl4DtSyrvbPZZORwjxLrTokpe2eywGQojbgeeklH9pwbruRYtS+XLDA+sAygnyCsWSQ0r53naPQWEeIcRFaE8KB9CkmxvQnrIUC1CGXKFQdCrLgO+hhesdBT4gpawWUnhaYom0oscZfxktuF4C75FSPtjwihUKhUJRFas88n8C7pJSvlEI4WF+qJRCoVAomkjDHrkQohst5Gu9NLmygYEBuXbt2oZ+V6FQKE43HnvssQkp5aJcECs88nVoIYBfE0JsBR4DPqxnVxUQQtyIlk3JyMgIO3bssOCnFQqF4vRBCHGo1PtWxJG70ErM/quU8ny0WOVFqe1SylullNuklNsGB1VVUYVCobAKKwz5UbRKdg/rr/8bzbArFAqFogU0bMillCeBI0IIo4vNVWhlORUKhULRAqyKWvkQcJsesbIfLT1XoVAoFC3AEkMupXwSrb6FQqFQKFqMLYtmKRQK4LbbYO1acDi0/2+7rd0jUrQJlaKvUNiR226DG2+EuN534dAh7TXA9u3tG5eiLSiPXKGwIzffPGfEDeJx7X3FaYcy5AqFHTl8uLb3FUsaZcgVCjsyMlLb+4oljTLkivpQE23t5ZZbyHr9898LBOCWW9ozHkVbUYZcUTvGRNuhQyDl3ESbMuatY/t2fvhHf8nRrkHyCFizBm69VU10nqa0pdXbtm3bpCqaZWPWrtWM90LWrIGDB1s9mtOWd371Ee57YZyVPX5++9FXtHs4ihYghHhMSrkoZ0d55IraURNtHcGhSa3A6GgkST6veu+ezihDrqgdNdHWdjK5PEemE/QE3GTzkolYuebyitMBZcgVtXPLLWR9aqKtnRydTpDLSy5Z1wfAyZlkm0d0GtJBE/7KkCtqZ/t2/ukt/4sT3UPkEUSHV6qJthZzUJdVLlvfD8AJZchbS4dN+CtDrqiZncdm+JdlF3PnHQ+x6WN38KX/+KUy4i3m4IRmyC/doBly5ZG3mA7LrFWGXFEz//nQIfxuJ2+8cBWDIS+jEWVEWs3BiRghr4vNQ2E8TofyyFtNh034K0OuqIlIMsMPnzzO9VtX0O13M9zlZXRWTbS1moOTcdYOBHA4BMPdXk7OJNo9pNOLDpvwV4ZcURPfe+woiUyO37tsDQBDXT7GlEfecg5OxljTHwRgeZef48ojby233EKugyb8lSFXmEZKyX8+fJitq3s4Z2U3AMNdXk4qQ95SMrk8R6cTrNMN+bJun9LIW8327dz5ob/umMxaVY9cYZqnjs6wdyzKZ954buG94bCPU/EMyUwOn9vZxtGdPhihh2sHdI+828ddO5NIKRFCtHl0pw+3bXwpH/rAFrwuB8//7WvaOhblkStMc2AiCsCFa3oL7w13+QAYVzp5yzAiVtb2BwDNI0/n8kzF0u0c1mlFNpfnqSMzOASksnnS2Xxbx6MMucI0J2c0Y71MN94AQ11eABW50kIOGIa8yCMHFUveSp47OUsik+O81T0ARFPZto7HMkMuhHAKIZ4QQtxh1ToVncVoJEnY6yLonVPkDI98NKI88lZxaDJG2OuiP+gBYFm3NummdPLW8cThaQBetnkQgNlkpp3DsdQj/zDwrIXrU3QYJ2YSLOv2zXtvzpArI9IqDkzGWTMQKOjhBY/cLsegg1Lb6+Xxw6cYCHk5a3kXALPJJeCRCyFWAa8FvmzF+hSdyclIapEh7w248TgdjM7axIgsAQ5OxFirR6wADIS8OB3CHrHkHZbaXi+PH57mgpEewj7t6XRJGHLgH4GPAGUVfyHEjUKIHUKIHePj4xb9bIeyBDyOUozOJAseuIEQgqEuL2NKWmkJ6Wyeo9Nx1g3MGXKnQzAc9tpDI++w1PZ6mIimODQZ54I1vYS9bmAJaORCiOuAMSnlY5WWk1LeKqXcJqXcNjg42OjPdi5LxONYSDaXZzyamjfRaTDc5VPSSos4Oh0nLykkAxnYJpa8w1Lb6+GJw6cAuGCkt8gjt79G/hLgeiHEQeBbwCuEEP9pwXrtyRLwOEoxEU2Ty8tF0gpoSUHKkLcGo+rhuoHAvPeXd/vtYcg7LLW9Hp44PI3LITh3VTch3ZDb3iOXUn5MSrlKSrkWeCvwKynl2xsemV1ZAh5HKYzszVIe+VDYp6SVFnFwQnMS1pbwyE/MaElBHc0ttyD9829Cdqtl//jhac5e0YXP7VxyGrnCYAl4HKUwvL3SHrmP2VSWWJu9ktOBg3roYZ8eemiwvNtHIpMjkujwY7B9O8c/+0+F1PbsqtW2qmVvJAKdP6IlxXldTjxOR9sNuaUp+lLKe4F7rVyn7bjlFrLvfR+uZFEEgc08jlIY0snCyU7tPS0paGw2xTqvqvrQTA5MxFg7EFyUir9cjyU/EUnQHXC3Y2im2fvKG3jnkeUA/Pu7L+KKM4baPCLzGIlAFxRlN4d9riWhkSuK2b6dRz726YLHMTO0wlYeRzlOzCRxO0UhCaUYFUveOg5NxgsZncUss1F2Z3G1TCNL1S4YiUAXjPQU3gv5XPbXyBWL2X3ldbz0A1/jhn/+NW+46Zu2N+KgGemhsA+HY3FRpmGVpt8SpJScnEmyomfxU5GRFGSHCc/xqDaf4nc72T9usSFvcujv00dnGAh5WdkzV8JW88iVIV9yxFI5AF53/kr2jkXZNx5t84ga5+RMsqQ+DlpNckBNeDaZZCZPOpenx7/4qWgw7MUh7OKRp7TuRsvC7J+w8NpoQejv2GyKlT2+edJWyOsiqgz50iOezuJzO3jNOcsAuHvXyTaPqHFGI8mSESsAYa8Lv9upPPImM5PQdNhu/2IN3O10MBi2R6eg8dkUQ2Ev6weCHLDSI29B6O9kLLVoojnscxNRGvnSI5rKEvS4WNHj59xV3fxs12i7h9QQUkpOVPDIhRCq5VsLOJXQytSWMuSgFc+yhUc+m2RQN+THZ5LE0xZ5sy0I/Z2KpukPeee9F/YqjXxJEktlCxUCX3X2Mp48csoW2mU5IsksiUyurEcOmryiPPLmMhMv75EDLO+yR3bn+GyKoS4f6wa1SVvLJjybHPorpWQilqY/tNAjVxr5kiSayhUM+TVbhgH4+W77yiuF0MMyHjlokSuqd2dzqSStgH3S9McK0koIsNCQNznZKJrKks7mGQjO98iNqJV2JmMpQ94E4uksQY/W9mzjUIj1A0HutrG8UkgGquCRD4e9jEZSnZ9ZaGOqGfLl3VpiVrtjmisRTWWJp3MMhr2Fwl+WRa5s387Rz8wlG432Dlsa+jsZ1aStxR65m1xeksjkLPmdelDZG00glsrSE9AOthCCa85expfv389MIlP2IuxkDEO+vIpHnsjkmE1l6fLZbxvtgBmPHPQGIB16DIyntqGwF7/HyYpun6Wx5LuuuI73f2A5175oGXc+c5KHf+cqhi1a92RMmwNaqJGH9KfvaDJLwNMek6o88iYQTWULBxfg/JEesnnJkal4hW91LkadFaOtWymMz5S80jwiiQxCUKjvsRAjMctoydeJjOkT4kNhbazrB0PstzA89+i0do2947K1ANz7/Jhl654wPPJFUSva8Yi0USdXhrwJxFI5gt65jvLGgZ+0aXPck5EkfUEPXpez7DKq5VvzmUlkCHtdJZOyAHr01HzDc+9EjCbdxo1/3UCQ/eMxyyS5o9MJwl4Xl6zrY3m3j3ues673gdHcutRkJ7S3AqIy5E0glp7/iGXEnU7F7GnkSjWUWMiydqbpL9FGHguZSWQq1lExJJdONuSGRz6oyxPrB4PMprIFb7dRjk4nWNnrRwjBFWcM8Zu9E5Z1uJ/UM1JLxZFDe2uSK0NuMVJKYguklTlD3rkXWCVOzCQr6uMw52G13CNfoo08SlFtjsUehjyJx+koPD2sH9QiV6ySV45Ox1nVq0WuvOLMIaKpLDsOTlmy7olomrDPtejJtFgjbxfKkFtMMpMnL5nXab7L58bpEPb1yCPVPfKAx0XY52q9R75EG3mUopoh97uduJ2iow35eCTFYNhbSHFfb0SuWDDhKaXk2HSCVb1aHZSXbOzH43Twq+es0cknY2kGQovniTqhJrky5BYT07PUijVyh0PQG/AUNDY7kcrmmIylK4YeGgx3+RhrdRPmdjfyaKGsU82QCyHo9rs725BHNUNusKLHj8flsCRyZSaRYTaVLRjygMfFJev7uMeiCc/J6OL0fKDQt3NWaeRLB6O5QnBBGFJf0G1LQ24UwlrWXT5ixUBr+dbip452NvJosawzk8hWDV/t8ruJdLAhH4toyUAGTodgbX/AEmnl6LRWZ8aQVkCTV/aNxzg82XjE2GQ0XbKMc6gD+nYqQ24xxsx10LvQkNvTIy+0eOv2V1kShsNtSNO/5RaS7gU3mVY18mihrCOlJJLI0FXFkHe6R27UWSlm/UDIkqQgI/TQ8MgBrtSbVvzqucYT8iZji+usgHYzCnicSiNfShglbEMLDHl/0GvL8EMzWZ0GA2FvIbysVey68jo+8qoPEhlaQR5BpJWNPFoo6xglbKt55J1syNPZPNPxTCGG3GD9YJDDU3EyucaiSwyPfHWRR752IMjGoRB3NViBNJ+XTMVSDIQWe+TQ/norypBbjKGRB7zzZ7Z7bSqtjFZouryQbr+bVDZPsoWpyt98+DB3b72K/IEDvPLvf8WffuaHLWvkIVevLv1BE2SdalmdBp1syCei82PIDdYNBC1JmDs6nSDscy0K0bx+6woePjDFiQZK/J5KZMjLxclABmGfW8WRLyUMjXyhR94X9DKTyJBt0OtoNSdnkvjcDrr81VOPjZCyVmm0sVSWHz55nNeeu5yegIetq3p4+uiplvw2wO4P3kTc1RpZp6MMeZ0TvHNZnQuklUIIYmPySnHoYTHXb12BlPDjp47XvW4jhryUtALa9d7OmuQNG3IhxGohxD1CiN1CiF1CiA9bMTC7EiujkfcHPUip3dntxIlIkuXd/kXNfkthGJlWbeOPnjpONJVl+yWaB/yiVd2MzaZaotNnc3k+7Dybz73pz4kOr2x6R/haDHkkmSGfb1LxsgYmeI3yDYs1cmvK2R4tCj0sZu1AkK2re/jhk/Ub8nLp+QbhNvfttMIjzwJ/JqXcAlwK/JEQYosF67UlhkZuVD80mEsKspe8omV1Vo9YAQotyE7FW2PIv/nwYc4YDnPBiNbR/NxVPQA8deRU03/7vx49wt6xKBfd/EGee+hp1t/0Y+7/+Y6myTq1GHIpmxgK18AE78I6KwY9ATcOAdPx+q8NKWVZQw5ww9YV7DoeYe/YbF3rL1cwy8D2GrmU8oSU8nH971ngWWBlo+u1K+U8crsa8pMVWrwtpJWZhc8cneGZYzO87ZKRwtPC2Su6cDoETx+daepvzyYz/OPPX+DidX1cs2WYjUOaNLCnTiNhBrOG3IhqaZq81cAE79hsCiFYNGEohCDgcRFP1z+3ciqeIZrKlpRWAK7buhyHoG6vvFydFYOw1710olaEEGuB84GHS3x2oxBihxBix/i4dYVsOo1oOovH5cDtnL9r7WjI83nJWCRVsaFEMYZGfqoBz8osP3jyGF6Xg9edP+cz+NxONg+HefpYcw35l+7bx2QszcdfexZCCHoCHgbDXvaMNq/Jdi0eefHyltNA3P74bIr+oAeXc7HZ8XucJBow5HMx5KU98qGwj5dsHOCHTx6vq0DXRDSNENAbKG3IQz7X0ogjF0KEgO8CfyKljCz8XEp5q5Rym5Ry2+DgoFU/23EsrLNiYMcKiFPxNOlc3rxH3sLqewcmYqwfDC0ybFtXdfP00VNNa3CRz0u+veMo12wZLkg5AJuGQuwZa74hr1ZnvOmG/JZbtAndYkxO8I7PJhkMlz6XAh4n8QainUrFkC/k+q0rODwV58k6pLfJaIq+gAdnmcqTYZ+LWDpHrllzE1WwxJALIdxoRvw2KeX3rFinXYmncgQ8i8u9Go0mpm1kyA/r4WAjfaUfVxcS8rhwiNYY8uOnEqzsWXzRnruqh1PxDEemzIeanZhJ8Ikf7jQ1WbX7RITx2RSvOnvZvPc3DYXYOxZt2g0kksgQ9rnKGhKDphvy7duZ/ZcvFrrwHO8eIv//zE3wjs2mFk10GvjdThINNGEuldW5kFedswyPy1GXvDIZTZdMzzcoFM5q04SnFVErAvgK8KyU8nOND8neLGwqYeBxOQj7XLaSVg7o4WBGS65qOBytq/Vx7FSClT2LvbtzV3UD8FQNYYhfuf8AX3/wELfet6/qsvfoBZhefsb8p8qNw2GiqWwhE9ZqzHaXasU8xa+3XcNLP/A1Pv69p3jx+7/K3ldeb+p7C9Pziwl4nA1p5Een43T5XBX3UZfPzdVnDXHH08drDgOejKXK6uPGusHGhhx4CfB7wCuEEE/q/661YL22JJbOLproNOgPemwlreyfiOJ0CFab9MhBMyTNjlqJJDPMJrOsKOGRn7EsjMfl4BmTOnkqm+N7TxxDCPi3+w9U7XD0q+fH2Lqqe1EVvE3GhGeTdPJOMuQP7p8g4HHy7hevBWDHwemq38nnJRPRSoa8sclOLWKl+nl6/daVTETT/HbfZE3rn4yWTs83aHe9FSuiVn4jpRRSynOllOfp/+60YnB2JFpGWgHoDXpsJa0cmIgx0hdYNHFbie6Ap+lx5MdPaY/RpQy52+lgy/Iu0yGIv9g9xlQszV9ffzaZXJ5/+uWesstOxdI8eeQUV545tOizgiFvkk5u1pAHPE5cjuaWsn1w3yQXre1j41CI/qCHxw5VN+TT8TTZvCxryK2Y7KykjxtcccYgfUEP//HAwZrWPxlLM1BBWil0CWpT5IrK7LSYeBlpBWzokY/HTMsqBq2QVgxDvrLMhXvuqm52HpsxNfF0+44jrOj2sf2SNWy/ZIRvPXqEfWUq8f36hXGknCvEVEx/yEtf0FN3nHI1zBryZpeyHYsk2Tce47IN/QghuGBNL48frm7IC52BKk521mcEtRjy0lmdC/G5tSeJXz03xrMnFsVklCSdzTOTyFT2yL3trUmuDLnFxFLlpRWtAqI9mkvk85KDk7FC1p1ZevxuZpocfnjslCZ/lJrsBG3CM5bOcWCisnd8dDrO/XvGedO21Tgdgg9dtQmfy8Hf3/18yeXveX6M/qCHF63sLvn5xqFQ26UVaO7N9MH9miRx2fp+AC5c08uBiVghhb0cY7Ol66wYBBrwyE/FM8TSOVMeOWiNmYMeJ18yMScCc4lKlTTyQrs3G2vkiiLKTXaCJq1MxdJNi2ywkpORJMlMnnWDNRryQGs8crdTFPo+LmSrMeF5pLJO/p0dRwF407ZVAAyEvNz4sg38dOfJRV5mLi+574VxXn7GYNnmx0YIYjOOby2GvJk1yR/aP0nY6+LsFV2AZsgBHj98quL3xsvUWTHwu111G/IjJkIPi+kOuHnbJSP8+KnjpuqUG8W+yqXnQ3GXIJtq5Io5pJTE0+U18v6gh0xOtrUmg1mMuhf1SitNq/UBHJtOsKzbV9agrh8MEfQ4K8YL5/KS7+w4wks3Dsx7JH/v5esYCHn41J3PztuGJ4+c4lQ8U1JWMdg4FGImkWG8indaK8lMjnQ2X7UWuUFTPfJ9k1y8rq+Q1POild24nYIdhyr3xTQ6Ry1Mzzcw4sjruQmaCT1cyHsvX4/L4eDW+6t75ZNGnZUK0orSyJcQqWyebF5WkFa0E8EOIYhGD8X1A6Gavtftd5NvZq0PyseQGzgdgovX9fHrPeNlDcNv9k5wfCbJWy+an5EY9Lr4X686g0cPTvPJO3YXvn/v82M4HYKXbSqfzLZpKAzAXovlFbNZnQbNMuQnZhIcnIxz2Yb+wns+t5NzVnbzeJUJz7FIirDXhb+Mk+P3OJFSu4ZqpZAM1GfOIwetLeH/uGAl395xtGp7wkKdlQoeud/txOkQSiNfCpQrYWvQF9QuRDtMeB4Yj+F3O00XzDLobnatDzRDXipipZhXnDnEocl42Yp6tz96mL6gh6u3LPaw37xtNb//0nX8+wMH+VddR73n+TEuHOldVOu6mE3DzYlc6RRD/qAesnfp+v5571840stTR2dIVzDC4xWSgYDCU2w9IYjHphN0+VyFWG6z/MHLN5DJ5fnqbw5WXM6MRy6EIORtX5q+MuRgWQNdo/JhOWnF8MjtEIJ4YCLKuoGgqfK1xRgZrM2KJc/m8pyMJCt65ABXFFp8LW68OxlN8fPdo/yP81fidS0+VkIIbr72LF533go+c9fzfOGevew8FuGKMyuXlhgKewn7XOztAEMeaYK89eC+Sbr9brYs75r3/oVrekln8+w6Xn5O4uh0vOLNd86Q1+7RRpLZwnlXC+sGglx7znJue+hQxVrik7E0bqegy1e5Jn/I61KTnW3Dwga6RnegSuGH0EKPvIEb1P6JWM0TndD8hJTR2RR5WTqGvJjVfQE2DYVKdlD//hPHyOQkb76oTIcftCzVz7xxKy/bPMhn9SiWSvo4aDcAbcLT2hDEmXjthjwvtQJuVvLg/kkuWde3aG7iAn3Cs1w8uZSSfeOxQpXIUvj1ZuX1THjGUtmyzlM13nv5OmZTWX6+q3xPz8loir6gp6pT085StsqQW9hAt1wJW4PeVlZAbOAGlc7mOTIVrzn0EIoqICaas43H9Imtah45aPLKIwem5k0uSym5/dEjnLe6h83D4Yrf97gc/Ov2C9i6uof1g0HOXFZ5edB08k7wyGHuBmAFx08lODqdWCSrgKY3r+r1l40nH42kiKaybKjgGATc9UsrsXT5SLFqbF3VQ9jr4rEKsfCT0TT9weoSY9jnUpOdbcPCBrrRKoY86HHicTlaI600cIM6PBUnL2uPWAEtjhya55FXyupcyBVnDJHJSX6zZ6Lw3lNHZ9gzFuUtFbzxYoJeF9/7wIv54R+9xJTMtGk4xEQ0benNulZD3tWEY7BTL3mwdXVPyc+3rellx8HpkpPLRoLVhsHyHnkjGnkslSNQpyF3OATnr+mtOFk7EUtXjCE3CPvczKaURt4eGqivvJBCdyBv6cc8IUTrsjsbuEEZE4TrK1x45TCMSLM08mMFQ169tO62tb2Efa5CoSuA2x89gs/t4Lpzl5v+TadDVC0fa2DIB1Z65YZBriX8EKydcN59IoIQlH0quWBNL2OzKY7PLI4AMfZFZWlFu2YSdWR3xtPZRR25auHCkV6eH50tq5NPRlOLauuUIuRVHnn7aKC+8kIMjTzoKe8d9AY8rZFWGrhBGRmR6/pr98h9bic+t6OpHnlvwE2gwj42cDsdvGzTIPc8P4aUkkQ6x4+fOs61L1pu2jDXSjO6Bc0kMoS91UvYGjRjnmL38QjrBoJlnzbPXqElYe0qUaxs33iUsNdVJWpFW2/dHrmJ86EcF67pRUp4okxS01QsXTH00EBp5O1k+3a49VYm+5eRRxBfvrLuBrrVwg9BS/NthUee+KtPknDPv3DiLi9PfeAjVb97YCJGf9BTMdSuEt1+t6X6bDFmQg+LueKMQcZmU+w6HuGnO08QTWV5yzZzsko9LO/24xBw4pR15WwjiYxpbxya0+Bj94nIomiVYs5aHkYI2HV8cf2SfeNR1g+FKkpTjUgr8XSWUJmnYDOcN9KDQ5SerI2ns8TTuYqhhwYhn4paaS/bt/Mnf/cD1t/0Y27/zv11N9A1DHmgwknV16IKiF8ZuYybXvVB0itXgxDIkRG++LaP8Pb4Bg5W6VZeT7GsYnr8nuZNdlZJBlqIEYZ4z3Nj3P7oEdb2B7h4XV9TxgaaDDMY9lZNMqmFWtLzwXqPfCaR4eh0gi0ryhvygMfF+oFgaUM+FmNjFZmuIK20WCMHzfE6c1lXSZ28EENuwiPv8rlJZ/OksvVXcawXZch1jEcioyZEPcTSOdxOUTI22aAV0ko0leXLvzlA7A1vwXP0MOTziEOHeOs/fhSnU/Cn336y4vcPTDRmyJuVkCKl5Nh0bR75YNjL1lXdfPuxIzx8YIo3bVtdc2x8rQx3+RiNWJemP5PIFKKBzBD0aFmGVh2D3bpxruSRgyavLKwoOJvMcDKSZMNQ5fOpXo88nc2TzuUb0shBk1eeODy9qGLmZJWmy8UUugS1QV5RhlzHyMiaaKBORqXKhwb9QQ/RVLapd+3/eOAgp+IZPnz1pnnvr+oN8CdXbeLxw6d4pkyn+Wgqy9hsqq4YcoPuQHOaS0SSWWLpXE0eOcCVZw5xZCqBQ8AbLlhl+bgWMhT2MWphp6BTNXrkVpey3a0b50oeufH5sVOJeU+c+/UuU5UiVgB8LsMjr80IGh58Ixo5aIY8ls7x/Mn5cxtGVUcz0spc4SxlyNuGFR55NJWtONEJ0Bcyenc2R0OOpbJ8+f79XHnG4LzmwAavv2AVPreDbz5yqOT3DxZqrDQirTSn+l4toYfFGIk8L988yLJuc42kG2G4y1so22oFtUorYO1T0e7jEQbD3rIFrwyMioi7i7xyM6GHoIUB+t21t3urloRnFqOK48J48oJHbkJaaWffTmXIdQxDPhGtX/bQPPLKj3h9ASO7szl1yb//xDGm4xk+dNWmkp93+938zrkr+OGTx0vWhTAuvHU1Fsta+BvN6BJUraFEOV60spt3XLaGP7l6s+VjKsVwl4+pWNqyp656DHmXxR55NVkFiiJXilL1945FcTkEa/qrVyY0KiDWgpl5KTOs6vUzGPYu0smNUFyzceRAxXT/ZqEMOZDJ5UnoJ1AjHnk8nasqrfQ1Obvzrp0n2TAY5IKR3rLLbL90DfF0jh+U6CZ+YCKGEJi68MrRE3ATT+cqFlGqh1piyItxOASfvOGcssksVmMUGmvkXDKotYStQbdFT0XpbJ69Y7NVZRXQzu3l3b55E577xqOs6TfXLrCedm8xfflqT8LVEEJoSU1F5XhHI0m+/sBBrj5r2JR0085StpYYciHEq4UQzwsh9gohPmrFOluJseP9bieTsVTdxYYqNZUwMO7stRjysUjSlFE8FU/z4P5JXnX2sorLbV3VzZblXXzz4cOLMvEOTMRY2ePH567fw2lWvZVjpxJ4nA4GTKRLt5OhLu1GY8WEZ61ZnQZWSSt7xmbJ5KQpjxw0eWW+IY9VlVUMAh5nzUWz4oZH3uBkJ2jyypGpRKEB96d/+hyZvOR/X3eWqe/bWiMXQjiBLwCvAbYAvyuE2NLoeluJsePXDQTJ5GTdF4CZ4j29gdoM+WOHprn8M/fwv3+ws+qyv3x2jFxe8upzKhtyIQRvu2SEZ09EFjVfaDRiBbQGzAAzFocgHj+VZHlP+YYSncKwriWPWTDhWb8hd1liyAsRKyY8cm25bvaPR0mkc2RyeQ5OVC6WVYzf46pDIzeyqRvzyGGu+Nfjh6fZcXCK7z9xjBsvX88ak4lxhrTSDo1cNNqWSghxGfBXUspX6a8/BiCl/FS572wLh+WOCy9s6HetJJbK8syxGfpCXqaiKc5d1VPXHf7xw6fo8rsqxsxK4OH9k6zs9bO6SkeTZCbHzuMRsrm81uh2pKfiI+rzo7PEUlnOH+mlmqnL5SWPHZ6mP+gpeExTsTR7xqIMhb0NGfNTiQzPnYhw9orugpdiBTuPR3CI6mFw7SaTy/PYoWnW9gcbnlyNJLPsPj7Dmcu7CnVszHB4Ks7xmSSXrOurei5U4uBkjLHZFBetNbeeqViaF0ZnOXtlNy6H4Kkjp9gwGKqY1Wmw+0QEKecmTc0wEU2xdyzK1tU9+Bt4igTIS8mjB6cZ7vIRSWTI5vNsXd2D02S4al7CIwcmWd0XqDmyyizivvsek1JuW/i+FdLKSuBI0euj+nvzByDEjUKIHUKIHZlMewrLlCOrSyl+t7Y7Mrn6tN28lFUPugBcTgeZXOUbaCYneU4Phdo8HEZKWTESIiclp+IZrdymibE6HYKBkJfJaJpEJscLo1FeGJ3F73ayvMGT0KV7zNm8tRp5OpvD6+r8aR2X04EQgnSd51ExOX0fump8CnE5BUi5KC66VrT0d6fpm4HhGcdS2YLeXa4r0EIcQtQ8XmN5s+ULqv1+yOtiNJIkns4y0hc0bcS172tPu9kmtjksi5SyoX/AG4EvF73+PeDzlb5z4YUXyk7i7p0n5Jqb7pDfe/yIXHPTHfIHTxytaz0b/+In8tM/fbbqcq/4+3vk+7+xo+zniXRWvuGLv5Wbbr5T7jg4KaWU8ndvfVC++FO/lJlsruR37nz6uFxz0x3ygb0Tpsf7zNFTcs1Nd8j1H/uJ3PQXd8rP/2qPTJdZfy0cGI/KNTfdIb/72JGG12WQzubk2o/eIf/vz563bJ3N5MWf+qX8n7c/0fB6/nuHdk4eGI/W9L1vPXJIrrnpDnlkKlb3b+fzeXnOJ+6SN3//6Zq+c+5f3S0/+t2n5Bfu2SPX3HSHjCTSpr77R7c9Jq/47D01jfHW+/bJNTfdIWeTmZq+V47/85Pdcs1Nd8g3fekBmc/na/7+BZ/8mfyL75nfX7UC7JAlbKoV7s0xoLh4xSr9PdtgaORGf8p6og1S2RyZnDSVYdYf9Fast/LXP97FjkPT/MObz+PCNVo6+TsuW8uxUwl+WaLjDcDdu07SG3Bz0dry0SoLOWdlN1eeMciFa3q588Mv5Y+u3GgquqAahZrkFiYFnZxJIiWsrDFipV0MdXkZa/NkZ/H36+HodILZVJYty7tNf0cIUZjw3DcWY7jLa7pAWT2TnUYceaBBWcXgFWcOEfa5+Ovrz64rAzjgrT3yxgqsEDAfBTYJIdahGfC3Am+zYL0tw4inXtnrx+0UdcWSx1PmJ136gh72jpcuc5rN5fnRk8d504WreG1RqdWrzxpiRbePrz94cFFUSjqb55fPjfGac5YVupub5Wvvvrim5c0Q9rkRAktjyQsx5D31h0W2kuGwrxCT3wi1lrA1sKIm+a4aJzoNtizv4usPHSIvpemIFdCyM2ue7NQDDKyaAL9kfT9P/+U1dZdxCLhdhZtLK2nY/ZJSZoEPAncDzwLfllLuanS99fBvv97P/XvGa/6e4ZF3+dwMhLx1eeTVmkoU01uhcNau4xFi6RyXb57fH9LldLD90jX8du8kexeUSH1w/ySzyWzVsMNW4XQIwl6Xpdmdx2fqiyFvF8NdXkvS9GstYWtgRU3y3Se0yWUznZGKOXtlF+lsnp3HIqYjVqD+OPJG0/MX0kgtnoC39uxUK7Bk5khKeaeUcrOUcoOUsvZC3hZweDLO//nps3xnx9GavxtNZfG6HHhcDgbD3rrqrZipRW4wGPIwHU+XPGkfPqB3Ki9Roe+tF63G43Tw9Qfnp9fftfMkQY+Tl2wcqHnczaIn4OFU3Lrww0OTcYSoPT2/XQx1+Ygksw0/ZtdawtbACmll9/EIGwZDNecUGBmeUD01v5iA20k2L2tKJIubyKZuJYE6bkZW0PkhACa57ZFDSDmXslsLkWS2oOPV65HP9eusflJtW9tHXsID+yYWffbQ/inWDwQLSSXF9Ie8XLd1Od997Ch7x6IcmYpzeDLOz3ef5IozhxpK4rEaqysg7hmLMtIX6KhtrMSwfvwaLWdbT3o+NG7IpZTsPj5Ts6wCWp0eI7qoFkNeTynbZnjkjeB3uwqx7a1kSRjyVDZX8MTrCcafTWbo0uOdB0N1euS6Rm6meM8l6/sIepyLJi5zecmjB6a4ZH35etnvvGwtsXSOqz93H5d/5h5e9tl7mIimeXWHyCoGPQFr663sHY2yqYbH9HZjpOk3mt1ZryEP6XJMvYb8gX2THJ9JclmJZsvVcDkdnKnH+lcrX1tMoUtQDe3eGm3zZjVBr7PmCo5W0Dm3sgb46TMnmYql6fa765pomE1mC4krA2Gtg08+L2uaQInVoJF7XU4u3zTIr54dQ75OFjS5Z09EmE1lS3YqN9i6uoevvfsiJvSnBiEEPreD15xjvgdlK+jyuwu1URolm8uzfyLKlWcOWbK+VjBcSNNvzCOfiqU5q44EKCEEXb76szv/5Vd7GO7y8rrzF6WEmOL81T0cmYqzrMSTZTnqqUkeTeVqSpRqNgGPsy0e+ZIw5Lc9fIi1/QHOWdldskNJNWaTmXnSSi4vmY6nTdUgNihMdpp8zLvqrCHu2nWSXccjnLNS0xQf2q/p45esq+wFGWVZO5keC9u9HZ6Kk8lJe3nkYWsM+dhsipdtrq+2jCZv1e7YPHpwiof2T/GJ67bULWX92TWbec9L1tU0cViPtBJPZTsqJDXgcSmNvB6eOxnh0YPTbL9kDWGfq05pZc4jN1KJx2uUV+KFmg/mTvwrzxxCCK0+isFD+6dY0x9oSc3sZmNIK7LBEhCg6eNQuQt7p9Hld+F1ORqqSx5PZ4mmsgx11W/I64la+fyv9tIf9PC7F1dv1F2OsM/NSI0VNOvxyOMdppEbsfBWnPe1YHtDfttDh/G4HLzxwlWEvK66SkjOk1Z0L3xitraIi1rCD43fOW91D798bhTQ9PFHDkxyaRVv3C50+93k8tKSx8y9uiHfYCNDLoTQW77V75EbCUXVGjqUo56a5E8dOcV9L4zz3svXm06tt4o5Q27+Go51mEbu9zjJS0hZXMK5GrY25LFUlu8/cYzrzl1Ob9BD0OsikcnVXK+hWFqZ88hruwBjqSxOh6ipFsjVZw3z9NEZxiJJnjsZIZLMVpzotBM9fq0CohUhiHtGZ1nZ42+4C0yraTSW3PjucAs98s/fs5duv5vfu2xNXb/ZCH63dnxrilox0V6xlRjSaqtjyW1tyH/45HGiqSzbL9FOOuNCr2XC0/AaG/XIYynNM6hFE7zqLE3r/tVzYzy8Xytof0kdUQKdiBWZhQZ7xqK2klUMhrp8DaXpG7JMvR55rSGgz56I8PPdo7znJevactOsVVpJZ/NaWYwOMuTGU0w9YdCNYGtDfveuk2wcCnHBSA8wv/KaWQwpxvDIu3wuPC5HzSGIsXSu5pP/jOEwK3v8/OLZMR7aP8nqPn/Tyl+2GqPeSqMTnrm8ZO+YvUIPDYYbbMI8Z8gbmew0P0/xxXv3EfK6eNeL19b1e41SMOQm270ZEowVTSWswvDIEzW2rGsUWxvyU/E0q3r9BS+4HkNu9NczPHIhBIN1JAXV84gnhOCqs4b4zd5xHj4wVTVaxU5Y1SXo2HSCVDbPpmH7GfKhLi+xdK7uRgNjs0k8Tkfhplgr3X432bw05eFGU1nu3nmSN164iu46f69R5qJWzO0vq9q8WUlAeeS1M7ugtVrYW3urpbk6K3PrGQh7a45aiaayBOp4xLvqrGGSmTwziQyXlEjLtyuFCogNGvI9el2ZjUO11fvoBOaSgurzysciKQbD3rprf/Tq/WHNPF3e89wY6Vx+XqG2VhOoUV+2qvGylQTqCKG0Alsb8mhRtAkUe+Tmd+JswSOf80IGQ566PPJQHSfUJev6Cge/UiKQ3Zib7GzUkNsv9NCg0Vjysdlk3aGHABsGtaxKI+qnEnftOslAyFuxaXezcToEHpfDtBGsJQmvVdR6M7IKWxvy2eR8j9yI4a7lUdbwyIvXoxXOqm2yM57O1fWI53M7ecWZQ6wbCLK6zx4lWs3gczvwOB0NSyt7RrXWc/Wkqbcbo15OPbV7QPPI69XHYe4p5oXRyoY8mclxz3NjvOrsYUs67TSCFodtViPvPGmlMNnZ4jT9ztkDNZLN5UlkcvM86VAdGvlsar5GDlrkylQsRS4vTZ/Y0QbCoD79hnPbkg3WTIQQdAfcDTdg3js2a0t9HCyQVmZTDT2ldfvdLOvysWd0tuJy9++ZIJ7OVW3a3QoCbvOGvCCtdNJkp1dJKzVRqkhVsI7ww9kFUSugeeR5ab7TvTae+stphrwuU81p7UajFRClNCJW7KePg3ZcAx5nXYWzkpkcM4lM3THkBpuGQ7wwVtmQ37XzJF0+V0dIe36Pk4TJollz2dSd448G3IYNUobcFIYnHSrypEMNTHYu9MihtkfiWDrXUSdUJ9DjdzekkZ+YSRJL52ypj0Nj2Z3jDcaQG2weDrN3LEq+TJJcJpfnF8+OcvWWYUva/DVKLV2CojWUjm4VtUbeWEX7j1ydGAcxXGQ8vS4HLoeoOfzQ43TMKw5USAoyGbmSyeVJZ/OEOkir6wT6gp66SgIbGBOddowhNxgK19e706hjPtigR755OEQyk+fIdLzk5w/vn2ImkemYMsj+mjTy2grVtQKPy4HbKZRHbpbCJGWRJy2EIOh11aaRL4h8gaI0fZMeudGvs9W1KTqd1X0Bjkwl6i4gZGi7m4btKa2AVs52tI7mEnN1VhqVVipPeP505wkCHicvW9BasF3U0mHHkFf9HdZspB0VEG1ryBdmZBqEvC6iNYUfLjbkAyHz8bcwl8XVSVXYOoGRvgCJTK7mmHyDvWNR+oMe+vR4aDti1Fup9WY2V2elMWnFeJp5ocSEZy4vuXvXKFee0TndpYzqgWaIp61tvGwVtWyDVdjWkM+mFocNgqaX1eaRZ0reDHxuh2mP3DDkfo9td2dTGNHDKY9MlX6sr4Zda6wUM9zlI5nJE6mxKufYbAqXQ9AXaOwmFva5Wd5dOnLlicPTTERTvKoDolUM/G7z3mw01ZnzUv42NJdoyPIIIT4rhHhOCPG0EOL7Qogei8ZVlWiJSUowPPLGpBUhBAM1tHwzTrxOe8RrN0Y96sN1GHIpJXtGZ21vyI1Y8rEaJzzHZlMMhLyWeJubhsMlpZW7dp7E43Rw5RmdIauA7s3WUGulk0rYGgRtKK38HDhHSnku8ALwscaHZI6oEbWyyCOv1ZBnFhly0HRys5LAnEfeed5BO1nZ40cIODRZuyEfn00RSWZtPdEJMKxr3CdmajfkjWR1FrN5KMS+8ei88s5SSu7efZKXbOxf9ETaTmpJCIqlOquphIHfU5sqYAUNGXIp5c+klMaIHwJWNT4kc0STWYRYnAwQqmuyc/GJPBDymi5lm8woj7wUPreTZV2+ujxyo2WfnSc6Adb0a2nyBydjNX1vLJJsOPTQYPNwmFQ2P+84PD86y5GpBNd0SLSKgd/jJJ3Nm+opEE/Xn7vRTIIep62rH74H+Gm5D4UQNwohdgghdoyPjzf8YxE9PX9hQaFao1YW1msx6A96mDLZFCGupJWyjPQF6tLIv/7gQfqCnrbW/rCC4S4vIa/LVL2TYqz0yI3M2OIJz5/tGkWIuZr4nUItXYJiqWxHeuQBT202yAqqGnIhxC+EEDtL/LuhaJmbgSxwW7n1SClvlVJuk1JuGxxsXJOLprJ0lfCka9HI83lJNF3aI/fXEAalJjvLM9IXqFlaee5khHueH+ddL15r+5BOIQQbhkI1GfJ0Ns9ULN1w6KGB8VRTPOH5s90nuWCk1zKv3yoMedLMtVdPD4BWUIvtsIqqe0FKeXWlz4UQ7wKuA66SLew4Gl1QMMvAMORSyqrlP6PpLFLOL2Fr4Hdrj0dm1pPUD1qnhHB1EiN9AcZmUyTSOdNG+db79uN3O3lHG9qNNYONgyHu32P+KdSYZG809NAg5HWxssdfmPA8dirBzmMRPvqaMy1Zv5UE3Oa7BMVT2Y6qs2IQrGHC1ioajVp5NfAR4HopZX0xZnUSTWXnJQMZBL0u8hKSmerNT0ul5xsEPE5yeUk6V309CaWRl8WIXDlaJrNwIcdOJfjRU8d568Wr6Wkw9K5T2DgUYmw2VWhiUg0jhtwqjxz0miu6R/6L3VrD72u2DFu2fquopd1bp5bF8HtchSTBVtGoFvB5IAz8XAjxpBDiSxaMyRQLm0oYhGooZVuqFrlBLY94c9KKMuQLMWLJzcorX7n/AADvvXx908bUaowQyn0m5ZVGe3WWYvNwmP3jMbK5PD/brbVIXD/YeRFBhVolVQpnSSl1jbzzrrmgx0k6lydjwgm0ikajVjZKKVdLKc/T/73fqoFVYzaZKeuRg7lSttU8cjDnGRjG3ufqvJOq3RiG3Ezkyql4mm89epjrt65YMr1LobYGD1BkyC2a7AQtwzOdy/P0sRke2j/FKzvQGwfzjRnSuTzZfGc1Xjbw12A7rMK2s3PRZLaktm0c2EY98loMeTKTw+tydFyqcCfQF/QQ9DhNGfJvPHiIeDrHjS9fOt44aDczj9PB3nFzhnw8ksQhtMgpq9isT3jeet9+cnnZkbIKmL/uDOmiIxOCvOaf5q3Cvoa8jLQSrsmQl/fIDb3brLSiZJXSCCEY6Q9WNeTJTI5/f+AgV54xyJnLulo0utbgcjpYOxAwLa2MRlL0h7y4LCwra8g7d+06yVDYy9ZVPZat20rmysBWvu6MngP19MltNoUGzC2st2JLQ57TO4OHvIs96VqklUhFacV4xKu+nkQ6V5htVyxmpM9f1ZDf+cwJJmNp3reEtPFiNtYQgjg2m7R0ohO062JVryZXvXLLcMc+PZr2yDuwzZtBLU6gVdjSkBvediWNvBZppVQ8ekHnMhFGlMjk8CmPvCxGUlC55gYA33rkCGv7A1y2of1daprBxsEQh6fihSzgSozNNtarsxyGvNKp+jjMddip5kAZ13egEzM7va1vwGxLQ17QtsvEkcNcreLK68nidgq8rsW7IWDyEc9YRoUelmekP0gqmy9M4i1k71iURw5O8ZaLRqrG7NuVDUMh8tJcqv7YbMqyGPJiLlzTy2DY29E3S7PSSrxEq8dOoR0NmG1pyAvdgUp65PpONOmRh33uksajpqiVjDLklagWuXL7o4dxOQRvvLBlpXpajqFRV5NXsrk8E9HmeOTvf/kG7vnzK/B2cHSVR+/yVe1JuKCRd+CTcLCG0GWrsKchL9EdyMDYibMmJztL3Qygtt57arKzMpUMeTqb57uPH+Pqs4aXZANqgw2DIYSobsgnY2mkhMEmeOROh+hID3YhZlLcO7HNm0FhsrOF9VZsacjLNZUAcDgEQZNlJCsZcrPxrKDdeVV6fnlW9vhxCDhcQlb4+e5RpmJp3nLx6jaMrHX43E5W9fqrGnKrWrzZGTMddowuYJ2okRdk2Ram6dvSkJdrKmFgtgLibDJDuETkCxTNPJs4GEklrVTE43KwvLt05Mq3Hj3Myh4/L9vUOc0NmsWGwRD7xitr5EbT5WZo5HYh4HGZiCMv78y1G8MJNDNPZxW2NOSFxstljLDZCoiVPHKnQ5sENR1Hrgx5RUb6AosM+ZGpOPfvmeBN21bh7NBwOCvZOBhi/4IGDwsZVR65VrCuahx5DiE6M5va53YghDlZ1ipsacgL3YEa9shLF94y8JvsVlJLZb/TlVKG/Ns7jiAEvGnb0pZVDDYOhUhl8xybTpRdxvDIB0KnryE30yUonsoScHde42XQkuACbvOdjqzAnoZc7w5ULj036HWa8sgjyUzJGHIDswcjmckrjbwKI/0BJqLpwg12Kpbm9keP8PLNg0uqrkolCpEr44sbIRuMzaboC3rwlAiJPV3wmygDG0tnOzKr08DvcbW0AbMtzxaj8mG5mOOQ112YDClHPi+JpspLK6DPnlepwpbN5Unn8h0ZBtVJGJErR6bjRFNZ3v21R5hJZPjjqza1eWStw0wI4uOHplk/EGzVkDqSgMdZVZaIpTqzqYRB0Ft9G6zEloY8msyWTAYyCHmrR63E9KYSlQy5mUmXZFYrVak08soYhnzvWJQ/+MYOdh6P8IW3XWD7Vm610BPwMBDylDXku49HeO7kLNeft6LFI+ssTE12pjuzhK2B3+1sqUfeube0CpRrKmFgRiOfK5hVXloxo5EbYVIqRb8ya/QGEx//wU5OxTN87s1bubqDU8WbxYbB8jVXfvDkMVwOwXXnnt6G3O9xVi1lEEvlOjKG3CDQ4nZvtvTIZ8u0eTMwE7VSqfKhgZmDkUwrj9wM3X43YZ+LU/EMn7huC//jgqWbxVkJo3jWwq6Iubzkh08e44ozBumzsHytHfGbmJuKp7MdGUNuEPS6VIp+NWZTpRsmGwS9LlLZyh06KtUiNzCTmKDavJlDCMH2S9Zw87Vn8Z6Xrmv3cNrGxqEQkWSWI1PzI1ce3DfJaCTF688/PW9wxQQ8c/1yyxFNZTvaIzcTQmkltjTk0TLdgQxCJkrZmvHI/W5X1YMx1+bNlruypXz0NWfyvpctzTK1Zrn6rGG8Lgd/85Pd8wzV9584Rtjr4qqzhto4us7A73Eiq/TdjadzhbpKnUjQW13ntxJbWp9oqtpkZ/VStpFCCdvK0kq1MKhCmzflkStMsLovwJ++cjM/3z3Knc+cBLRz6K6dJ7j2RcvVeQSF2v6Vnoa1fp0d7JGbeJq3Ensa8ioaedBEKVvDI68YR25isjOppBVFjfz+S9fxopXd/OWPdjIdS/Oz3SeJpXO8/oKV7R5aR1CtzpGUsvM9cpPJhFZhiSEXQvyZEEIKIQasWF8lcnlJLJ2rErWiHeBKHrnZqJV0Nl8xpXpOWunck0rRWbicDv7uDedyKp7hb3/yLN9/4hgre/xcvLav3UPrCPxVik6lslrj5c72yDVppVIzFStp2JALIVYD1wCHGx9OdeZqkZc3wGakldlkBpdD4HOX3wVmqpgZ0oryyBW1sGVFF+9/+Qa++/hR7nthnBvOW9GR6ebtoFovAOP9Tk4IMrYhmW2NV26FR/4PwEeAltx6Coa8kkbuMzfZGfaVzw4F7a4KlbU65ZEr6uWDr9jIhsEgUsLrz1eyikGhzWKZ6864rjs5IShY5WZkNQ3d0oQQNwDHpJRPVWvRJYS4EbgRYGRkpO7frNRUwsAIS6o22VnJq4e5SZdKkSvKI1fUi8/t5F/ffiEP7J1gk95PUzGnkZe77gqNlzvYIy84gakchJr/e1X3hBDiF8CyEh/dDPwFmqxSFSnlrcCtANu2bavbey9UPjQRtVLNI+/yV958v4m7quGRq2gDRT1sHg4XmiIrNAxvtlyKe9ROHnmVWk1WUdWQSymvLvW+EOJFwDrA8MZXAY8LIS6WUp60dJRFRMx45KYMefmmEgZmDbnbKXA7bRkApFB0HN1+7bqciadLfl5o89bRHrnR7q3DpRUp5TNAIXtBCHEQ2CalnLBgXGUpdAeqcBA9Lgcep6Ni387ZZLZQyKkcZqUV5Y0rFNbRE9BKFEzHMyU/N4xjJ2d2GjeZVmV32s6NNBO1AprHXn2ys4pGbmKyU7V5UyisxeNyEPK6mK7qkXfudWfYhFbVW7HslialXGvVuiphZrITtINc6bEmkshUTM+H6vGsxmcqYkWhsJaegJtTZT1yQyPvXI+8ELqsPPLSzKa07kCBKl5w0FO+AmI+L4mms3T5q3nkJjTytPLIFQqr6Q14mIqV9sgNybST48gNaaVV4Ye2M+TRZJaQx1U1eSJUoSZ5VG8qUanOCpg05BmlkSsUVqN55KUN+VQ0jd/t7Ogn4Wqx8FZjO0M+W6XyoUGwQk1yM5UPoUhaURq5QtFSegOespOdU7F0x9dsnyv8pTzyklTrs2kQ8lUy5NVrkQN4nA6cDlHVI+/keFaFwo70BtxlJzsnY2n6Q51tyF1OBx6Xo2WTnbY05Ga0sZCnvLQSSZjzyIUQBNzOipOd8XROtXlTKCymN+hhNpklW6I5jB08cmhtuzfbGfLZZJZQFU8ajL6dpXfibKEWefX1+KscjKSa7FQoLKdXjyU/lVgsr9jFkAc95W2Q1djOkFdrKmEQ8jqJprIly0ia1cihek3yhNLIFQrL6QloTlapCc/JWIp+Gxhyv8dJokUp+rYz5LPJjClppRD+U0IWMauRw1xd4XKoOHKFwnoMj3wqNt8jj6ezJDN5+oLedgyrJlrZXMJ2hjyaND/ZCaXrrURq8Mj9bkfZu2o+L0lm8ir8UKGwmN5Cmv58j3wyqr22i0ceV9LKYsx0BzKo1FwikszgcTpMGeBABY88ldUmYpS0olBYSzlpxUgSsotG3qrqh7Yy5EYojylpxVPeIzdTwtag0mRnoalEhS5DCoWidgxDvTCWfDKWAuj48ENQHnlZojVIIoZGbnynGDMFswwqTXaq7kAKRXMIeJx4nI4K0krna+Rmmrdbha0MuRFtEqpSR1xbpry0MpusXjDLoKIhN7oDdXDxHoXCjgghtDT9BZOdBWnFBh55wONSCUGlKHQHqmWys8SOnDU5YQrgd7vKpugnC9KK8sgVCqvpDXiYKqGRe1yOQgeeTkYlBJWhlvhvo1ZxtIRGFUlU7w5kEPA4iWdySLk4Hj2u+nUqFE2jVOGsyVia/qCnYtP0TiHodZHNS9LZxdmpVmMrQ15oKmEqIaiyRl7LZKeUcxEqxcxp5LbajQqFLShVOMsuWZ0w5+C1ogKirSyQ2aYSoO1Ev9vJZDS16DNNIzfvkUPpAvHGeyqOXKGwnt6gp6RHbhdDbqgCrZjwtJUhn5vsrG7IhRAMd3kZnZ1vyI1Y9FomO6F0hqjSyBWK5tGrdwkqljWnbJKeD3NBEMojX4DRHchs09WhLh+jkeS89+ZCGM155MbBKDXhqcIPFYrm0RvwkM3LeU3Up6JpW6TnQ+ma5LkStZ+swFaG3Gx3IIPhLh9jCwx5pFBnxaRHXqFAfEJNdioUTaOQ3amHICYzOWLpnC2SgQACurRiVEB8/uQsr/7HX7P7eMTy32rYkAshPiSEeE4IsUsI8RkrBlWOtQMBXn7GoOnlh8NeRiOpeY9mkRpK2ELldm+GR640coXCegqFs3Sd3E7p+TDXHDqRyfLYoWne9KUHiCQzuJzWR9w0lMkihLgSuAHYKqVMCSGGrBlWad5x2Vrecdla08sPdXlJZHLMprIFw23o7NX6dRr4K0x2JjM5hACvy1YPNgqFLegNateskd1pp4JZQCHW/We7Rvnhk8dZ1u3j6++5mNV9Act/q1EL9AHg01LKFICUcqzxIVnHcJcPgLHI3ITnbM0aeWVpJeB22iKmVaGwGz1GcwnDkNuozgrM2Y5vPXqEdQNBvv0HlzXFiEPjhnwzcLkQ4mEhxH1CiIvKLSiEuFEIsUMIsWN8fLzBnzXHUNgw5HM6+WzNGnn5mee4qkWuUDSNPqOUra6Rz0kr9pjs7Pa7cToEF6/t41t/cCmD4eaNu6o1E0L8AlhW4qOb9e/3AZcCFwHfFkKslyXSIKWUtwK3Amzbtq05U7cLGO7Sdtzo7JwhjyRqM+QFaaVU+GE6p/RxhaJJdPndCDHnkdtNIw/73Nz14csZ6Q/gdTXXTlS1ZlLKq8t9JoT4APA93XA/IoTIAwNAa1zuKgzp0spoA9JKtclOFbGiUDQHp0PQ7XcXsjsnY2ncTmF6fqsT2DQcbsnvNCqt/AC4EkAIsRnwABMNrtMyQl4XQY9zXiz5bCqLz+3AY3KC0l8p/FBJKwpFU9HS9HWPPJqmN2CPOiutptFb21eBrwohdgJp4J2lZJV2osWSF3vk5tPzARwOgc/tKJ0QpKQVhaKp9ATcc1ErNkrPbzUNGXIpZRp4u0VjaQpDXV7G5mnk5kvYGgQ8rtIaeSZXmFlXKBTW0xvwcHJGu36nYinbRKy0miUfAD3c5ZunkUdq9MhBk1eURq5QtJ7egGfeZKddIlZazWliyJOF7M7ZZLbmyZJyBeKVRq5QNJfewPzJTrskA7WaJW/Ih8JeUtk8kYSmcc8mM6bT8w3KtXtLpPNKI1comkhv0KNlZyczzCazSiMvw9I35EYIoq6T19LmzcBfxiNPZnKF8ESFQmE9RuGsfeMxwD5Zna1myRvyYT2byghBjNTQeNkg4HERz8yPWpFSEk9nlUauUDQRo3DWvrEoYJ86K61m6RvyonormVyeZCZfs7TiLyGtpHN58lLVIlcomsmcR64ZcjXZWZolb8iHitL0a2neXEzAvVhaSaa1Hp5KI1comoehic8ZcuWRl2LJG/KAx0XY52IskioqmNW4R55Qbd4UiqZTkFYMjVwZ8pIseUMOWuTKaKR+j7zUZOdcm7fTYhcqFG3BkFYOTsQKtVcUizktrJARSz5X+bDG8EO3i3QuTzaXL7yn2rwpFM3H63IS8DjJ5iW9AbfpNo+nG6eRIU8RMboD+WtPCAKt/riBavOmULQGQ15R+nh5TgtDPtTlZXw2VXO/TgMjMiVZJK8klUauULQEQ15Rhrw8p4UhHw77SOfyHJ2KA3VErZSoSV6QVlT4oULRVAyPvF+FHpbl9DDkeiz5Xj2EKeS1wJDrHrnK7FQomktvUEkr1TgtDLkRS75nNErA48TlrG2z/R7N8CeKsjsNj1xp5ApFc+nVpRWVnl+e08KQD+tNmA9OxmrWx6GyR640coWiufQUpBVlyMtxWhhywyPP5GTN+jiUbvc2F0euDLlC0Ux6C5OdSiMvx2lhyH1uZyGRoB5DbnjkiRKTnb4md8dWKE53VPhhdU4LQw4wrHvltSYDgZbmD/M98mQmh9flUAkKCkWTuXBNLy/e0M+W5V3tHkrH0pAhF0KcJ4R4SAjxpBBihxDiYqsGZjVDuk5el7RS0MiLJjtVdyCFoiWs7gvwzfddSndApeeXo1GP/DPAX0spzwM+ob/uSAydvKuOWg3lpBU10alQKDqBRg25BIznnW7geIPraxpGLHk9Hrnb6cDlEItS9JUhVygUnUDtVm0+fwLcLYT4e7SbwovLLSiEuBG4EWBkZKTBn60do1NQPeGHsLgCYjKTUzHkCoWiI6hqyIUQvwCWlfjoZuAq4H9KKb8rhHgz8BXg6lLrkVLeCtwKsG3bNln3iOukEY8cNHklsSD8UGV1KhSKTqCqVZNSljTMAEKIrwMf1l9+B/iyReOynKFC1Eq9htw1T1qJp3M1p/orFApFM2hUIz8OvFz/+xXAngbX1zTOXtHN7126hpduHKzr+363k0R6foq+klYUCkUn0KhL+T7gn4QQLiCJroF3Ij63k7953Tl1f78n4Oae58d519ce4XXnrWQ2mVWTnQqFoiNoyJBLKX8DXGjRWDqav3vDudz28GF+9OQx/uT2JwF4ycb+9g5KoVAoaNwjP21Y3Rfgo685k4+86gx2HJrm7l0nufZFpeaAFQqForUoQ14jDofg4nV9XLyur91DUSgUCuA0qrWiUCgUSxVlyBUKhcLmKEOuUCgUNkcZcoVCobA5ypArFAqFzVGGXKFQKGyOMuQKhUJhc5QhVygUCpsjpGx5RVmEEOPAoTq/PgBMWDgcO6C2+fRAbfPpQSPbvEZKuajyX1sMeSMIIXZIKbe1exytRG3z6YHa5tODZmyzklYUCoXC5ihDrlAoFDbHjob81nYPoA2obT49UNt8emD5NttOI1coFArFfOzokSsUCoWiCGXIFQqFwuZ0rCEXQrxaCPG8EGKvEOKjJT73CiFu1z9/WAixtg3DtBQT2/ynQojdQoinhRC/FEKsacc4raTaNhct9wYhhBRC2DpUzcz2CiHerB/nXUKIb7Z6jFZj4rweEULcI4R4Qj+3r23HOK1ECPFVIcSYEGJnmc+FEOKf9X3ytBDigoZ+UErZcf8AJ7APWA94gKeALQuW+UPgS/rfbwVub/e4W7DNVwIB/e8PnA7brC8XBn4NPARsa/e4m3yMNwFPAL3666F2j7sF23wr8AH97y3AwXaP24LtfhlwAbCzzOfXAj8FBHAp8HAjv9epHvnFwF4p5X4pZRr4FnDDgmVuAP5D//u/gauEEKKFY7SaqtsspbxHShnXXz4ErGrxGK3GzHEG+Bvg74BkKwfXBMxs7/uAL0gppwGklGMtHqPVmNlmCXTpf3cDx1s4vqYgpfw1MFVhkRuAr0uNh4AeIcTyen+vUw35SuBI0euj+nsll5FSZoEZwM5t7c1sczG/j3ZHtzNVt1l/5FwtpfxJKwfWJMwc483AZiHEb4UQDwkhXt2y0TUHM9v8V8DbhRBHgTuBD7VmaG2l1uu9Iqr5sg0RQrwd2Aa8vN1jaSZCCAfwOeBdbR5KK3GhyStXoD1x/VoI8SIp5al2DqrJ/C7w71LK/yuEuAz4hhDiHCllvt0Dswud6pEfA1YXvV6lv1dyGSGEC+2RbLIlo2sOZrYZIcTVwM3A9VLKVIvG1iyqbXMYOAe4VwhxEE1L/JGNJzzNHOOjwI+klBkp5QHgBTTDblfMbPPvA98GkFI+CPjQCkstZUxd72bpVEP+KLBJCLFOCOFBm8z80YJlfgS8U//7jcCvpD6LYFOqbrMQ4nzg/6EZcbtrp1Blm6WUM1LKASnlWinlWrR5geullDvaM9yGMXNe/wDNG0cIMYAmtexv4Ritxsw2HwauAhBCnIVmyMdbOsrW8yPgHXr0yqXAjJTyRN1ra/fsboVZ32vRvJF9wM36e59Eu5BBO9jfAfYCjwDr2z3mFmzzL4BR4En934/aPeZmb/OCZe/FxlErJo+xQJOTdgPPAG9t95hbsM1bgN+iRbQ8CVzT7jFbsM3/BZwAMmhPWb8PvB94f9Fx/oK+T55p9LxWKfoKhUJhczpVWlEoFAqFSZQhVygUCpujDLlCoVDYHGXIFQqFwuYoQ65QKBQ2RxlyhUKhsDnKkCsUCoXN+f/v3HhKnrbUYQAAAABJRU5ErkJggg==\n", 41 | "text/plain": [ 42 | "
" 43 | ] 44 | }, 45 | "metadata": { 46 | "needs_background": "light" 47 | }, 48 | "output_type": "display_data" 49 | } 50 | ], 51 | "source": [ 52 | "n = 80\n", 53 | "m = 20\n", 54 | "limit = 0\n", 55 | "spacing = 3\n", 56 | "t = np.linspace(0., 1, n)\n", 57 | "x = np.zeros(n)\n", 58 | "np.random.seed(0)\n", 59 | "phase = 2 * np.pi * np.random.random(m)\n", 60 | "for i in range(m):\n", 61 | " x += np.sin(phase[i] + 2 * np.pi * t * i)\n", 62 | "\n", 63 | "peaks = findpeaks(x, spacing=spacing, limit=limit)\n", 64 | "plt.plot(t, x)\n", 65 | "plt.axhline(limit, color='r')\n", 66 | "plt.plot(t[peaks], x[peaks], 'ro')\n", 67 | "plt.title('Peaks: minimum value {limit}, minimum spacing {spacing} points'.format(**{'limit': limit, 'spacing': spacing}))\n", 68 | "plt.show()" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 4, 74 | "metadata": {}, 75 | "outputs": [ 76 | { 77 | "name": "stdout", 78 | "output_type": "stream", 79 | "text": [ 80 | "268 µs ± 3.94 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" 81 | ] 82 | } 83 | ], 84 | "source": [ 85 | "%%timeit\n", 86 | "peaks = findpeaks(x, spacing=100, limit=4.)" 87 | ] 88 | } 89 | ], 90 | "metadata": { 91 | "kernelspec": { 92 | "display_name": "Python 3", 93 | "language": "python", 94 | "name": "python3" 95 | }, 96 | "language_info": { 97 | "codemirror_mode": { 98 | "name": "ipython", 99 | "version": 3 100 | }, 101 | "file_extension": ".py", 102 | "mimetype": "text/x-python", 103 | "name": "python", 104 | "nbconvert_exporter": "python", 105 | "pygments_lexer": "ipython3", 106 | "version": "3.8.6" 107 | }, 108 | "toc": { 109 | "base_numbering": 1, 110 | "nav_menu": {}, 111 | "number_sections": true, 112 | "sideBar": true, 113 | "skip_h1_title": false, 114 | "title_cell": "Kazalo", 115 | "title_sidebar": "Kazalo", 116 | "toc_cell": false, 117 | "toc_position": {}, 118 | "toc_section_display": true, 119 | "toc_window_display": false 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 1 124 | } 125 | -------------------------------------------------------------------------------- /findpeaks/findpeaks.py: -------------------------------------------------------------------------------- 1 | """ Searches for peaks in data 2 | 3 | History: 4 | -nov 2015: Janko Slavic, update 5 | -mar 2013: janko.slavic@fs.uni-lj.si 6 | """ 7 | 8 | import numpy as np 9 | 10 | 11 | def findpeaks(data, spacing=1, limit=None): 12 | """Finds peaks in `data` which are of `spacing` width and >=`limit`. 13 | 14 | :param data: values 15 | :param spacing: minimum spacing to the next peak (should be 1 or more) 16 | :param limit: peaks should have value greater or equal 17 | :return: 18 | """ 19 | ln = data.size 20 | x = np.zeros(ln+2*spacing) 21 | x[:spacing] = data[0]-1.e-6 22 | x[-spacing:] = data[-1]-1.e-6 23 | x[spacing:spacing+ln] = data 24 | peak_candidate = np.zeros(ln) 25 | peak_candidate[:] = True 26 | for s in range(spacing): 27 | start = spacing - s - 1 28 | h_b = x[start : start + ln] # before 29 | start = spacing 30 | h_c = x[start : start + ln] # central 31 | start = spacing + s + 1 32 | h_a = x[start : start + ln] # after 33 | peak_candidate = np.logical_and(peak_candidate, np.logical_and(h_c > h_b, h_c > h_a)) 34 | 35 | ind = np.argwhere(peak_candidate) 36 | ind = ind.reshape(ind.size) 37 | if limit is not None: 38 | ind = ind[data[ind] > limit] 39 | return ind 40 | 41 | 42 | if __name__ == '__main__': 43 | import matplotlib.pyplot as plt 44 | 45 | n = 80 46 | m = 20 47 | limit = 0 48 | spacing = 3 49 | t = np.linspace(0., 1, n) 50 | x = np.zeros(n) 51 | np.random.seed(0) 52 | phase = 2 * np.pi * np.random.random(m) 53 | for i in range(m): 54 | x += np.sin(phase[i] + 2 * np.pi * t * i) 55 | 56 | peaks = findpeaks(x, spacing=spacing, limit=limit) 57 | plt.plot(t, x) 58 | plt.axhline(limit, color='r') 59 | plt.plot(t[peaks], x[peaks], 'ro') 60 | plt.title('Peaks: minimum value {limit}, minimum spacing {spacing} points'.format(**{'limit': limit, 'spacing': spacing})) 61 | plt.show() 62 | -------------------------------------------------------------------------------- /mkl/mkl_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Benchmark script to be used to evaluate the performance improvement of the MKL 3 | Copyright (c) 2010, Didrik Pinte 4 | All rights reserved. 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or other 11 | materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 17 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 18 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 19 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 21 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 22 | POSSIBILITY OF SUCH DAMAGE. 23 | 24 | Source: https://dpinte.wordpress.com/2010/01/15/numpy-performance-improvement-with-the-mkl/ 25 | """ 26 | import os 27 | import sys 28 | import timeit 29 | 30 | import numpy as np 31 | 32 | 33 | def test_eigenvalue(): 34 | """ 35 | Test eigen value computation of a matrix 36 | """ 37 | i = 500 38 | data = np.random.random((i, i)) 39 | result = np.linalg.eig(data) 40 | 41 | 42 | def test_svd(): 43 | """ 44 | Test single value decomposition of a matrix 45 | """ 46 | i = 1000 47 | data = np.random.random((i, i)) 48 | result = np.linalg.svd(data) 49 | result = np.linalg.svd(data, full_matrices=False) 50 | 51 | 52 | def test_inv(): 53 | """ 54 | Test matrix inversion 55 | """ 56 | i = 1000 57 | data = np.random.random((i, i)) 58 | result = np.linalg.inv(data) 59 | 60 | 61 | def test_det(): 62 | """ 63 | Test the computation of the matrix determinant 64 | """ 65 | i = 1000 66 | data = np.random.random((i, i)) 67 | result = np.linalg.det(data) 68 | 69 | 70 | def test_dot(): 71 | """ 72 | Test the dot product 73 | """ 74 | i = 1000 75 | a = np.random.random((i, i)) 76 | b = np.linalg.inv(a) 77 | result = np.dot(a, b) - np.eye(i) 78 | 79 | 80 | # Test to start. The dict is the value I had with the MKL using EPD 6.0 and without MKL using EPD 5.1 81 | tests = (test_eigenvalue, 82 | test_svd, 83 | test_inv, 84 | test_det, 85 | test_dot) 86 | 87 | # Setting the following environment variable in the shell executing the script allows 88 | # you limit the maximal number threads used for computation 89 | THREADS_LIMIT_ENV = 'OMP_NUM_THREADS' 90 | 91 | 92 | def start_benchmark(): 93 | if THREADS_LIMIT_ENV in os.environ: 94 | print("Maximum number of threads used for computation is : %s" % os.environ[THREADS_LIMIT_ENV]) 95 | print("-" * 80) 96 | print("Starting timing with numpy %s\nVersion: %s" % (np.__version__, sys.version)) 97 | print("%20s : %10s - %5s / %5s" % ("Function", "Timing [ms]", "MKL", "No MKL")) 98 | 99 | for fun in tests: 100 | t = timeit.Timer(stmt="%s()" % fun.__name__, setup="from __main__ import %s" % fun.__name__) 101 | res = t.repeat(repeat=3, number=1) 102 | timing = 1000.0 * sum(res) / len(res) 103 | print("%20s : %7.1f ms" % (fun.__name__, timing)) 104 | 105 | 106 | if __name__ == '__main__': 107 | start_benchmark() 108 | -------------------------------------------------------------------------------- /nosetests.py: -------------------------------------------------------------------------------- 1 | def test_numbers(): 2 | assert 12 == 12 3 | -------------------------------------------------------------------------------- /rainflow/README.md: -------------------------------------------------------------------------------- 1 | This is the implementation of the rainflow algorithm based on Primoz Cermelj and Adam Nieslony's code. -------------------------------------------------------------------------------- /rainflow/compile_dll.bat: -------------------------------------------------------------------------------- 1 | gcc -c rainflow.c 2 | gcc -shared -o rainflow.dll rainflow.o -Wl,--out-implib,librainflow.a -------------------------------------------------------------------------------- /rainflow/librainflow.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/rainflow/librainflow.a -------------------------------------------------------------------------------- /rainflow/rainflow.c: -------------------------------------------------------------------------------- 1 | /******************************************************************** 2 | * Copyright: (c) 2007-2008 Ladisk 3 | * Author: Primoz Cermelj 4 | *********************************************************************/ 5 | #include "rainflow.h" 6 | 7 | 8 | /*------------------------------------------------------------- 9 | * rf3 10 | * 11 | * Performs rainflow analysis without time analysis and returns 12 | * the actual number of rows in the output rf matrix - the 13 | * actual size of the data in array_out is (Nr x 3). 14 | * 15 | * Based on Adam Nieslony's rainflow.c for Matlab. 16 | *-------------------------------------------------------------*/ 17 | int rf3(double *array_ext, // (in) an array of turning points (see sig2ext on how to get these) 18 | int nr, // (in) length of the array_ext (number of rows of the vector) 19 | double *array_out) // (out) output matrix of size nr x 3; the columns are: 20 | // cycles amplitude, cycles mean value, number of 21 | // cycles (0.5 or 1.0). This array must be allocated 22 | // apriori. 23 | { 24 | double a[512], ampl, mean; 25 | int index, j, cNr, tot_num; 26 | 27 | tot_num = nr; 28 | 29 | // Init array_out to zero 30 | for (index=0; index= 2) && (fabs(a[j-1]-a[j-2]) <= fabs(a[j]-a[j-1])) ) { 39 | ampl = fabs( (a[j-1]-a[j-2])/2 ); 40 | switch(j){ 41 | case 0: { break; } 42 | case 1: { break; } 43 | case 2: { 44 | mean=(a[0]+a[1])/2; 45 | a[0]=a[1]; 46 | a[1]=a[2]; 47 | j=1; 48 | if (ampl > 0) { 49 | *array_out ++= ampl; 50 | *array_out ++= mean; 51 | *array_out ++= 0.50; 52 | } 53 | break; 54 | } 55 | default: { 56 | mean = (a[j-1]+a[j-2])/2; 57 | a[j-2] = a[j]; 58 | j = j-2; 59 | if (ampl > 0) { 60 | *array_out ++= ampl; 61 | *array_out ++= mean; 62 | *array_out ++= 1.00; 63 | cNr++; 64 | } 65 | break; 66 | } 67 | } 68 | } 69 | } 70 | for (index=0; index 0){ 74 | *array_out ++= ampl; 75 | *array_out ++= mean; 76 | *array_out ++= 0.50; 77 | } 78 | } 79 | 80 | return (tot_num - 1 - cNr); 81 | } 82 | 83 | 84 | /*------------------------------------------------------------- 85 | * rf5 86 | * 87 | * Performs rainflow analysis with time analysis. 88 | * 89 | * Inputs: 90 | * array_ext an array of turning points (see sig2ext on how to get these) 91 | * nr length of the array_ext and number of rows in array_out 92 | * array_t an array of time values 93 | * 94 | * Outputs: 95 | * cnr the actual number of rows in the rf matrix 96 | * array_out matrix of length nr x 5 where the result will be returned; 97 | * this array must be pre-allocated apriori. 98 | * 99 | * Based on Adam Nieslony's rainflow.c for Matlab. 100 | *-------------------------------------------------------------*/ 101 | int rf5(double *array_ext, int nr, double *array_t, double *array_out){ 102 | double a[512], t[512], ampl, mean, period, atime; 103 | int index, j, cNr, tot_num; 104 | 105 | tot_num = nr; 106 | 107 | // Init array_out to zero 108 | for (index=0; index= 2) && (fabs(a[j-1]-a[j-2]) <= fabs(a[j]-a[j-1])) ) { 118 | ampl=fabs( (a[j-1]-a[j-2])/2 ); 119 | switch(j) { 120 | case 0: { break; } 121 | case 1: { break; } 122 | case 2: { 123 | mean=(a[0]+a[1])/2; 124 | period=(t[1]-t[0])*2; 125 | atime=t[0]; 126 | a[0]=a[1]; 127 | a[1]=a[2]; 128 | t[0]=t[1]; 129 | t[1]=t[2]; 130 | j=1; 131 | if (ampl > 0) { 132 | *array_out++=ampl; 133 | *array_out++=mean; 134 | *array_out++=0.50; 135 | *array_out++=atime; 136 | *array_out++=period; 137 | } 138 | break; 139 | } 140 | default: { 141 | mean=(a[j-1]+a[j-2])/2; 142 | period=(t[j-1]-t[j-2])*2; 143 | atime=t[j-2]; 144 | a[j-2]=a[j]; 145 | t[j-2]=t[j]; 146 | j=j-2; 147 | if (ampl > 0) { 148 | *array_out++=ampl; 149 | *array_out++=mean; 150 | *array_out++=1.00; 151 | *array_out++=atime; 152 | *array_out++=period; 153 | cNr++; 154 | } 155 | break; 156 | } 157 | } 158 | } 159 | } 160 | for (index=0; index 0){ 166 | *array_out++=ampl; 167 | *array_out++=mean; 168 | *array_out++=0.50; 169 | *array_out++=atime; 170 | *array_out++=period; 171 | } 172 | } 173 | 174 | return (tot_num - 1 - cNr); 175 | } 176 | 177 | 178 | /*------------------------------------------------------------- 179 | * sig2ext 180 | * 181 | * Searches local extrema from time course (signal) sig. 182 | * 183 | * Inputs: 184 | * sig an array of n time points 185 | * time_sig an array of n delta time points (pass NULL if this array 186 | * is to be assumed in the form of 0, 1, 2,....) 187 | * n number of points (sig, time_sig) 188 | * clsn number of classes (pass -1 if not to be used, i.e., no 189 | * divisions into classes) 190 | * ext (output) extrema found on sig 191 | * exttime (output) time values corresponding to ext; 192 | * if time was NULL, a dt=1 is assumed. 193 | * Outputs: 194 | * np number of extrema (number of points on the output) 195 | * 196 | * Based on Adam Nieslony's sig2ext.m (Matlab function). 197 | *-------------------------------------------------------------*/ 198 | int sig2ext(double *sig, double *time_sig, long n, int clsn, 199 | double *ext, double *exttime){ 200 | int i, have_time = 0; 201 | double smax, smin; 202 | double *w1; 203 | int *w; 204 | int np; 205 | 206 | if (time_sig != NULL){ 207 | have_time = 1; 208 | } 209 | if (clsn != -1){ 210 | clsn--; 211 | smax = arr_max(sig, n, NULL); 212 | smin = arr_min(sig, n, NULL); 213 | for (i=0; i 2){ 259 | free(w1); 260 | RENEW(w, int, np); 261 | w1 = diff(ext, np); 262 | for (i=1; i max_val){ 313 | max_val = sig[i]; 314 | ind = i; 315 | } 316 | } 317 | if (pos != NULL) *pos = ind; 318 | return max_val; 319 | } 320 | 321 | /*------------------------------------------------------------- 322 | * diff 323 | * 324 | * Returns a new, dynamically allocated vector with the length 325 | * n-1 and values defined as: 326 | * v[2]-v[1] 327 | * v[3]-v[2] 328 | * .... 329 | * 330 | * Make sure the vector is cleared with free when no longer 331 | * needed. 332 | *-------------------------------------------------------------*/ 333 | double *diff(double *vec, int n){ 334 | // The length of vec_out is n-1! 335 | int i; 336 | double *vec_out; 337 | 338 | vec_out = NNEW(double, n-1); 339 | for (i=0; i<(n-1); i++){ 340 | vec_out[i] = vec[i+1] - vec[i]; 341 | } 342 | return vec_out; 343 | } 344 | 345 | 346 | /*------------------------------------------------------------- 347 | * repl 348 | * 349 | * Replaces x with x_repl where filt is 1. x and filt are of the length n 350 | * while x_repl can be n or greater. x is replaced inplace. It returns 351 | * the number of terms replaced. 352 | *-------------------------------------------------------------*/ 353 | int repl(double *x, int *filt, int n, double *x_repl){ 354 | int i, j; 355 | j = 0; 356 | for (i=0; i 0){ 358 | x[j] = x_repl[i]; 359 | j++; 360 | } 361 | } 362 | return j; 363 | } -------------------------------------------------------------------------------- /rainflow/rainflow.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/rainflow/rainflow.dll -------------------------------------------------------------------------------- /rainflow/rainflow.h: -------------------------------------------------------------------------------- 1 | /******************************************************************** 2 | * fatigue.h Fatigue related functions. 3 | * 4 | * Name: fatigue.h 5 | * Purpose: Fatigue analysis 6 | * Copyright: (c) 2007-2008 Ladisk 7 | * Author: Primoz Cermelj 8 | * License: BSD license 9 | *********************************************************************/ 10 | #include 11 | #include 12 | #include 13 | 14 | int rf3(double *array_ext, int nr, double *array_out); 15 | int rf5(double *array_ext, int nr, double *array_t, double *array_out); 16 | int sig2ext(double *sig, double *time_sig, long n, int clsn, 17 | double *ext, double *exttime); 18 | double arr_min(double *sig, int n, int *pos); 19 | double arr_max(double *sig, int n, int *pos); 20 | #define NNEW(a,b) (a *)calloc((b),sizeof(a)) 21 | #define RENEW(a,b,c) a=(b *) realloc((b *)(a),(c)*sizeof(b)) 22 | double *diff(double *vec, int n); 23 | int repl(double *x, int *filt, int n, double *x_repl); 24 | -------------------------------------------------------------------------------- /rainflow/rainflow.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/rainflow/rainflow.o -------------------------------------------------------------------------------- /rainflow/rainflow.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from numpy.ctypeslib import ndpointer 3 | import numpy as np 4 | import os 5 | 6 | _file = 'rainflow' 7 | _path = os.path.join(*(os.path.split(__file__)[:-1] + (_file,))) 8 | _rf = ctypes.cdll.LoadLibrary(_path) 9 | 10 | def _sig2ext(sig, time_sig = None, clsn=-1): 11 | """Converts signal ``sig`` to turning points used by ``rainflow``. The 12 | syntax is: :: 13 | 14 | (ntp, ext, exttime) = sig2ext(sig, clsn, [time_vals=None]) 15 | 16 | where ``ntp`` is the number of turning points, ``ext`` is a turning-point 17 | signal and ``exttime`` are the corresponding time values. 18 | 19 | :param sig: signal as numpy array 20 | :param time_sig: time data of the signal, if `None` time is assumed as 0, 1, 2,... 21 | :param clsn: number of classes (pass -1 if not to be used, i.e., no divisions into classes) 22 | :return (ntp, ext, exttime): 23 | """ 24 | sig = np.asarray(sig, dtype=float) 25 | sig = np.ascontiguousarray(sig) 26 | ext = np.ascontiguousarray(np.zeros_like(sig)) 27 | exttime = np.ascontiguousarray(np.zeros_like(sig)) 28 | try: 29 | __sig2ext = _rf.sig2ext 30 | __sig2ext.restype = ctypes.c_int 31 | if time_sig is None: 32 | __sig2ext.argtypes = [ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 33 | ctypes.c_voidp,#ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 34 | ctypes.c_int, 35 | ctypes.c_long, 36 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 37 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS")] 38 | ntp = __sig2ext(sig, None, len(sig), clsn, 39 | ext, exttime) 40 | else: 41 | __sig2ext.argtypes = [ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 42 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 43 | ctypes.c_int, 44 | ctypes.c_long, 45 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 46 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS")] 47 | time_sig = np.asarray(time_sig, dtype=float) 48 | time_sig = np.ascontiguousarray(time_sig) 49 | ntp = __sig2ext(sig, time_sig, len(sig), clsn, 50 | 51 | ext, exttime) 52 | except: 53 | raise Exception('sig2ext raised exception.') 54 | 55 | return (ntp, ext, exttime) 56 | 57 | def _rainflow(ext, exttime=None): 58 | """Rainflow counting array_ext and array_t are results from sig2ext 59 | 60 | :param ext: is a turning-point signal and . 61 | :param exttime: are the corresponding time values 62 | :return: (cnr, rf) 63 | cnr: the number nonzero of rows in the rf matrix 64 | rf[:, 0] Cycles amplitude, 65 | rf[:, 1] Cycles mean value, 66 | rf[:, 2] Number of cycles (0.5 or 1.0), 67 | rf[:, 3] Begining time (when input includes exttime), 68 | rf[:, 4] Cycle period (when input includes exttime), 69 | """ 70 | ext = np.ascontiguousarray(ext) 71 | try: 72 | _rf5 = _rf.rf5 73 | _rf5.restype = ctypes.c_int 74 | _rf3 = _rf.rf3 75 | _rf3.restype = ctypes.c_int 76 | 77 | if exttime is None: 78 | _rf3.argtypes = [ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 79 | ctypes.c_int, 80 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS")] 81 | array_out = np.ascontiguousarray(np.zeros((len(ext), 3))) 82 | cnr = _rf3(ext, len(ext), array_out) 83 | else: 84 | exttime = np.ascontiguousarray(exttime) 85 | _rf5.argtypes = [ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 86 | ctypes.c_int, 87 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), 88 | ndpointer(ctypes.c_double, flags="C_CONTIGUOUS")] 89 | array_out = np.ascontiguousarray(np.zeros((len(ext), 5))) 90 | cnr = _rf5(ext, len(ext), exttime, array_out) 91 | except: 92 | raise Exception('_rainflow raised exception.') 93 | return cnr, array_out 94 | 95 | def rainflow(sig, time_sig = None, clsn=-1): 96 | """Rainflow counting method 97 | 98 | (amplitude, mean, cycles) = rainflow(sig, time_sig, clsn) #if time_sig=None 99 | (amplitude, mean, cycles, start_time, period) = rainflow(sig, time_sig, clsn) 100 | 101 | :param sig: signal as numpy array 102 | :param time_sig: time data of the signal, if `None` time is assumed as 0, 1, 2,... 103 | :param clsn: number of classes (pass -1 if not to be used, i.e., no divisions into classes) 104 | :return: 105 | amplitude: Cycles amplitude, 106 | mean: Cycles mean value, 107 | cycles: Number of cycles (0.5 or 1.0), 108 | start_time: Begining time (when input includes exttime), 109 | period: Cycle period (when input includes exttime), 110 | """ 111 | (ntp, ext, exttime) = _sig2ext(sig=sig, time_sig=time_sig, clsn=clsn) 112 | if time_sig is None: 113 | (_, rf) = _rainflow(ext[:ntp]) 114 | else: 115 | (_, rf) = _rainflow(ext[:ntp], exttime[:ntp]) 116 | 117 | up_to = np.where(rf[:,0]==0,)[0][0] 118 | 119 | amplitude = rf[:up_to,0] 120 | mean = rf[:up_to,1] 121 | cycles = rf[:up_to,2] 122 | if time_sig is None: 123 | return amplitude, mean, cycles 124 | else: 125 | start_time = rf[:up_to,3] 126 | period = rf[:up_to,4] 127 | return amplitude, mean, cycles, start_time, period 128 | 129 | if __name__ == '__main__': 130 | sig = np.random.rand(10) 131 | import time 132 | 133 | tic = time.perf_counter() 134 | amp, mean, cyc = rainflow(sig) 135 | toc = time.perf_counter() 136 | print(1000*(toc-tic)) 137 | print(amp) -------------------------------------------------------------------------------- /sklearn/plot_segmentation_toy.py: -------------------------------------------------------------------------------- 1 | """ 2 | =========================================== 3 | Spectral clustering for image segmentation 4 | =========================================== 5 | 6 | In this example, an image with connected circles is generated and 7 | spectral clustering is used to separate the circles. 8 | 9 | In these settings, the :ref:`spectral_clustering` approach solves the problem 10 | know as 'normalized graph cuts': the image is seen as a graph of 11 | connected voxels, and the spectral clustering algorithm amounts to 12 | choosing graph cuts defining regions while minimizing the ratio of the 13 | gradient along the cut, and the volume of the region. 14 | 15 | As the algorithm tries to balance the volume (ie balance the region 16 | sizes), if we take circles with different sizes, the segmentation fails. 17 | 18 | In addition, as there is no useful information in the intensity of the image, 19 | or its gradient, we choose to perform the spectral clustering on a graph 20 | that is only weakly informed by the gradient. This is close to performing 21 | a Voronoi partition of the graph. 22 | 23 | In addition, we use the mask of the objects to restrict the graph to the 24 | outline of the objects. In this example, we are interested in 25 | separating the objects one from the other, and not from the background. 26 | """ 27 | print(__doc__) 28 | 29 | # Authors: Emmanuelle Gouillart 30 | # Gael Varoquaux 31 | # License: BSD 3 clause 32 | 33 | import numpy as np 34 | import matplotlib.pyplot as plt 35 | 36 | from sklearn.feature_extraction import image 37 | from sklearn.cluster import spectral_clustering 38 | 39 | ############################################################################### 40 | l = 100 41 | x, y = np.indices((l, l)) 42 | 43 | center1 = (28, 24) 44 | center2 = (40, 50) 45 | center3 = (67, 58) 46 | center4 = (24, 70) 47 | 48 | radius1, radius2, radius3, radius4 = 6, 4, 5, 4 49 | 50 | circle1 = (x - center1[0]) ** 2 + (y - center1[1]) ** 2 < radius1 ** 2 51 | circle2 = (x - center2[0]) ** 2 + (y - center2[1]) ** 2 < radius2 ** 2 52 | circle3 = (x - center3[0]) ** 2 + (y - center3[1]) ** 2 < radius3 ** 2 53 | circle4 = (x - center4[0]) ** 2 + (y - center4[1]) ** 2 < radius4 ** 2 54 | 55 | ############################################################################### 56 | # 4 circles 57 | img = circle1 + circle2 + circle3 + circle4 58 | mask = img.astype(bool) 59 | img = img.astype(float) 60 | 61 | img += 1 + 0.2 * np.random.randn(*img.shape) 62 | 63 | # Convert the image into a graph with the value of the gradient on the 64 | # edges. 65 | graph = image.img_to_graph(img, mask=mask) 66 | 67 | # Take a decreasing function of the gradient: we take it weakly 68 | # dependent from the gradient the segmentation is close to a voronoi 69 | graph.data = np.exp(-graph.data / graph.data.std()) 70 | 71 | # Force the solver to be arpack, since amg is numerically 72 | # unstable on this example 73 | labels = spectral_clustering(graph, n_clusters=4, eigen_solver='arpack') 74 | label_im = -np.ones(mask.shape) 75 | label_im[mask] = labels 76 | 77 | plt.matshow(img) 78 | plt.matshow(label_im) 79 | 80 | ############################################################################### 81 | # 2 circles 82 | img = circle1 + circle2 83 | mask = img.astype(bool) 84 | img = img.astype(float) 85 | 86 | img += 1 + 0.2 * np.random.randn(*img.shape) 87 | 88 | graph = image.img_to_graph(img, mask=mask) 89 | graph.data = np.exp(-graph.data / graph.data.std()) 90 | 91 | labels = spectral_clustering(graph, n_clusters=2, eigen_solver='arpack') 92 | label_im = -np.ones(mask.shape) 93 | label_im[mask] = labels 94 | 95 | plt.matshow(img) 96 | plt.matshow(label_im) 97 | 98 | plt.show() 99 | -------------------------------------------------------------------------------- /wrapping C code/Compiling C with mingw-w64 and wrapping it with ctypes in Python 3.4.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "#Compiling C code to dll" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Look at the `sample.c` and `sample.h` files for C code and header, respectively." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "Install `mingw-w64` from:\n", 22 | "http://sourceforge.net/projects/mingw-w64/\n", 23 | "\n", 24 | "put `mingw64\\bin\\` folder into path and compile it with:" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 1, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "data": { 34 | "text/plain": [ 35 | "[]" 36 | ] 37 | }, 38 | "execution_count": 1, 39 | "output_type": "execute_result", 40 | "metadata": {} 41 | } 42 | ], 43 | "source": [ 44 | "!!gcc -c sample.c" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 2, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "[]" 56 | ] 57 | }, 58 | "execution_count": 2, 59 | "output_type": "execute_result", 60 | "metadata": {} 61 | } 62 | ], 63 | "source": [ 64 | "!!gcc -shared -o sample.dll sample.o -Wl,--out-implib,libsample.a" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "#Wrapping into py 3.4" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 3, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "import ctypes" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 4, 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "data": { 90 | "text/plain": [ 91 | "'sample.o'" 92 | ] 93 | }, 94 | "execution_count": 4, 95 | "output_type": "execute_result", 96 | "metadata": {} 97 | } 98 | ], 99 | "source": [ 100 | "ctypes.util.find_library('sample.o')" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "Load the dll:" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 5, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "_mod = ctypes.cdll.LoadLibrary('sample')" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "Define a wrapper to the dll function" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 6, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "in_mandel = _mod.in_mandel\n", 133 | "in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)\n", 134 | "in_mandel.restype = ctypes.c_int" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "Use it:" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 7, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "data": { 151 | "text/plain": [ 152 | "5" 153 | ] 154 | }, 155 | "execution_count": 7, 156 | "output_type": "execute_result", 157 | "metadata": {} 158 | } 159 | ], 160 | "source": [ 161 | "in_mandel(1., 4., 1)" 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "For more functions look at the `sample.py`." 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "A good reference used for this notebook is:\n", 176 | "\n", 177 | "http://chimera.labs.oreilly.com/books/1230000000393/ch15.html\n", 178 | "\n", 179 | "from the python cookbook: \n", 180 | "\n", 181 | "http://chimera.labs.oreilly.com/books/1230000000393/index.html\n", 182 | "\n", 183 | "For a MS Visual C++ compiler setup see:\n", 184 | "\n", 185 | "http://blog.ionelmc.ro/2014/12/21/compiling-python-extensions-on-windows/" 186 | ] 187 | } 188 | ], 189 | "metadata": { 190 | "kernelspec": { 191 | "display_name": "Python 3", 192 | "language": "python", 193 | "name": "python3" 194 | }, 195 | "language_info": { 196 | "codemirror_mode": { 197 | "name": "ipython", 198 | "version": 3.0 199 | }, 200 | "file_extension": ".py", 201 | "mimetype": "text/x-python", 202 | "name": "python", 203 | "nbconvert_exporter": "python", 204 | "pygments_lexer": "ipython3", 205 | "version": "3.4.3" 206 | } 207 | }, 208 | "nbformat": 4, 209 | "nbformat_minor": 0 210 | } -------------------------------------------------------------------------------- /wrapping C code/README.md: -------------------------------------------------------------------------------- 1 | This is an example on how to compile and use C code inside py. See also the Compiling C with... ipython notebook. -------------------------------------------------------------------------------- /wrapping C code/compile.bat: -------------------------------------------------------------------------------- 1 | gcc -c sample.c 2 | gcc -shared -o sample.dll sample.o -Wl,--out-implib,libsample.a -------------------------------------------------------------------------------- /wrapping C code/compile_exe.bat: -------------------------------------------------------------------------------- 1 | gcc -c sample_exe.c 2 | gcc -o sample_exe.exe sample_exe.o -L. -lsample -------------------------------------------------------------------------------- /wrapping C code/libsample.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/wrapping C code/libsample.a -------------------------------------------------------------------------------- /wrapping C code/sample.c: -------------------------------------------------------------------------------- 1 | /* sample.c */ 2 | #include 3 | 4 | /* Compute the greatest common divisor */ 5 | int gcd(int x, int y) { 6 | int g = y; 7 | while (x > 0) { 8 | g = x; 9 | x = y % x; 10 | y = g; 11 | } 12 | return g; 13 | } 14 | 15 | int Double(int x) 16 | { 17 | return 2 * x; 18 | } 19 | 20 | /* Test if (x0,y0) is in the Mandelbrot set or not */ 21 | int in_mandel(double x0, double y0, int n) { 22 | double x=0,y=0,xtemp; 23 | return 5; 24 | while (n > 0) { 25 | xtemp = x*x - y*y + x0; 26 | y = 2*x*y + y0; 27 | x = xtemp; 28 | n -= 1; 29 | if (x*x + y*y > 4) return 0; 30 | } 31 | return 1; 32 | } 33 | 34 | /* Divide two numbers */ 35 | int divide(int a, int b, int *remainder) { 36 | int quot = a / b; 37 | *remainder = a % b; 38 | return quot; 39 | } 40 | 41 | /* Average values in an array */ 42 | double avg(double *a, int n) { 43 | int i; 44 | double total = 0.0; 45 | for (i = 0; i < n; i++) { 46 | total += a[i]; 47 | } 48 | return total / n; 49 | } 50 | 51 | /* A C data structure */ 52 | typedef struct Point { 53 | double x,y; 54 | } Point; 55 | 56 | /* Function involving a C data structure */ 57 | double distance(Point *p1, Point *p2) { 58 | return hypot(p1->x - p2->x, p1->y - p2->y); 59 | } -------------------------------------------------------------------------------- /wrapping C code/sample.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/wrapping C code/sample.dll -------------------------------------------------------------------------------- /wrapping C code/sample.h: -------------------------------------------------------------------------------- 1 | /* sample.h */ 2 | 3 | #include 4 | 5 | extern int gcd(int, int); 6 | extern int in_mandel(double x0, double y0, int n); 7 | extern int divide(int a, int b, int *remainder); 8 | extern double avg(double *a, int n); 9 | 10 | typedef struct Point { 11 | double x,y; 12 | } Point; 13 | 14 | extern double distance(Point *p1, Point *p2); 15 | -------------------------------------------------------------------------------- /wrapping C code/sample.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/wrapping C code/sample.o -------------------------------------------------------------------------------- /wrapping C code/sample.py: -------------------------------------------------------------------------------- 1 | # sample.py 2 | import ctypes 3 | import os 4 | 5 | # Try to locate the .so file in the same directory as this file 6 | _file = 'sample' 7 | _path = os.path.join(*(os.path.split(__file__)[:-1] + (_file,))) 8 | _mod = ctypes.cdll.LoadLibrary(_path) 9 | 10 | # int gcd(int, int) 11 | gcd = _mod.gcd 12 | gcd.argtypes = (ctypes.c_int, ctypes.c_int) 13 | gcd.restype = ctypes.c_int 14 | 15 | # int in_mandel(double, double, int) 16 | in_mandel = _mod.in_mandel 17 | in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int) 18 | in_mandel.restype = ctypes.c_int 19 | 20 | # int divide(int, int, int *) 21 | _divide = _mod.divide 22 | _divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) 23 | _divide.restype = ctypes.c_int 24 | 25 | def divide(x, y): 26 | rem = ctypes.c_int() 27 | quot = _divide(x, y, rem) 28 | return quot,rem.value 29 | 30 | # void avg(double *, int n) 31 | # Define a special type for the 'double *' argument 32 | class DoubleArrayType: 33 | def from_param(self, param): 34 | typename = type(param).__name__ 35 | if hasattr(self, 'from_' + typename): 36 | return getattr(self, 'from_' + typename)(param) 37 | elif isinstance(param, ctypes.Array): 38 | return param 39 | else: 40 | raise TypeError("Can't convert %s" % typename) 41 | 42 | # Cast from array.array objects 43 | def from_array(self, param): 44 | if param.typecode != 'd': 45 | raise TypeError('must be an array of doubles') 46 | ptr, _ = param.buffer_info() 47 | return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double)) 48 | 49 | # Cast from lists/tuples 50 | def from_list(self, param): 51 | val = ((ctypes.c_double)*len(param))(*param) 52 | return val 53 | 54 | from_tuple = from_list 55 | 56 | # Cast from a numpy array 57 | def from_ndarray(self, param): 58 | return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) 59 | 60 | DoubleArray = DoubleArrayType() 61 | _avg = _mod.avg 62 | _avg.argtypes = (DoubleArray, ctypes.c_int) 63 | _avg.restype = ctypes.c_double 64 | 65 | def avg(values): 66 | return _avg(values, len(values)) 67 | 68 | # struct Point { } 69 | class Point(ctypes.Structure): 70 | _fields_ = [('x', ctypes.c_double), 71 | ('y', ctypes.c_double)] 72 | 73 | # double distance(Point *, Point *) 74 | distance = _mod.distance 75 | distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point)) 76 | distance.restype = ctypes.c_double 77 | -------------------------------------------------------------------------------- /wrapping C code/sample_exe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sample.h" 3 | 4 | int main(void) 5 | { 6 | printf("%d\n", in_mandel(1., 2., 3)); 7 | return 0; 8 | } -------------------------------------------------------------------------------- /wrapping C code/sample_exe.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/wrapping C code/sample_exe.exe -------------------------------------------------------------------------------- /wrapping C code/sample_exe.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankoslavic/py-tools/dfc79507e90e1beaa8297fb48b197990b24d0929/wrapping C code/sample_exe.o --------------------------------------------------------------------------------