Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 140 additions & 15 deletions xray_vision/backend/mpl/cross_section_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,21 @@ class CrossSection(object):
interpolation : str, optional
Interpolation method to use. List of valid options can be found in
CrossSection2DView.interpolation
vert_loc : str, optional
set to 'left' by default, can specify 'right' to move the verical
axis to the right side of the image
horiz_loc : str, optional
set to 'top' by default, can specify 'bottom' to move the horizontal
axis below the image
title : str, optional
title for the figure
vert_label : str, optional
label that describes the x axis of the vertical plot
horiz_label : str, optional
label that describes the x axis of the horizontal plot
extent_labels : scalars, (left, right, bottom, top), optional
The location, in data-coordinates, of the ranges for the values
of the vertical and horizontal slices, scaling the image as needed

Properties
----------
Expand All @@ -291,9 +306,30 @@ class CrossSection(object):

"""
def __init__(self, fig, cmap=None, norm=None,
limit_func=None, auto_redraw=True, interpolation=None):
limit_func=None, auto_redraw=True, interpolation=None,
vert_loc=None, horiz_loc=None, title=None,
vert_label=None, horiz_label=None, extent_labels=None):

self._cursor_position_cbs = []
self.title = title
self.vert_label = vert_label
self.horiz_label = horiz_label
self.extent_labels = extent_labels

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With my suggestion on extent, this will move to update_img and thus not need to be an instance attribute.

if extent_labels is not None:
self.x_tick_values = np.linspace(int(self.extent_labels[0]),
int(self.extent_labels[1]), 5)
self.y_tick_values = np.linspace(int(extent_labels[2]),
int(extent_labels[3]), 5)
if vert_loc is None:
vert_loc = 'left'
if vert_loc not in {'right', 'left'}:
raise ValueError(f"Error, {vert_loc} not 'left' or 'right'")
if horiz_loc is None:
horiz_loc = 'top'
if horiz_loc not in {'top', 'bottom'}:
raise ValueError(f"Error, {horiz_loc} not 'top' or 'bottom'")
self.horiz_loc = horiz_loc
self.vert_loc = vert_loc
if interpolation is None:
interpolation = _INTERPOLATION[0]
self._interpolation = interpolation
Expand Down Expand Up @@ -325,9 +361,9 @@ def __init__(self, fig, cmap=None, norm=None,
fig.clf()
# Configure the figure in our own image
#
# +----------------------+
# | H cross section |
# +----------------------+
# +----------------------+
# | H cross section |
# +----------------------+
# +---+ +----------------------+
# | V | | |
# | | | |
Expand Down Expand Up @@ -357,15 +393,63 @@ def __init__(self, fig, cmap=None, norm=None,

# set up all the other axes
# (set up the horizontal and vertical cuts)
self._ax_h = divider.append_axes('top', .5, pad=0.1,
sharex=self._im_ax)
self._ax_h.yaxis.set_major_locator(LinearLocator(numticks=2))
self._ax_v = divider.append_axes('left', .5, pad=0.1,
sharey=self._im_ax)

# Orientation types and changing plots based on arguments
enum_left_right = {'left': True, 'right': False}

kwargs_vert = {'position': self.vert_loc, 'size': .5, 'pad': 0.1}
kwargs_horiz = {'position': self.horiz_loc, 'size': .5}
if self.extent_labels is None:
kwargs_vert['sharey'] = self._im_ax

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why are you only sharing when there are extent labels?
I'm not very familiar, but I tried moving the image with the mouse without extent_labels and the cross section follows.
However, it does not for the image. Perhaps we should always share, and make sure the image extent is the same as the cross section's limits.

(more comments below suggesting what to do )

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

  • Initially there was no reason to get rid of the shared axes, but when they are shared the extent labels appear on the image rather than the vertical and horizontal cuts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. The code seems to be right since it applies the labels/ticks to the correct axes, self._ax_h and self._ax_v. Can it be a matplotlib bug? @tacaswell, thoughts?

if self.horiz_loc is 'top':
kwargs_horiz['pad'] = 0.1
else:
kwargs_horiz['pad'] = 0.25
kwargs_horiz['sharex'] = self._im_ax
else:
kwargs_horiz['pad'] = 0.25
self._ax_v = divider.append_axes(**kwargs_vert)
self._ax_h = divider.append_axes(**kwargs_horiz)

if self.vert_loc is 'right':
self._ax_v.yaxis.tick_right()

if self.vert_label is not None:
kwargs = {'ylabel': self.vert_label}
if self.vert_loc is 'right':
if self.extent_labels is None:
kwargs['labelpad'] = -60
else:
kwargs['labelpad'] = -80
self._ax_v.set_ylabel(**kwargs)

if self.horiz_label is not None:
kwargs = {'xlabel': self.horiz_label}
if self.horiz_loc is 'top':
if self.title is not None or extent_labels is not None:
kwargs['labelpad'] = -70
else:
kwargs['labelpad'] = -50
self._ax_h.set_xlabel(**kwargs)

self._ax_v.xaxis.set_major_locator(LinearLocator(numticks=2))
self._ax_cb = divider.append_axes('right', .2, pad=.5)
# add the color bar
self._ax_h.yaxis.set_major_locator(LinearLocator(numticks=2))

self._ax_cb = divider.append_axes(
#used to place the colorbar based on the location of the vertical
#axis
_swap_sides(self.vert_loc,enum_left_right), .2, pad=.5)

if self.title is not None:
if self.horiz_loc is 'top':
self._ax_h.set_title(self.title, pad=50)
else:
self._im_ax.set_title(self.title)

# colorbar
self._cb = fig.colorbar(self._im, cax=self._ax_cb)
if self.horiz_loc is 'bottom' and self.vert_loc is 'left':
self._ax_h.yaxis.tick_right()

# add the cursor place holder
self._cur = None
Expand Down Expand Up @@ -471,7 +555,9 @@ def _connect_callbacks(self):

self._clear_cid = self._fig.canvas.mpl_connect('draw_event',
self._clear)
self._fig.tight_layout()
if self.vert_label is None and self.horiz_label is None \
and self.extent_labels is None:
self._fig.tight_layout()
self._fig.canvas.draw()

def _disconnect_callbacks(self):
Expand Down Expand Up @@ -522,11 +608,17 @@ def _init_artists(self, init_image):
# update the extent of the image artist
self._im.set_extent([-0.5, im_shape[1] + .5,
im_shape[0] + .5, -0.5])

# update the limits of the image axes to match the exent
self._im_ax.set_xlim([-.05, im_shape[1] + .5])
self._im_ax.set_ylim([im_shape[0] + .5, -0.5])

if self.extent_labels is not None:
self._ax_h.set_xticks(np.linspace(0, im_shape[1], 5))
self._ax_h.set_xticklabels(self.x_tick_values)

self._ax_v.set_yticks(np.linspace(0, im_shape[0], 5))
self._ax_v.set_yticklabels(self.y_tick_values)

# update the format coords printer
numrows, numcols = im_shape

Expand All @@ -539,9 +631,20 @@ def format_coord(x, y):
if col >= 0 and col < numcols and row >= 0 and row < numrows:
# if it does, grab the value
z = self._imdata[row, col]
return "X: {x:d} Y: {y:d} I: {i:.2f}".format(x=col, y=row, i=z)
if self.extent_labels is not None:
return "X: {x:g} Y: {y:g} I: {i:g}".format(
x=col / im_shape[1] * (self.extent_labels[1] -

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think if you set the image extent you won't need this anymore
you can do this in (look for this line in code)

self._init_artists(image, extent=extent)

and update things accordingly to get it working.

self.extent_labels[0]) +
self.extent_labels[0],
y=row / im_shape[0] * (self.extent_labels[2] -
self.extent_labels[3]) +
self.extent_labels[3],
i=z)
else:
return "X: {x:d} Y: {y:d} I: {i:.2f}".format(x=col, y=row,
i=z)
else:
return "X: {x:d} Y: {y:d}".format(x=col, y=row)
return ""

# replace the current format_coord function
self._im_ax.format_coord = format_coord
Expand Down Expand Up @@ -676,3 +779,25 @@ def autoscale_horizontal(self, enable):
@auto_redraw
def autoscale_vertical(self, enable):
self._ax_v.autoscale(enable=False)

"""
Easily swaps sides using the values in the enum and the key. Whatever the
value of the key is, the key in the enum that is not the given key will be
returned. If key is not in enum, the key assigned to 'True' is returned.

Parameters
----------

key : string
value in enum that will not be returned
enum : dict
dictionary holding two keys with values 'True' and 'False'

"""
def _swap_sides(key, enum):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can you add a comment explaining what this is doing and why?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

  • I will add a comment!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a helper function to easily swap the sides, e.g. 'left' -> 'right' and vice versa. In case the supplied value key is not in keys of enum, the default value from the enum is used (default is the one which has the True value).

@kalebswartz7, please create a docstring explaining this and the arguments to the function. Thanks!

assert len(enum) == 2, 'The enum dict should contain 2 key-value pairs'
reverse_enum = {v: k for k, v in enum.items()}
try:
return reverse_enum[not enum[key]]
except Exception:
return reverse_enum[True]