mirror of
https://github.com/fatiando/moulder.git
synced 2025-12-21 10:31:09 +08:00
Continue writting
This commit is contained in:
213
interactive.py
213
interactive.py
@@ -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):
|
||||||
|
|||||||
17
moulder.py
17
moulder.py
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user