def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) self._alpha = None self.set_facecolors(zalpha(self._facecolor3d, vzs)) self.set_edgecolors(zalpha(self._edgecolor3d, vzs)) PatchCollection.set_offsets(self, zip(vxs, vys)) return min(vzs)
def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) #FIXME: mpl allows us no way to unset the collection alpha value self._alpha = None self.set_facecolors(zalpha(self._facecolor3d, vzs)) self.set_edgecolors(zalpha(self._edgecolor3d, vzs)) PatchCollection.set_offsets(self, zip(vxs, vys)) return min(vzs)
def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) #FIXME: mpl allows us no way to unset the collection alpha value self._alpha = None self.set_facecolors(zalpha(self._facecolor3d, vzs)) self.set_edgecolors(zalpha(self._edgecolor3d, vzs)) PatchCollection.set_offsets(self, list(zip(vxs, vys))) if vzs.size > 0 : return min(vzs) else : return np.nan
def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) fcs = zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d fcs = mcolors.colorConverter.to_rgba_array(fcs, self._alpha) self.set_facecolors(fcs) ecs = zalpha(self._edgecolor3d, vzs) if self._depthshade else self._edgecolor3d ecs = mcolors.colorConverter.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) PatchCollection.set_offsets(self, list(zip(vxs, vys))) if vzs.size > 0: return min(vzs) else: return np.nan
def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) fcs = mcolors.colorConverter.to_rgba_array(fcs, self._alpha) self.set_facecolors(fcs) ecs = (zalpha(self._edgecolor3d, vzs) if self._depthshade else self._edgecolor3d) ecs = mcolors.colorConverter.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) PatchCollection.set_offsets(self, list(zip(vxs, vys))) if vzs.size > 0: return min(vzs) else: return np.nan
def render(self, patch_size=0.5, alpha=1.0, x_offset=100): '''Draws the legend graphic and saves it to a file.''' n = len(self.colors) s = patch_size # This offset is transformed to "data" coordinates (inches) left_offset = (-s * 1.5) - (x_offset / self.dpi) # Create grid to plot the artists grid = np.concatenate(( np.zeros(n).reshape(n, 1), np.arange(-n, 1)[1:].reshape(n, 1) ), axis=1) plt.text(left_offset, 1.1, 'Legend', family='sans-serif', size=14, weight='bold', color='#ffffff') patches = [] for i in range(n): # Add a rectangle rect = mpatches.Rectangle(grid[i] - [0, 0], s, s, ec='none') patches.append(rect) self.__label__(grid[i], self.labels[i], x_offset) collection = PatchCollection(patches, alpha=alpha) # Space the patches and the text labels collection.set_offset_position('data') collection.set_offsets(np.array([ [left_offset, 0] ])) collection.set_facecolors(self.colors) #collection.set_edgecolor('#ffffff') # Draw color for box outlines self.axis.add_collection(collection) plt.axis('equal') plt.axis('off') plt.savefig(self.file_path, facecolor=self.bg_color, dpi=self.dpi, pad_inches=0) return self.file_path
def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) fcs = (zalpha(self._facecolor, vzs) if self._depthshade else self._facecolor) fcs = mcolors.to_rgba_array(fcs, self._alpha) self.set_facecolor(fcs) ecs = (zalpha(self._edgecolor, vzs) if self._depthshade else self._edgecolor) ecs = mcolors.to_rgba_array(ecs, self._alpha) self.set_edgecolor(ecs) PatchCollection.set_offsets(self, np.column_stack([vxs, vys])) if vzs.size > 0: return min(vzs) else: return np.nan
def animate(x, rs, label=False, lims=(12, 12), cols=[]): """ Takes an array of positions with shape = (steps, particle, coordinates) and spot size (r) and animates their trajectory. """ if not plt.get_fignums(): fig, ax = plt.subplots(figsize=(5,5)) else: ax = plt.gca() fig = plt.gcf() x = np.squeeze(x) # if there is one particle fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9) # use plt.Circle over ax.scatter since Circle size is in data coordinates, but # scatter marker sizes are in Figure coordinates. c = PatchCollection([plt.Circle((0, 0), r) for r in rs]) c.set_offset_position('data') c.set_offsets(x[0]) if not cols and x.shape[1] > 10: cols = ['r' if i < lims[0]/2 else 'b' for i in x[0, :, 0]] elif cols: pass else: cols = ['r' for i in x[0, :]] c.set_color(cols) ax.add_collection(c) def advance(fn): step = fn % len(x) c.set_offsets(x[step]) ax.set_title(step) for l, xy in zip(ls, x[step]): l.set_x(xy[0]) l.set_y(xy[1]) if label: ls = [plt.text(*xy, i) for i, xy in enumerate(x[0])] else: ls = [] ax.set_xlim((0, lims[0])) ax.set_ylim((0, lims[0])) ax.add_collection(c) fa = animation.FuncAnimation(fig, advance, interval=5e3//len(x)) plt.show() return fa
def do_3d_projection(self, renderer): # see _update_scalarmappable docstring for why this must be here _update_scalarmappable(self) xs, ys, zs = self._offsets3d vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) fcs = mcolors.to_rgba_array(fcs, self._alpha) self.set_facecolors(fcs) ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else self._edgecolor3d) ecs = mcolors.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) PatchCollection.set_offsets(self, np.column_stack([vxs, vys])) if vzs.size > 0: return min(vzs) else: return np.nan
class InstrumentView: def __init__(self, scipp_obj=None, bins=None, masks=None, cmap=None, log=None, vmin=None, vmax=None, aspect=None, size=1, projection=None, nan_color=None, filename=None, continuous_update=None, dim=None): self.fig2d = None self.fig3d = None self.scatter2d = None self.scatter3d = None self.outline = None self.size = size self.aspect = aspect self.do_update = None self.figurewidget = widgets.Output() self.figure2d = False self.figure3d = False self.image = None self.nan_color = nan_color self.log = log self.current_projection = None self.dim = dim self.data_arrays = {} tp = type(scipp_obj) if tp is sc.Dataset or tp is sc.DatasetProxy: for key in sorted(scipp_obj.keys()): var = scipp_obj[key] if self.dim in var.dims: self.data_arrays[key] = var elif tp is sc.DataArray or tp is sc.DataProxy: self.data_arrays[scipp_obj.name] = scipp_obj else: raise RuntimeError("Unknown input type: {}. Allowed inputs " "are a Dataset or a DataArray (and their " "respective proxies).".format(tp)) self.globs = {"cmap": cmap, "log": log, "vmin": vmin, "vmax": vmax} self.params = {} self.hist_data_array = {} self.scalar_map = {} self.minmax = {} # Find the min/max time-of-flight limits and store them self.minmax["tof"] = [np.Inf, np.NINF, 1] for key, data_array in self.data_arrays.items(): bins_here = bins if data_array.sparse_dim is not None and bins_here is None: bins_here = True if bins_here is not None: dim = None if data_array.sparse_dim is not None else self.dim spdim = None if data_array.sparse_dim is None else self.dim var = make_bins(data_array=data_array, sparse_dim=spdim, dim=dim, bins=bins_here, padding=(data_array.sparse_dim is not None)) else: var = data_array.coords[self.dim] self.minmax["tof"][0] = min(self.minmax["tof"][0], var.values[0]) self.minmax["tof"][1] = max(self.minmax["tof"][1], var.values[-1]) self.minmax["tof"][2] = var.shape[0] # Rebin all DataArrays to common Tof axis self.rebin_data(np.linspace(*self.minmax["tof"])) # Create dropdown menu to select the DataArray keys = list(self.hist_data_array.keys()) self.dropdown = widgets.Dropdown(options=keys, description="Select entry:", layout={"width": "initial"}) self.dropdown.observe(self.change_data_array, names="value") # Store current active data entry (DataArray) self.key = keys[0] # Create a Tof slider and its label self.tof_dim_indx = self.hist_data_array[self.key].dims.index(self.dim) self.slider = widgets.IntSlider( value=0, min=0, step=1, description=str(self.dim).replace("Dim.", ""), max=self.hist_data_array[self.key].shape[self.tof_dim_indx] - 1, continuous_update=continuous_update, readout=False) self.slider.observe(self.update_colors, names="value") self.label = widgets.Label() # Add text boxes to change number of bins/bin size self.nbins = widgets.Text(value=str( self.hist_data_array[self.key].shape[self.tof_dim_indx]), description="Number of bins:", style={"description_width": "initial"}) self.nbins.on_submit(self.update_nbins) tof_values = self.hist_data_array[self.key].coords[self.dim].values self.bin_size = widgets.Text(value=str(tof_values[1] - tof_values[0]), description="Bin size:") self.bin_size.on_submit(self.update_bin_size) projections = [ "3D", "Cylindrical X", "Cylindrical Y", "Cylindrical Z", "Spherical X", "Spherical Y", "Spherical Z" ] # Create toggle buttons to change projection self.buttons = {} for p in projections: self.buttons[p] = widgets.Button( description=p, disabled=False, button_style=("info" if (p == projection) else "")) self.buttons[p].on_click(self.change_projection) items = [self.buttons["3D"]] for x in "XYZ": items.append(self.buttons["Cylindrical {}".format(x)]) items.append(self.buttons["Spherical {}".format(x)]) if x != "Z": items.append(widgets.Label()) self.togglebuttons = widgets.GridBox( items, layout=widgets.Layout(grid_template_columns="repeat(3, 150px)")) # Place widgets in boxes self.vbox = widgets.VBox([ widgets.HBox([self.dropdown, self.slider, self.label]), widgets.HBox([self.nbins, self.bin_size]), self.togglebuttons ]) self.box = widgets.VBox([self.figurewidget, self.vbox]) self.box.layout.align_items = "center" # Protect against uninstalled ipyvolume if ipv is None and projection == "3D": print("Warning: 3D projection requires ipyvolume to be " "installed. Use conda/pip install ipyvolume. Reverting to " "2D projection.") self.buttons[projections[1]].button_style = "info" self.buttons["3D"].button_style = "" self.buttons["3D"].disabled = True # Render the plot here instead of at the top level because to capture # the matplotlib output (if a 2D projection is requested to begin with, # the output widget needs to be displayed first, before any mpl figure # is displayed. render_plot(widgets=self.box, filename=filename, ipv=ipv) # Get detector positions self.det_pos = np.array( sn.position(self.hist_data_array[self.key]).values) # Find extents of the detectors for i, x in enumerate("xyz"): self.minmax[x] = [ np.amin(self.det_pos[:, i]), np.amax(self.det_pos[:, i]) ] # Update the figure self.change_projection(self.buttons[projection]) # Create members object self.members = { "widgets": { "sliders": self.slider, "buttons": self.buttons, "text": { "nbins": self.nbins, "bin_size": self.bin_size }, "dropdown": self.dropdown }, "fig2d": self.fig2d, "fig3d": self.fig3d, "scatter2d": self.scatter2d, "scatter3d": self.scatter3d, "outline": self.outline } return def rebin_data(self, bins): """ Rebin the original data to Tof given some bins. This is executed both on first instrument display and when either the number of bins or the bin width is changed. """ for key, data_array in self.data_arrays.items(): # Histogram the data in the Tof dimension if data_array.sparse_dim is not None: self.hist_data_array[key] = histogram_sparse_data( data_array, data_array.sparse_dim, bins) else: self.hist_data_array[key] = sc.rebin( data_array, self.dim, make_bins(data_array=data_array, dim=self.dim, bins=bins, padding=False)) # Parse input parameters for colorbar self.params[key] = parse_params( globs=self.globs, array=self.hist_data_array[key].values) cmap = cm.get_cmap(self.params[key]["cmap"]) cmap.set_bad(color=self.nan_color) self.scalar_map[key] = cm.ScalarMappable( cmap=cmap, norm=self.params[key]["norm"]) return def update_colors(self, change): self.do_update(change) self.label.value = name_with_unit( var=self.hist_data_array[self.key].coords[self.dim], name=value_to_string(self.hist_data_array[self.key].coords[ self.dim].values[change["new"]])) return def change_projection(self, owner): if owner.description == self.current_projection: owner.button_style = "info" return if self.current_projection is not None: self.buttons[self.current_projection].button_style = "" # Temporarily disable automatic plotting in notebook if plt.isinteractive(): plt.ioff() re_enable_interactive = True else: re_enable_interactive = False update_children = False if owner.description == "3D": self.projection_3d() self.do_update = self.update_colors_3d else: if self.current_projection == "3D" or \ self.current_projection is None: update_children = True self.projection_2d(owner.description, update_children) self.do_update = self.update_colors_2d self.update_colors({"new": self.slider.value}) self.current_projection = owner.description self.buttons[owner.description].button_style = "info" # Re-enable automatic plotting in notebook if re_enable_interactive: plt.ion() return def projection_3d(self): # Initialise Figure if not self.figure3d: self.fig3d = ipv.figure(width=config.plot.width, height=config.plot.height, animation=0, lighting=False) max_size = 0.0 dx = {"x": 0, "y": 0, "z": 0} for ax in dx.keys(): dx[ax] = np.ediff1d(self.minmax[ax]) max_size = np.amax(list(dx.values())) # Make plot outline if aspect ratio is to be conserved if self.aspect == "equal": arrays = dict() for ax, s in dx.items(): diff = max_size - s arrays[ax] = [ self.minmax[ax][0] - 0.5 * diff, self.minmax[ax][1] + 0.5 * diff ] outl_x, outl_y, outl_z = np.meshgrid(arrays["x"], arrays["y"], arrays["z"], indexing="ij") self.outline = ipv.plot_wireframe(outl_x, outl_y, outl_z, color="black") # Try to guess marker size perc_size = 100.0 * self.size / max_size self.scatter3d = ipv.scatter(x=self.det_pos[:, 0], y=self.det_pos[:, 1], z=self.det_pos[:, 2], marker="square_2d", size=perc_size) self.figure3d = True self.figurewidget.clear_output() self.box.children = tuple([self.figurewidget, ipv.gcc(), self.vbox]) return def update_colors_3d(self, change): arr = self.hist_data_array[self.key][self.dim, change["new"]].values if self.log: arr = np.ma.masked_where(arr <= 0, arr) self.scatter3d.color = self.scalar_map[self.key].to_rgba(arr) return def projection_2d(self, projection, update_children): # Initialise figure if we switched from 3D view, if not re-use current # figure. if update_children: self.box.children = tuple([self.figurewidget, self.vbox]) if not self.figure2d: self.fig2d, self.ax = plt.subplots( 1, 1, figsize=(config.plot.width / config.plot.dpi, config.plot.height / config.plot.dpi)) if update_children: with self.figurewidget: disp.display(self.fig2d) # Compute cylindrical or spherical projections permutations = {"X": [0, 2, 1], "Y": [1, 0, 2], "Z": [2, 1, 0]} axis = projection[-1] theta = np.arctan2(self.det_pos[:, permutations[axis][2]], self.det_pos[:, permutations[axis][1]]) if projection.startswith("Cylindrical"): z_or_phi = self.det_pos[:, permutations[axis][0]] elif projection.startswith("Spherical"): z_or_phi = np.arcsin( self.det_pos[:, permutations[axis][0]] / np.sqrt(self.det_pos[:, 0]**2 + self.det_pos[:, 1]**2 + self.det_pos[:, 2]**2)) # Create the scatter if not self.figure2d: patches = [] for x, y in zip(theta, z_or_phi): patches.append( Rectangle((x - 0.5 * self.size, y - 0.5 * self.size), self.size, self.size)) self.scatter2d = PatchCollection( patches, cmap=self.params[self.key]["cmap"], norm=self.params[self.key]["norm"], array=self.hist_data_array[self.key][self.dim, self.slider.value].values) self.ax.add_collection(self.scatter2d) self.save_xy = np.array([theta, z_or_phi]).T if self.params[self.key]["cbar"]: self.cbar = plt.colorbar(self.scatter2d, ax=self.ax) self.cbar.ax.set_ylabel( name_with_unit(var=self.hist_data_array[self.key], name="")) self.cbar.ax.yaxis.set_label_coords(-1.1, 0.5) self.figure2d = True else: self.scatter2d.set_offset_position("data") self.scatter2d.set_offsets( np.array([theta, z_or_phi]).T - self.save_xy) self.ax.set_xlim( [np.amin(theta) - self.size, np.amax(theta) + self.size]) self.ax.set_ylim( [np.amin(z_or_phi) - self.size, np.amax(z_or_phi) + self.size]) return def update_colors_2d(self, change): self.scatter2d.set_array( self.hist_data_array[self.key][self.dim, change["new"]].values) self.fig2d.canvas.draw_idle() return def update_nbins(self, owner): try: nbins = int(owner.value) except ValueError: print("Warning: could not convert value: {} to an " "integer.".format(owner.value)) return # self.rebin_data(nbins, from_nbins_text) self.rebin_data( np.linspace(self.minmax["tof"][0], self.minmax["tof"][1], nbins + 1)) x = self.hist_data_array[self.key].coords[self.dim].values self.bin_size.value = str(x[1] - x[0]) self.update_slider() return def update_bin_size(self, owner): try: binw = float(owner.value) except ValueError: print("Warning: could not convert value: {} to a " "float.".format(owner.value)) return self.rebin_data( np.arange(self.minmax["tof"][0], self.minmax["tof"][1], binw)) self.nbins.value = str( self.hist_data_array[self.key].shape[self.tof_dim_indx]) self.update_slider() return def update_slider(self): """ Try to replace the slider around the same position """ # Compute percentage position perc_pos = self.slider.value / self.slider.max # Compute new position nbins = int(self.nbins.value) new_pos = int(perc_pos * nbins) # Either place new upper boundary first, or change slider value first if new_pos > self.slider.max: self.slider.max = nbins self.slider.value = new_pos else: self.slider.value = new_pos self.slider.max = nbins return def change_data_array(self, change): self.key = change["new"] if self.scatter2d is not None: # Apparently, you have to set norm, clim on PatchCollection and # clim on the colorbar to get this working. Only setting norm # seems to work only on the first change. self.scatter2d.set_norm(self.params[self.key]["norm"]) self.scatter2d.set_clim(vmin=self.params[self.key]["vmin"], vmax=self.params[self.key]["vmax"]) self.cbar.set_clim(vmin=self.params[self.key]["vmin"], vmax=self.params[self.key]["vmax"]) self.update_colors({"new": self.slider.value})