def save( self, fname: Optional[str] = None, sigma_clip: Optional[float] = None, render: bool = True, ): r"""Saves a rendered image of the Scene to disk. Once you have created a scene, this saves an image array to disk with an optional filename. This function calls render() to generate an image array, unless the render parameter is set to False, in which case the most recently rendered scene is used if it exists. Parameters ---------- fname: string, optional If specified, save the rendering as to the file "fname". If unspecified, it creates a default based on the dataset filename. The file format is inferred from the filename's suffix. Supported formats depend on which version of matplotlib is installed. Default: None sigma_clip: float, optional Image values greater than this number times the standard deviation plus the mean of the image will be clipped before saving. Useful for enhancing images as it gets rid of rare high pixel values. Default: None floor(vals > std_dev*sigma_clip + mean) render: boolean, optional If True, will always render the scene before saving. If False, will use results of previous render if it exists. Default: True Returns ------- Nothing Examples -------- >>> import yt >>> ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030") >>> sc = yt.create_scene(ds) >>> # Modify camera, sources, etc... >>> sc.save("test.png", sigma_clip=4) When saving multiple images without modifying the scene (camera, sources,etc.), render=False can be used to avoid re-rendering. This is useful for generating images at a range of sigma_clip values: >>> import yt >>> ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030") >>> sc = yt.create_scene(ds) >>> # save with different sigma clipping values >>> sc.save("raw.png") # The initial render call happens here >>> sc.save("clipped_2.png", sigma_clip=2, render=False) >>> sc.save("clipped_4.png", sigma_clip=4, render=False) """ fname = self._setup_save(fname, render) # We can render pngs natively but for other formats we defer to # matplotlib. if fname.endswith(".png"): self._last_render.write_png(fname, sigma_clip=sigma_clip) else: from matplotlib.figure import Figure shape = self._last_render.shape fig = Figure((shape[0] / 100.0, shape[1] / 100.0)) canvas = get_canvas(fig, fname) ax = fig.add_axes([0, 0, 1, 1]) ax.set_axis_off() out = self._last_render nz = out[:, :, :3][out[:, :, :3].nonzero()] max_val = nz.mean() + sigma_clip * nz.std() alpha = 255 * out[:, :, 3].astype("uint8") out = np.clip(out[:, :, :3] / max_val, 0.0, 1.0) * 255 out = np.concatenate([out.astype("uint8"), alpha[..., None]], axis=-1) # not sure why we need rot90, but this makes the orientation # match the png writer ax.imshow(np.rot90(out), origin="lower") canvas.print_figure(fname, dpi=100)
def save_annotated( self, fname: Optional[str] = None, label_fmt: Optional[str] = None, text_annotate=None, dpi: int = 100, sigma_clip: Optional[float] = None, render: bool = True, tf_rect: Optional[List[float]] = None, ): r"""Saves the most recently rendered image of the Scene to disk, including an image of the transfer function and and user-defined text. Once you have created a scene and rendered that scene to an image array, this saves that image array to disk with an optional filename. If an image has not yet been rendered for the current scene object, it forces one and writes it out. Parameters ---------- fname: string, optional If specified, save the rendering as a bitmap to the file "fname". If unspecified, it creates a default based on the dataset filename. Default: None sigma_clip: float, optional Image values greater than this number times the standard deviation plus the mean of the image will be clipped before saving. Useful for enhancing images as it gets rid of rare high pixel values. Default: None floor(vals > std_dev*sigma_clip + mean) dpi: integer, optional By default, the resulting image will be the same size as the camera parameters. If you supply a dpi, then the image will be scaled accordingly (from the default 100 dpi) label_fmt : str, optional A format specifier (e.g., label_fmt="%.2g") to use in formatting the data values that label the transfer function colorbar. text_annotate : list of iterables Any text that you wish to display on the image. This should be an list containing a tuple of coordinates (in normalized figure coordinates), the text to display, and, optionally, a dictionary of keyword/value pairs to pass through to the matplotlib text() function. Each item in the main list is a separate string to write. render: boolean, optional If True, will render the scene before saving. If False, will use results of previous render if it exists. Default: True tf_rect : sequence of floats, optional A rectangle that defines the location of the transfer function legend. This is only used for the case where there are multiple volume sources with associated transfer functions. tf_rect is of the form [x0, y0, width, height], in figure coordinates. Returns ------- Nothing Examples -------- >>> sc.save_annotated( ... "fig.png", ... text_annotate=[ ... [ ... (0.05, 0.05), ... f"t = {ds.current_time.d}", ... dict(horizontalalignment="left"), ... ], ... [ ... (0.5, 0.95), ... "simulation title", ... dict(color="y", fontsize="24", horizontalalignment="center"), ... ], ... ], ... ) """ fname = self._setup_save(fname, render) ax = self._show_mpl(self._last_render.swapaxes(0, 1), sigma_clip=sigma_clip, dpi=dpi) # number of transfer functions? num_trans_func = 0 for rs in self._get_render_sources(): if hasattr(rs, "transfer_function"): num_trans_func += 1 # which transfer function? if num_trans_func == 1: rs = self._get_render_sources()[0] tf = rs.transfer_function label = rs.data_source.ds._get_field_info(rs.field).get_label() self._annotate(ax.axes, tf, rs, label=label, label_fmt=label_fmt) else: # set the origin and width and height of the colorbar region if tf_rect is None: tf_rect = [0.80, 0.12, 0.12, 0.9] cbx0, cby0, cbw, cbh = tf_rect cbh_each = cbh / num_trans_func for i, rs in enumerate(self._get_render_sources()): ax = self._render_figure.add_axes( [cbx0, cby0 + i * cbh_each, 0.8 * cbw, 0.8 * cbh_each]) try: tf = rs.transfer_function except AttributeError: pass else: label = rs.data_source.ds._get_field_info( rs.field).get_label() self._annotate_multi(ax, tf, rs, label=label, label_fmt=label_fmt) # any text? if text_annotate is not None: f = self._render_figure for t in text_annotate: xy = t[0] string = t[1] if len(t) == 3: opt = t[2] else: opt = dict() # sane default if "color" not in opt: opt["color"] = "w" ax.axes.text(xy[0], xy[1], string, transform=f.transFigure, **opt) self._render_figure.canvas = get_canvas(self._render_figure, fname) self._render_figure.tight_layout() self._render_figure.savefig(fname, facecolor="black", pad_inches=0)
def save_annotated( self, fname=None, label_fmt=None, text_annotate=None, dpi=100, sigma_clip=None, render=True, ): r"""Saves the most recently rendered image of the Scene to disk, including an image of the transfer function and and user-defined text. Once you have created a scene and rendered that scene to an image array, this saves that image array to disk with an optional filename. If an image has not yet been rendered for the current scene object, it forces one and writes it out. Parameters ---------- fname: string, optional If specified, save the rendering as a bitmap to the file "fname". If unspecified, it creates a default based on the dataset filename. Default: None sigma_clip: float, optional Image values greater than this number times the standard deviation plus the mean of the image will be clipped before saving. Useful for enhancing images as it gets rid of rare high pixel values. Default: None floor(vals > std_dev*sigma_clip + mean) dpi: integer, optional By default, the resulting image will be the same size as the camera parameters. If you supply a dpi, then the image will be scaled accordingly (from the default 100 dpi) label_fmt : str, optional A format specifier (e.g., label_fmt="%.2g") to use in formatting the data values that label the transfer function colorbar. text_annotate : list of iterables Any text that you wish to display on the image. This should be an list containing a tuple of coordinates (in normalized figure coordinates), the text to display, and, optionally, a dictionary of keyword/value pairs to pass through to the matplotlib text() function. Each item in the main list is a separate string to write. render: boolean, optional If True, will render the scene before saving. If False, will use results of previous render if it exists. Default: True Returns ------- Nothing Examples -------- >>> sc.save_annotated("fig.png", ... text_annotate=[[(0.05, 0.05), ... "t = {}".format(ds.current_time.d), ... dict(horizontalalignment="left")], ... [(0.5,0.95), ... "simulation title", ... dict(color="y", fontsize="24", ... horizontalalignment="center")]]) """ fname = self._setup_save(fname, render) # which transfer function? rs = self._get_render_sources()[0] tf = rs.transfer_function label = rs.data_source.ds._get_field_info(rs.field).get_label() ax = self._show_mpl(self._last_render.swapaxes(0, 1), sigma_clip=sigma_clip, dpi=dpi) self._annotate(ax.axes, tf, rs, label=label, label_fmt=label_fmt) # any text? if text_annotate is not None: f = self._render_figure for t in text_annotate: xy = t[0] string = t[1] if len(t) == 3: opt = t[2] else: opt = dict() # sane default if "color" not in opt: opt["color"] = "w" ax.axes.text(xy[0], xy[1], string, transform=f.transFigure, **opt) self._render_figure.canvas = get_canvas(self._render_figure, fname) self._render_figure.tight_layout() self._render_figure.savefig(fname, facecolor="black", pad_inches=0)