Skip to content

Commit

Permalink
Merge pull request #4 from z-gora/dev
Browse files Browse the repository at this point in the history
v1.1 - release
  • Loading branch information
jdranczewski authored Aug 30, 2018
2 parents 88e4635 + 2bae871 commit 5891c39
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 56 deletions.
2 changes: 1 addition & 1 deletion help.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ <h3 id="h3_fast_interpolation">Fast interpolation</h3>
<h3 id="h3_exact_interpolation">Exact interpolation</h3>
<p>
The exact interpolation method is a custom one written for this software and
based on the work George has done with Magic, as well as <a
based on the work George Swadling has done with Magic, as well as <a
href="#https://doi.org/10.1016/S0098-3004(97)00088-5" target=_blank>this
paper</a>.
</p>
Expand Down
76 changes: 43 additions & 33 deletions magic2/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,49 @@ def __init__(self, filename, fi=None, m=None, imshow=None):
# An image is loaded, and only its first colour component is taken
# out of red, green, blue, alpha.
# The .png images supplied are greyscale.
if filename != 'dump' and fi is None:
image = plt.imread(filename.name)[:, :, 0]
# Fringes are black, extract them from the image
self.fringes_image = image == 0
# This is the user defined mask, it was grey (so neither black nor
# white, which is the condition we're using here)
self.mask = sp.logical_or(image == 1, self.fringes_image)
else:
# This uses a provide mask and image (for example from an .m2 file)
self.fringes_image = fi
self.mask = m
# This will store only the labelled fringes, currently empty
self.fringes_image_clean = sp.zeros_like(self.fringes_image)-0
# -1024 indicates an area where there is no data
# Visual stores the fringe phases, but allows for width, making
# the fringes easier to display
self.fringe_phases_visual = sp.zeros_like(self.fringes_image)-1024
# In fringe_phases all the fringes have their initial width
self.fringe_phases = sp.zeros_like(self.fringes_image)-1024
# Indexing starts at 0, so -1 is a good choice for 'not an index'
self.fringe_indices = sp.zeros_like(self.fringes_image)-1
# x and y are used during interpolation processes to make
# calculations easier, they store the x and y position of
# every pixel
self.x, self.y = sp.meshgrid(sp.arange(0, len(self.fringes_image[0])),
sp.arange(0, len(self.fringes_image)))
self.xy = sp.transpose([self.y.ravel(), self.x.ravel()])
# Interpolated will store the interpolated version of the image
self.interpolation_done = False
self.interpolated = sp.zeros_like(self.fringes_image)-1024.0
# this parameter will store the object returned by matplotlib's
# imshow function, making it easy to change the data being displayed
self.imshow = imshow
self.error = False
try:
if filename != 'dump' and fi is None:
# The image may have 3-4 channels if its RGB(A), we only
# need one of them
try:
image = plt.imread(filename.name)[:, :, 0]
# If the image is greyscale, just take the whole thing
except IndexError:
image = plt.imread(filename.name)
# Fringes are black, extract them from the image
self.fringes_image = image == 0
# This is the user defined mask, it was grey (so neither black nor
# white, which is the condition we're using here)
self.mask = sp.logical_or(image == 1, self.fringes_image)
else:
# This uses a provide mask and image (for example from an .m2 file)
self.fringes_image = fi
self.mask = m
# This will store only the labelled fringes, currently empty
self.fringes_image_clean = sp.zeros_like(self.fringes_image)-0
# -1024 indicates an area where there is no data
# Visual stores the fringe phases, but allows for width, making
# the fringes easier to display
self.fringe_phases_visual = sp.zeros_like(self.fringes_image)-1024
# In fringe_phases all the fringes have their initial width
self.fringe_phases = sp.zeros_like(self.fringes_image)-1024
# Indexing starts at 0, so -1 is a good choice for 'not an index'
self.fringe_indices = sp.zeros_like(self.fringes_image)-1
# x and y are used during interpolation processes to make
# calculations easier, they store the x and y position of
# every pixel
self.x, self.y = sp.meshgrid(sp.arange(0, len(self.fringes_image[0])),
sp.arange(0, len(self.fringes_image)))
self.xy = sp.transpose([self.y.ravel(), self.x.ravel()])
# Interpolated will store the interpolated version of the image
self.interpolation_done = False
self.interpolated = sp.zeros_like(self.fringes_image)-1024.0
# this parameter will store the object returned by matplotlib's
# imshow function, making it easy to change the data being displayed
self.imshow = imshow
except OSError:
self.error = True


# This function can be used to draw the fringes on a given canvas
Expand Down
69 changes: 53 additions & 16 deletions magic2/labelling.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import scipy as sp
import scipy.special as special
from . import graphics as m2graphics
from matplotlib.animation import FuncAnimation


# This class stores some data about the current labelling operation
Expand All @@ -14,27 +15,30 @@ def __init__(self, options=None):
self.binds = []
# The app's options
self.options = options
# The animator object
self.ani = None


# This function is used to handle the user pressing a mouse key
# while in the graphing area
def onclick(event, labeller, line_plot, temp_line, fringes, canvas, fig, ax):
def onclick(event, labeller, line_plot, temp_line, fringes, canvas, fig, ax, ani):
# If this was a single click within the graphing area, add a point
# to the line
if labeller.control and not event.dblclick and event.xdata:
labeller.points.append([event.ydata, event.xdata])
# We use the points list to draw a line on the graph
points = sp.array(labeller.points)
line_plot.set_data(points[:, 1], points[:, 0])
line_plot.figure.canvas.draw()
# If the event was a double click, label the fringes and clear the data
# related to the current labelling operation
if event.button == 3:
label_fringes(labeller, fringes, canvas, fig, ax)
labeller.points = []
line_plot.set_data([], [])
temp_line.set_data([], [])
line_plot.figure.canvas.draw()
# This clears the blit cache and redraws the figure
ani._blit_cache.clear()
fig.canvas.draw()


# This uses the set of points chosen by the user to label the fringes
Expand Down Expand Up @@ -68,7 +72,7 @@ def label_fringes(labeller, fringes, canvas, fig, ax):
# overhead here)
fix_indices = []
# Get the increment from a radio button variable if available
if labeller.options.direction_var is not None:
if labeller.options is not None:
increment = labeller.options.direction_var.get()
else:
increment = 1
Expand All @@ -84,10 +88,14 @@ def label_fringes(labeller, fringes, canvas, fig, ax):
# 0## 0 - fringe
# ##0 # - line
# # 0
for index in canvas.fringe_indices[int(y[i])-1:int(y[i])+1, int(x[i])]:
# If a non-empty index found, use this and break
if index != -1:
break
try:
for index in canvas.fringe_indices[int(y[i])-1:int(y[i])+1, int(x[i])]:
# If a non-empty index found, use this and break
if index != -1:
break
# In case we go out of range, just pass
except IndexError:
pass
# If the index is not -1 and not the same as the previous one, assign
# a calculated phase to a fringe
if index >= 0 and index != prev_index:
Expand All @@ -107,7 +115,7 @@ def label_fringes(labeller, fringes, canvas, fig, ax):
prev_index = index
# If the maximum phase reached in this labelling series is higher than
# the one stored, update that. This is used to update the colour range
if labeller.options.direction_var is not None:
if labeller.options is not None:
if labeller.options.direction_var.get() == 1:
if phase > fringes.max:
fringes.max = phase
Expand All @@ -118,7 +126,11 @@ def label_fringes(labeller, fringes, canvas, fig, ax):
if phase > fringes.max:
fringes.max = phase
# Render and show the changed fringes
m2graphics.render_fringes(fringes, canvas, width=labeller.options.width_var.get(), indices=fix_indices)
if labeller.options is not None:
width = labeller.options.width_var.get()
else:
width = 3
m2graphics.render_fringes(fringes, canvas, width=width, indices=fix_indices)
canvas.imshow.set_data(sp.ma.masked_where(canvas.fringe_phases_visual == -1024, canvas.fringe_phases_visual))
canvas.imshow.set_clim(fringes.min, fringes.max)
canvas.imshow.figure.canvas.draw_idle()
Expand All @@ -134,7 +146,7 @@ def onmove(event, labeller, line_plot, temp_line, ax):
# Note that we are not actually modifying labeller.points here
points = sp.array([labeller.points[-1], [event.ydata, event.xdata]])
temp_line.set_data(points[:, 1], points[:, 0])
line_plot.figure.canvas.draw_idle()
# The FuncAnimation will render this, we just update data here


# Store whether the control key is pressed
Expand All @@ -160,26 +172,51 @@ def onrelease(event, labeller):
labeller.control = False


# This update function is used in the animation. It doesn't do stuff, but
# it returns the objects that need to be redrawn. i is the frame number
def ani_update(i, line_plot, temp_line, imshow, prev_lim, ax, labeller):
if prev_lim != [ax.get_xlim(), ax.get_ylim()]:
# If the limits change, do a redraw. This is slooow, but actually works
# (the slight overhead with scrolling is worth it, as we get
# significant improvements when drawing lines)
prev_lim[0], prev_lim[1] = ax.get_xlim(), ax.get_ylim()
labeller.ani._blit_cache.clear()
imshow.figure.canvas.draw()
return line_plot, temp_line


# This sets up the labeller object, the line that is drawn, as well as
# attaches all the event handlers
def label(fringes, canvas, fig, ax, direction_var=None):
labeller = Labeller(direction_var)
line_plot, = ax.plot([], [], "--")
temp_line, = ax.plot([], [], "--")
def label(fringes, canvas, fig, ax, master=None, options=None, imshow=None):
labeller = Labeller(options=options)
line_plot, = ax.plot([], [], "--", animated=True)
temp_line, = ax.plot([], [], "--", animated=True)
b0 = fig.canvas.mpl_connect('button_press_event',
lambda event: onclick(event, labeller, line_plot, temp_line,
fringes, canvas, fig, ax))
fringes, canvas, fig, ax, labeller.ani))
b1 = fig.canvas.mpl_connect('motion_notify_event',
lambda event: onmove(event, labeller, line_plot, temp_line, ax))
b2 = fig.canvas.mpl_connect('key_press_event',
lambda event: onpress(event, labeller, line_plot, temp_line))
b3 = fig.canvas.mpl_connect('key_release_event',
lambda event: onrelease(event, labeller))
labeller.binds = [b0, b1, b2, b3]
# Create an animation function that updates the state of the line
# that is being drawn. blit is used to speed things up. It's cache
# has to be cleared when fringe labelling is changed
prev_lim = [ax.get_xlim(), ax.get_ylim()]
labeller.ani = FuncAnimation(fig, ani_update, interval=100,
fargs=(line_plot, temp_line, imshow,
prev_lim, ax, labeller), blit=True)
# This is needed for the animation to start.
# Because reasons
# I guess
fig.canvas.draw()
return labeller


def stop_labelling(fig, labeller):
labeller.ani._stop()
for bind in labeller.binds:
fig.canvas.mpl_disconnect(bind)
del labeller
16 changes: 11 additions & 5 deletions magic2gui/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def open_image(options, env):
pass
else:
# Display a window for chooisng the file
filename = fd.askopenfile(filetypes=[("PNG files", "*.png;*.PNG")])
filename = fd.askopenfile(filetypes=[("PNG files", "*.png;*.PNG")],
title="Open "+env+" interferogram")
if filename is not None:
options.status.set("Reading the file", 0)
if not options.ncmanual:
Expand All @@ -46,6 +47,10 @@ def open_image(options, env):
options.subtracted = None
# Create a canvas object
canvas = options.objects[env]['canvas'] = m2graphics.Canvas(filename)
if canvas.error:
mb.showerror("File not opened", "There was an error while opening the image. Are you sure it's a .png?")
options.objects[env]['canvas'] = None
return False
options.status.set("Looking for fringes", 33)
# Extract fringe information from the file
fringes = options.objects[env]['fringes'] = m2fringes.Fringes()
Expand Down Expand Up @@ -328,8 +333,6 @@ def set_colormap(options):

# Handle the user choosing one of the radio buttons
def show_radio(options):
# Give the focus back to the graph
options.mframe.canvas._tkcanvas.focus_set()
# Store the mode info from the buttons
key = options.show_var.get().split("_")
# Set the buttons to the previous state, in case the user cancels
Expand Down Expand Up @@ -408,6 +411,7 @@ def set_mode(options):
options.ax.clear()
if options.labeller is not None:
m2labelling.stop_labelling(options.fig, options.labeller)
options.labeller = None
if options.cbar is not None:
# If there exists a colorbar, clean it and hide it
options.mframe.cax.clear()
Expand All @@ -425,7 +429,7 @@ def set_mode(options):
canvas.imshow.set_clim(fringes.min, fringes.max)
options.labeller = m2labelling.label(fringes, canvas,
options.fig, options.ax,
options)
options=options, imshow=options.imshow)
elif key[1] == 'map':
canvas = options.objects[key[0]]['canvas']
# The map image is masked where there is no interpolation data and
Expand Down Expand Up @@ -472,6 +476,8 @@ def set_mode(options):
options.fig.canvas.draw()
# Set the radio buttons to the correct position
options.show_var.set(options.mode)
# Give the focus back to the graph
options.mframe.canvas._tkcanvas.focus_set()


# Decrease the width of the rendered fringes
Expand Down Expand Up @@ -845,7 +851,7 @@ def body(self, master):
logo = Tk.Label(master, image=photo)
logo.photo = photo
logo.pack()
label = Tk.Label(master, text="This software was created by Jakub Dranczewski during a UROP in 2018.\nIt is based on concepts from Magic, which was created by George.\n\nYou can contact me on jbd17@ic.ac.uk or (as I inevitably loose either the whole email or the 17) jakub.dranczewski@gmail.com")
label = Tk.Label(master, text="This software was created by Jakub Dranczewski during a UROP in 2018.\nIt is based on concepts from Magic, which was created by George Swadling.\n\nYou can contact me on jbd17@ic.ac.uk or (as I inevitably loose either the whole email or the 17) jakub.dranczewski@gmail.com\n\nv1.1")
label.pack()

def buttonbox(self):
Expand Down
11 changes: 10 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from matplotlib.pyplot import imread
import pickle
import webbrowser
import ctypes


# This is a way of getting global variables without actually using global
Expand Down Expand Up @@ -76,7 +77,15 @@ def main():
# Create a root tkinter object and set its title
options.root = root = Tk.Tk()
root.wm_title("Magic2")
# Setting the icon doesn't work on Linux for some reason
# This is windows specific, but needed for the icon to show up
# in the taskbar. try/catch in case this is run on other platforms
try:
myappid = 'jdranczewski.magic2'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
except:
pass
# Setting the icon doesn't work on Linux for some reason, so we have
# a try/catch here again
try:
root.iconbitmap("magic2.ico")
except:
Expand Down

1 comment on commit 5891c39

@jdranczewski
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Drawing the labelling line is now much, much more speedy (it was significantly lagging behind before, slowing down the entire operation).
  • The programme's icon should now show up in your taskbar, unless your installation of IPython (Anaconda) is greedy and takes the icon for itself.
  • The prompt for choosing the .png file now tells you whether you're opening the background or the plasma interferogram (at the top of the window).
  • No more crashes if you go out of the interferogram with your labelling line for some reason.
  • Errors are now handled properly if an invalid .png file is opened.
  • The focus is now given back to the graph immediately after using a dialog box.
  • I found out what George's surname was, so now he's credited more properly!

Please sign in to comment.