Continue writting

This commit is contained in:
Santiago Soler
2018-02-01 11:18:39 -03:00
parent 1dfca26fd9
commit 0511fa673d
2 changed files with 70 additions and 162 deletions

View File

@@ -21,12 +21,16 @@ from PyQt5.QtWidgets import QMenu, QWidget, QVBoxLayout, QMessageBox
from PyQt5.QtWidgets import QSlider, QHBoxLayout, QLabel, QDialog from PyQt5.QtWidgets import QSlider, QHBoxLayout, QLabel, QDialog
from PyQt5.QtWidgets import QDialogButtonBox from PyQt5.QtWidgets import QDialogButtonBox
from fatiando import utils 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(
linewidth=2, linestyle='-', color='k', marker='o',
markerfacecolor='k', markersize=5, animated=False, alpha=0.6
)
class Moulder(FigureCanvasQTAgg): class Moulder(FigureCanvasQTAgg):
""" """
Interactive 2D forward modeling using polygons. Interactive 2D forward modeling using polygons.
@@ -108,104 +112,63 @@ class Moulder(FigureCanvasQTAgg):
instructions = ' | '.join([ instructions = ' | '.join([
'n: New polygon', 'd: delete', 'click: select/move', 'esc: cancel']) 'n: New polygon', 'd: delete', 'click: select/move', 'esc: cancel'])
def __init__(self, parent, width=5, height=4, dpi=100):
def __init__(self, parent, area, x, z, data=None,
density_range=[-2000, 2000], width=5, height=4, dpi=100,
**kwargs):
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.cmap = pyplot.cm.RdBu_r
self.x, self.z = numpy.asarray(x), numpy.asarray(z)
self.density_range = density_range self._x = None
self.data = data self._z = None
# Used to set the ylims for the data axes. self._min_depth = 0
if data is None: self._max_depth = 10000
self.dmin, self.dmax = 0, 0
else: self.predicted = None
self.dmin, self.dmax = data.min(), data.max() self.error = None
self.predicted = kwargs.get('predicted', numpy.zeros_like(x))
self.error = kwargs.get('error', 0) self.predicted_line = []
self.cmap = kwargs.get('cmap', pyplot.cm.RdBu_r) self.data = None
self.line_args = dict(
linewidth=2, linestyle='-', color='k', marker='o',
markerfacecolor='k', markersize=5, animated=False, alpha=0.6)
self.polygons = [] self.polygons = []
self.lines = [] self.lines = []
self.densities = kwargs.get('densities', []) self.densities = []
vertices = kwargs.get('vertices', [])
for xy, dens in zip(vertices, self.densities):
poly, line = self._make_polygon(xy, dens)
self.polygons.append(poly)
self.lines.append(line)
def save_predicted(self, fname): @property
""" def plotted(self):
Save the predicted data to a text file. return self._plotted
Data will be saved in 3 columns separated by spaces: x z data @property
def x(self):
return self._x
Parameters: @x.setter
def x(self, new_value):
self._x = numpy.asarray(new_value)
* fname : string or file-like object @property
The name of the output file or an open file-like object. def z(self):
return self._z
""" @z.setter
numpy.savetxt(fname, numpy.transpose([self.x, self.z, self.predicted])) def z(self, new_value):
self._z = numpy.asarray(new_value)
def save(self, fname): @property
""" def min_depth(self):
Save the application state into a pickle file. return self._min_depth
Use this to persist the application. You can later reload the entire @min_depth.setter
object, with the drawn model and data, using the def min_depth(self, new_value):
:meth:`~fatiando.gravmag.interactive.Moulder.load` method. self._min_depth = new_value
Parameters: @property
def max_depth(self):
return self._max_depth
* fname : string @max_depth.setter
The name of the file to save the application. The extension doesn't def max_depth(self, new_value):
matter (use ``.pkl`` if in doubt). self._max_depth = new_value
"""
with open(fname, 'w') as f:
vertices = [numpy.asarray(p.xy) for p in self.polygons]
state = dict(area=self.area, x=self.x,
z=self.z, data=self.data,
density_range=self.density_range,
cmap=self.cmap,
predicted=self.predicted,
vertices=vertices,
densities=self.densities,
error=self.error)
pickle.dump(state, f)
@classmethod
def load(cls, fname):
"""
Restore an application from a pickle file.
The pickle file should have been generated by the
:meth:`~fatiando.gravmag.interactive.Moulder.save` method.
Parameters:
* fname : string
The name of the file.
Returns:
* app : Moulder object
The restored application. You can continue using it as if nothing
had happened.
"""
with open(fname) as f:
state = pickle.load(f)
app = cls(**state)
return app
@property @property
def model(self): def model(self):
@@ -217,27 +180,7 @@ class Moulder(FigureCanvasQTAgg):
return m return m
def run(self): def run(self):
"""
Start the application for drawing.
Will pop-up a window with a place for drawing the model (below) and a
place with the predicted (and, optionally, observed) data (top).
Follow the instruction on the figure title.
When done, close the window to resume program execution.
"""
self._figure_setup() self._figure_setup()
# Sliders to control the density and the error in the data
self.density_slider = widgets.Slider(
self.fig.add_axes([0.10, 0.01, 0.30, 0.02]), 'Density',
self.density_range[0], self.density_range[1], valinit=0.,
valfmt='%6.0f kg/m3')
self.error_slider = widgets.Slider(
self.fig.add_axes([0.60, 0.01, 0.30, 0.02]), 'Error',
0, 5, valinit=self.error, valfmt='%1.2f mGal')
# Put instructions on figure title
self.dataax.set_title(self.instructions)
# Markers for mouse click events # Markers for mouse click events
self._ivert = None self._ivert = None
self._ipoly = None self._ipoly = None
@@ -263,34 +206,12 @@ 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.canvas.mpl_connect('key_press_event',
self._key_press_callback) # 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)
self.density_slider.on_changed(self._set_density_callback)
self.error_slider.on_changed(self._set_error_callback)
def plot(self, figsize=(10, 8), dpi=70):
"""
Make a plot of the data and model for embedding in IPython notebooks
Doesn't require ``%matplotlib inline`` to embed the plot (as that would
not allow the app to run).
Parameters:
* figsize : list = (width, height)
The figure size in inches.
* dpi : float
The number of dots-per-inch for the figure resolution.
"""
self._update_data_plot()
pyplot.close(self.fig)
data = print_figure(self.fig, dpi=dpi)
return Image(data=data)
def _figure_setup(self, **kwargs): def _figure_setup(self, **kwargs):
""" """
@@ -310,32 +231,12 @@ class Moulder(FigureCanvasQTAgg):
kwargs['sharex'] = True kwargs['sharex'] = True
axes = self.fig.subplots(2, 1, **kwargs) axes = self.fig.subplots(2, 1, **kwargs)
ax1, ax2 = axes ax1, ax2 = axes
self.predicted_line, = ax1.plot(self.x, self.predicted, '-r')
if self.data is not None:
self.data_line, = ax1.plot(self.x, self.data, '.k')
ax1.set_ylabel('Gravity anomaly (mGal)') ax1.set_ylabel('Gravity anomaly (mGal)')
ax2.set_xlabel('x (m)', labelpad=-10) ax2.set_xlabel('x (m)', labelpad=-10)
ax1.set_xlim(self.area[:2]) ax1.set_xlim(self.x.min(), self.x.max())
ax1.set_ylim((-200, 200)) ax1.set_ylim((-200, 200))
ax1.grid(True) ax1.grid(True)
tmp = ax2.pcolor(numpy.array([self.density_range]), cmap=self.cmap) ax2.set_ylim(self.min_depth, self.max_depth)
tmp.set_visible(False)
pyplot.colorbar(tmp, orientation='horizontal',
pad=0.08, aspect=80).set_label(r'Density (kg/cm3)')
# Remake the polygons and lines to make sure they belong to the right
# axis coordinates
vertices = [p.xy for p in self.polygons]
newpolygons, newlines = [], []
for xy, dens in zip(vertices, self.densities):
poly, line = self._make_polygon(xy, dens)
newpolygons.append(poly)
newlines.append(line)
ax2.add_patch(poly)
ax2.add_line(line)
self.polygons = newpolygons
self.lines = newlines
ax2.set_xlim(self.area[:2])
ax2.set_ylim(self.area[2:])
ax2.grid(True) ax2.grid(True)
ax2.invert_yaxis() ax2.invert_yaxis()
ax2.set_ylabel('z (m)') ax2.set_ylabel('z (m)')
@@ -590,11 +491,11 @@ class Moulder(FigureCanvasQTAgg):
self._update_data() self._update_data()
self._update_data_plot() self._update_data_plot()
def _key_press_callback(self, event): 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.
""" """
if event.key == 'd': if event_key == 'd':
if self._drawing and self._xy: if self._drawing and self._xy:
self._xy.pop() self._xy.pop()
if self._xy: if self._xy:
@@ -629,7 +530,7 @@ class Moulder(FigureCanvasQTAgg):
self.canvas.draw() self.canvas.draw()
self._update_data() self._update_data()
self._update_data_plot() self._update_data_plot()
elif event.key == 'n': elif event_key == 'n':
self._ivert = None self._ivert = None
self._ipoly = None self._ipoly = None
for line, poly in zip(self.lines, self.polygons): for line, poly in zip(self.lines, self.polygons):
@@ -647,7 +548,7 @@ class Moulder(FigureCanvasQTAgg):
'left click: set vertice', 'right click: finish', 'left click: set vertice', 'right click: finish',
'esc: cancel'])) 'esc: cancel']))
self.canvas.draw() self.canvas.draw()
elif event.key == 'escape': elif event_key == 'escape':
if self._add_vertex: if self._add_vertex:
self._add_vertex = False self._add_vertex = False
else: else:
@@ -661,11 +562,11 @@ class Moulder(FigureCanvasQTAgg):
line.set_animated(False) line.set_animated(False)
line.set_color([0, 0, 0, 0]) line.set_color([0, 0, 0, 0])
self.canvas.draw() self.canvas.draw()
elif event.key == 'r': elif event_key == 'r':
self.modelax.set_xlim(self.area[:2]) self.modelax.set_xlim(self.area[:2])
self.modelax.set_ylim(self.area[2:]) self.modelax.set_ylim(self.area[2:])
self._update_data_plot() self._update_data_plot()
elif event.key == 'a': elif event_key == 'a':
self._add_vertex = not self._add_vertex self._add_vertex = not self._add_vertex
def _mouse_move_callback(self, event): def _mouse_move_callback(self, event):

View File

@@ -29,10 +29,19 @@ 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 = 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):
print(event)
keys_dict = {Qt.Key_N: "n", Qt.Key_R: "r",
Qt.Key_A: "a", Qt.Key_D: "d",
Qt.Key_Escape: "escape"}
if self.canvas.plotted and event.key in keys_dict.keys():
self.canvas.key_press_callback(keys_dict[event.key])
def closeEvent(self, event): def closeEvent(self, event):
event.ignore() event.ignore()
@@ -91,10 +100,8 @@ class MoulderApp(QMainWindow):
new_model_dialog = NewModelDialog(parent=self) new_model_dialog = NewModelDialog(parent=self)
new_model_dialog.exec_() new_model_dialog.exec_()
if new_model_dialog.is_completed(): if new_model_dialog.is_completed():
x = new_model_dialog.x self.canvas.x = new_model_dialog.x
z = new_model_dialog.z self.canvas.z = new_model_dialog.z
area = (x.min(), x.max(), z.min(), 10000)
self.canvas = Moulder(self, area, x, z)
self.canvas.run() self.canvas.run()
self.setCentralWidget(self.canvas) self.setCentralWidget(self.canvas)