Continue writting

This commit is contained in:
Santiago Soler
2018-02-01 12:39:59 -03:00
parent 0511fa673d
commit 03340d4010
2 changed files with 81 additions and 172 deletions

View File

@@ -25,118 +25,44 @@ from fatiando import utils
from fatiando.gravmag import talwani from fatiando.gravmag import talwani
from fatiando.mesher import Polygon from fatiando.mesher import Polygon
LINE_ARGS = dict( LINE_ARGS = dict(
linewidth=2, linestyle='-', color='k', marker='o', linewidth=2, linestyle='-', color='k', marker='o',
markerfacecolor='k', markersize=5, animated=False, alpha=0.6 markerfacecolor='k', markersize=5, animated=False, alpha=0.6)
)
class Moulder(FigureCanvasQTAgg): class Moulder(FigureCanvasQTAgg):
"""
Interactive 2D forward modeling using polygons.
A matplotlib GUI application. Allows drawing and manipulating polygons and
computes their predicted data automatically. Also permits contaminating the
data with gaussian pseudo-random error for producing synthetic data sets.
Uses :mod:`fatiando.gravmag.talwani` for computations.
*Moulder* objects can be persisted to Python pickle files using the
:meth:`~fatiando.gravmag.interactive.Moulder.save` method and later
restored using :meth:`~fatiando.gravmag.interactive.Moulder.load`.
.. warning::
Cannot be used with ``%matplotlib inline`` on IPython notebooks because
the app uses the matplotlib plot window. You can still embed the
generated model and data figure on notebooks using the
:meth:`~fatiando.gravmag.interactive.Moulder.plot` method.
Parameters:
* area : list = (x1, x2, z1, z2)
The limits of the model drawing area, in meters.
* x, z : 1d-arrays
The x- and z-coordinates of the computation points (places where
predicted data will be computed). In meters.
* data : None or 1d-array
Observed data measured at *x* and *z*. Will plot this with black dots
along the predicted data.
* density_range : list = [min, max]
The minimum and maximum values allowed for the density. Determines the
limits of the density slider of the application. In kg.m^-3. Defaults
to [-2000, 2000].
* kwargs : dict
Other keyword arguments used to restore the state of the application.
Used by the :meth:`~fatiando.gravmag.interactive.Moulder.load` method.
Not intended for general use.
Examples:
Make the Moulder object and start the app::
import numpy as np
area = (0, 10e3, 0, 5e3)
# Calculate on 100 points
x = np.linspace(area[0], area[1], 100)
z = np.zeros_like(x)
app = Moulder(area, x, z)
app.run()
# This will pop-up a window with the application (like the screenshot
# below). Start drawing (follow the instruction in the figure title).
# When satisfied, close the window to resume execution.
.. image:: ../_static/images/Moulder-screenshot.png
:alt: Screenshot of the Moulder GUI
After closing the plot window, you can access the model and data from the
*Moulder* object::
app.model # The drawn model as fatiando.mesher.Polygon
app.predicted # 1d-array with the data predicted by the model
# You can save the predicted data to use later
app.save_predicted('data.txt')
# You can also save the application and resume it later
app.save('application.pkl')
# Close this session/IPython notebook/etc.
# To resume drawing later:
app = Moulder.load('application.pkl')
app.run()
"""
# The tolerance range for mouse clicks on vertices. In pixels. # The tolerance range for mouse clicks on vertices. In pixels.
epsilon = 5 epsilon = 5
# App instructions printed in the figure suptitle # App instructions printed in the figure suptitle
instructions = ' | '.join([ instructions = ' | '.join([
'n: New polygon', 'd: delete', 'click: select/move', 'esc: cancel']) 'n: New polygon', 'd: delete', 'click: select/move', 'a: add vertex',
'r: reset view', 'esc: cancel'])
def __init__(self, parent, width=5, height=4, dpi=100): def __init__(self, parent, area, x, z, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi) self.fig = Figure(figsize=(width, height), dpi=dpi)
super().__init__(self.fig) super().__init__(self.fig)
self.setParent(parent) self.setParent(parent)
self._plotted = False
self._area = area
self._x, self._z = x, z
self._predicted = numpy.zeros_like(x)
self._data = None
self.cmap = pyplot.cm.RdBu_r self.cmap = pyplot.cm.RdBu_r
self.canvas = self.fig.canvas
self._x = None
self._z = None
self._min_depth = 0
self._max_depth = 10000
self.predicted = None
self.error = None
self.predicted_line = []
self.data = None
self.polygons = [] self.polygons = []
self.lines = [] self.lines = []
self.densities = [] self.densities = []
@property # Set arbitrary density and error values (only for first implementations)
def plotted(self): # It will be determined by sliders/entries in MoulderApp
return self._plotted self._density = 100
self._error = 0
self._figure_setup()
self._init_markers()
self._connect()
@property @property
def x(self): def x(self):
@@ -155,20 +81,43 @@ class Moulder(FigureCanvasQTAgg):
self._z = numpy.asarray(new_value) self._z = numpy.asarray(new_value)
@property @property
def min_depth(self): def area(self):
return self._min_depth return self._area
@min_depth.setter @area.setter
def min_depth(self, new_value): def area(self, new_area):
self._min_depth = new_value self._area = new_area
@property @property
def max_depth(self): def data(self):
return self._max_depth return self._data
@max_depth.setter @data.setter
def max_depth(self, new_value): def data(self, new_data):
self._max_depth = new_value self._data = new_data
@property
def density(self):
return self._density
@density.setter
def density(self, new_value):
self._density = new_value
@property
def error(self):
return self._error
@error.setter
def error(self, new_value):
self._error = new_value
@property
def predicted(self):
self._predicted = talwani.gz(self.x, self.z, self.model)
if self.error > 0:
self._predicted = utils.contaminate(self.predicted, self.error)
return self._predicted
@property @property
def model(self): def model(self):
@@ -179,9 +128,20 @@ class Moulder(FigureCanvasQTAgg):
for p, d in zip(self.polygons, self.densities)] for p, d in zip(self.polygons, self.densities)]
return m return m
def run(self): def _figure_setup(self):
self._figure_setup() self.dataax, self.modelax = self.fig.subplots(2, 1, sharex=True)
# Markers for mouse click events self.dataax.set_ylabel("Gravity Anomaly [mGal]")
self.dataax.set_ylim((-200, 200))
self.dataax.grid(True)
self.modelax.set_xlabel("x [m]")
self.modelax.set_ylabel("z [m]")
self.modelax.set_xlim(self.area[:2])
self.modelax.set_ylim(self.area[2:])
self.modelax.grid(True)
self.modelax.invert_yaxis()
self.canvas.draw()
def _init_markers(self):
self._ivert = None self._ivert = None
self._ipoly = None self._ipoly = None
self._lastevent = None self._lastevent = None
@@ -189,15 +149,7 @@ class Moulder(FigureCanvasQTAgg):
self._add_vertex = False self._add_vertex = False
self._xy = [] self._xy = []
self._drawing_plot = None self._drawing_plot = None
# Used to blit the model plot and make
# rendering faster
self.background = None self.background = None
# Connect event callbacks
self._connect()
self._update_data()
self._update_data_plot()
self.canvas.draw()
#pyplot.show()
def _connect(self): def _connect(self):
""" """
@@ -206,47 +158,11 @@ class Moulder(FigureCanvasQTAgg):
# Make the proper callback connections # Make the proper callback connections
self.canvas.mpl_connect('button_press_event', self.canvas.mpl_connect('button_press_event',
self._button_press_callback) self._button_press_callback)
# self.canvas.mpl_connect('key_press_event',
# self._key_press_callback)
self.canvas.mpl_connect('button_release_event', self.canvas.mpl_connect('button_release_event',
self._button_release_callback) self._button_release_callback)
self.canvas.mpl_connect('motion_notify_event', self.canvas.mpl_connect('motion_notify_event',
self._mouse_move_callback) self._mouse_move_callback)
def _figure_setup(self, **kwargs):
"""
Setup the plot figure with labels, titles, ticks, etc.
Sets the *canvas*, *dataax*, *modelax*, *polygons* and *lines*
attributes.
Parameters:
* kwargs : dict
Keyword arguments passed to ``pyplot.subplots``.
"""
sharex = kwargs.get('sharex')
if not sharex:
kwargs['sharex'] = True
axes = self.fig.subplots(2, 1, **kwargs)
ax1, ax2 = axes
ax1.set_ylabel('Gravity anomaly (mGal)')
ax2.set_xlabel('x (m)', labelpad=-10)
ax1.set_xlim(self.x.min(), self.x.max())
ax1.set_ylim((-200, 200))
ax1.grid(True)
ax2.set_ylim(self.min_depth, self.max_depth)
ax2.grid(True)
ax2.invert_yaxis()
ax2.set_ylabel('z (m)')
#self.fig.subplots_adjust(top=0.95, left=0.1, right=0.95, bottom=0.06,
# hspace=0.1)
self.canvas = self.fig.canvas
self.dataax = axes[0]
self.modelax = axes[1]
self.fig.canvas.draw()
def _density2color(self, density): def _density2color(self, density):
""" """
Map density values to colors using the given *cmap* attribute. Map density values to colors using the given *cmap* attribute.
@@ -292,23 +208,16 @@ class Moulder(FigureCanvasQTAgg):
line = Line2D(x, y, **self.line_args) line = Line2D(x, y, **self.line_args)
return poly, line return poly, line
def _update_data(self):
"""
Recalculate the predicted data (optionally with random error)
"""
self.predicted = talwani.gz(self.x, self.z, self.model)
if self.error > 0:
self.predicted = utils.contaminate(self.predicted, self.error)
def _update_data_plot(self): def _update_data_plot(self):
""" """
Update the predicted data plot in the *dataax*. Update the predicted data plot in the *dataax*.
Adjusts the xlim of the axes to fit the data. Adjusts the xlim of the axes to fit the data.
""" """
self.predicted_line.set_ydata(self.predicted) predicted = self.predicted
vmin = 1.2*min(self.predicted.min(), self.dmin) self.predicted_line.set_ydata(predicted)
vmax = 1.2*max(self.predicted.max(), self.dmax) vmin = 1.2*min(predicted.min(), self.dmin)
vmax = 1.2*max(predicted.max(), self.dmax)
self.dataax.set_ylim(vmin, vmax) self.dataax.set_ylim(vmin, vmax)
self.dataax.grid(True) self.dataax.grid(True)
self.canvas.draw() self.canvas.draw()
@@ -318,7 +227,6 @@ class Moulder(FigureCanvasQTAgg):
Callback when error slider is edited Callback when error slider is edited
""" """
self.error = value self.error = value
self._update_data()
self._update_data_plot() self._update_data_plot()
def _set_density_callback(self, value): def _set_density_callback(self, value):
@@ -410,7 +318,7 @@ class Moulder(FigureCanvasQTAgg):
# and which vertice of which polygon # and which vertice of which polygon
self._ipoly, self._ivert = self._get_polygon_vertice_id(event) self._ipoly, self._ivert = self._get_polygon_vertice_id(event)
if self._ipoly is not None: if self._ipoly is not None:
self.density_slider.set_val(self.densities[self._ipoly]) # self.density_slider.set_val(self.densities[self._ipoly])
self.polygons[self._ipoly].set_animated(True) self.polygons[self._ipoly].set_animated(True)
self.lines[self._ipoly].set_animated(True) self.lines[self._ipoly].set_animated(True)
self.lines[self._ipoly].set_color([0, 1, 0, 0]) self.lines[self._ipoly].set_color([0, 1, 0, 0])
@@ -449,11 +357,10 @@ class Moulder(FigureCanvasQTAgg):
self.canvas.blit(self.modelax.bbox) self.canvas.blit(self.modelax.bbox)
elif event.button == 3: elif event.button == 3:
if len(self._xy) >= 3: if len(self._xy) >= 3:
density = self.density_slider.val poly, line = self._make_polygon(self._xy, self.density)
poly, line = self._make_polygon(self._xy, density)
self.polygons.append(poly) self.polygons.append(poly)
self.lines.append(line) self.lines.append(line)
self.densities.append(density) self.densities.append(self.density)
self.modelax.add_patch(poly) self.modelax.add_patch(poly)
self.modelax.add_line(line) self.modelax.add_line(line)
self._drawing_plot.remove() self._drawing_plot.remove()
@@ -491,7 +398,7 @@ class Moulder(FigureCanvasQTAgg):
self._update_data() self._update_data()
self._update_data_plot() self._update_data_plot()
def key_press_callback(self, event_key): def _key_press_callback(self, event_key):
""" """
What to do when a key is pressed on the keyboard. What to do when a key is pressed on the keyboard.
""" """

View File

@@ -3,7 +3,7 @@ from future.builtins import super
import os import os
import sys import sys
import numpy as np import numpy
import matplotlib import matplotlib
from matplotlib.figure import Figure from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
@@ -29,19 +29,21 @@ class MoulderApp(QMainWindow):
self.init_ui() self.init_ui()
self.set_callbacks() self.set_callbacks()
self.canvas = Moulder(self, width=5, height=4, dpi=100) self.canvas = Moulder(self, [0, 100, 0, 1000],
numpy.linspace(0, 100, 11),
numpy.zeros(11),
width=5, height=4, dpi=100)
#self.canvas = GravityModelCanvas(self, #self.canvas = GravityModelCanvas(self,
# width=5, height=4, dpi=100) # width=5, height=4, dpi=100)
# self.canvas.setFocus() # self.canvas.setFocus()
self.setCentralWidget(self.canvas) self.setCentralWidget(self.canvas)
def keyPressEvent(self, event): def keyPressEvent(self, event):
print(event)
keys_dict = {Qt.Key_N: "n", Qt.Key_R: "r", keys_dict = {Qt.Key_N: "n", Qt.Key_R: "r",
Qt.Key_A: "a", Qt.Key_D: "d", Qt.Key_A: "a", Qt.Key_D: "d",
Qt.Key_Escape: "escape"} Qt.Key_Escape: "escape"}
if self.canvas.plotted and event.key in keys_dict.keys(): if event.key in keys_dict.keys():
self.canvas.key_press_callback(keys_dict[event.key]) self.canvas._key_press_callback(keys_dict[event.key])
def closeEvent(self, event): def closeEvent(self, event):
event.ignore() event.ignore()