Is there a way to save the output of a cell?

Basically I want to make a plot from a cell, “save” the plot, tweak some code within the cell, remake the plot, and compare the two versions, all from within Jupyterlab. Is this possible? Or perhaps with an extension?

“Create New View From Output” is almost exactly what I want, except that the new view always updates to the most recent output of that cell. I really just want a snapshot of the output. Otherwise the behavior where the output becomes a new tab which I can place wherever I want is perfect.

Any suggestions? Thanks.

1 Like

Maybe this project will help you accomplish this:

It seems pretty cool!

6 Likes

That is super cool, I did not know about it! It basically works, even with Julia (which wasn’t clear from the docs if its Python only), and I’m able to simultaneously view old versions of a plot like I needed. One downside is that I can’t put each version into its own tab to conveniently flip between them, plus its definiely got some rough edges being alpha, but overall I’m pretty impressed!

1 Like

If you are using IPython, you could try the %%capture magic to get the plot data:

https://ipython.readthedocs.io/en/stable/interactive/magics.html#cellmagic-capture

3 Likes

oh and this project out of nteract is also pretty nifty, and might do what you like: https://github.com/nteract/scrapbook (I believe @MSeal may be working on this?)

2 Likes

I need to check out @choldgraf’s awesome suggestions more because I think I could find uses for both of those. However, if you are looking for somethings more hacky I can offer these suggestions…

When you say ‘compare’ you mean visually just look at the plots yourself? Can you duplicate the notebook, make your changes, and then place it side-by-side next to the original? You can launch the duplicate in the same kernel if that helps save time. To attach the new notebook to the old kernel, you click on the kernel indicator in the upper right side of JupyterLab and select the kernel from the other notebook session.

Alternatively, add saving the plot as an image, if you can. (Otherwise you can most likely use %%capture magic, as suggested, and substitute in show() the way I use display image with the image handling suggestions that follow.) With the image you can do all sorts of things with IPython’s display ability. I have made notebooks that display a lot of images and then just use the new view ability in JupyterLab to place them side-by-side at different points for review. And you can add in html to tag different ones. Or you can see from a notebook in this repo where I automate switching between viewing different output to facilitate scanning for an image that looks striking among a lot of combinations. You can see it illustrated in a sped up manner in that gif that plays. The bottom two cells in the notebook here might give you some ideas to get closer to what you need. To get to the specific notebook actively, launch the binder and then click on ’ Demo of Sampling Various Combinations of Applying A Color Palette to a Complex’ in the list and run the notebook that comes up. If you do run the notebook to better get a sense what I am trying to describe, I’d suggest changing the fourth code cell to shuffles_to_do = 5 and the line that starts cmd.png in the fifth cell to cmd.png("img_{}.png".format(x), width=50, height=50, dpi=10, ray=0, quiet=0) so you don’t need to wait forever for fancy images you don’t care about.

It sounds somewhat like you want to clone the notebook and rerun with different parameters, or perhaps have two cells you wish to compare? The scrapbook project lets you save the results (plot or primitive data) and recall it programmatically, so if you were versioning your notebooks with different saves you could do:

sb.glue("my-plot", plot_obj, display=True)

in the notebook, then in a downstream notebook:

nb = sb.read_notebook('prior_notebook.ipynb')
nb.scraps["my-plot"]

Or if you wanted to re-render the scrap to compare visually:

nb = sb.read_notebook('prior_notebook.ipynb')
nb.reglue("my-plot")

The project hasn’t had recent updates, but that was somewhat intentional as I was working on other jupyter repos. I’m going to be actively contributing some more features now that some of my other open source tasks are done.

2 Likes

Thanks in advance - this project looks very promising!
Just a follow up - I’ve been trying to sb.glue a dataframe and also a df.plot()

sb.glue(“non_json”, df, ‘arrow’)
running into the following error
ScrapbookMissingEncoder: no encoder found for “none” data type:

Trying to glue a plot - df.plot(kind=‘bar’,x=‘names’,y=‘ages’) - which displays well in the notebook)
sb.glue(‘food_plot’,food_plot, display=True)

ScrapbookDataException: Scrap (name=food_plot) contents do not conform to required type structures: <matplotlib.axes._subplots.AxesSubplot object at 0x7f4142d81a00> is not of type ‘object’, ‘array’, ‘boolean’, ‘string’, ‘number’, ‘integer’

Failed validating ‘type’ in schema[‘properties’][‘data’]:
{’$id’: ‘#/properties/data’,
‘description’: 'The raw data in JSON format associated with this ’
‘Scrap’,
‘examples’: [‘data’],
‘title’: ‘scrap data’,
‘type’: [‘object’, ‘array’, ‘boolean’, ‘string’, ‘number’, ‘integer’]}

On instance[‘data’]:
<matplotlib.axes._subplots.AxesSubplot object at 0x7f4142d81a00>

Yeah the sb.glue(“non_json”, df, ‘arrow’) is a pending feature to support dataframes directly. I’ll be trying to get that added and released by the end of the month. Until then you have to convert the dataframe to json and back.

For the second error, try setting encoder = "display" as well to avoid it trying to save the plot object as a data field in addition to the display field.

1 Like

I have a similar use case: I want to convert a jupyter-style markdown file, representing the output of a cell, to pdf. JupyterLab displays it just fine in the notebook or a tab opened by “Create new view for output”. The output includes HTML to insert figures from external files, and latex. I looked at both of the packages suggested in this question, didn’t find what I’m looking for.
My purpose is to export nicely-formatted analysis to documents. It looks like nbformat must have code to do this, but I don’t see how to implement my example.

This is how I found to do it. To get a PDF it seems necessary to let nbformat generate an HTML document.

import IPython.display as display
from nbconvert.exporters import HTMLExporter

def to_html(md_text, filename):
    """ Convert a string of text in Jupyter-markdown format to an HTML file
    """
    
    # create a markdown notebook node object
    class Dict(dict):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
            self.update(kwargs)
    nb = Dict(
            cells= [Dict(cell_type="markdown", 
                         metadata={}, 
                         source=md_text,
                        )
                   ],
            metadata={},
            nbformat=4,
            nbformat_minor=4,
            )
    # now pass it to nbformat to write as an HtML file
    exporter = HTMLExporter()
    output, resources = exporter.from_notebook_node(nb) 
    with open(filename, 'wb') as f:
        f.write(output.encode('utf8'))

Surprisingly, the HTML file has at least 271KB.

Update from 2022: there is now jupyter_capture_output that comes with cell magic like %capture_code and %capture_img to capture code and images.
Another option: plywood-gallery captures code-image pairs at each Jupyter cell execution.
These code-image pairs can be accessed on an HTML page, which logs all new generated plots in real-time.
This is how the HTML page looks like after running a single Jupyter cell three times:
(Images have to be clicked to reveal their code)
ezgif-4-4d80fa9fed

2 Likes

Jupyter Lab Extension way of doing this, something like …

import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin,
} from '@jupyterlab/application';

import {
  NotebookActions,
} from '@jupyterlab/notebook';

import {
  CodeCellModel,
  isCodeCellModel
} from '@jupyterlab/cells';

import {
  ILatexTypesetter
} from '@jupyterlab/rendermime';

/**
 * Initialization data for the icode extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
  id: 'icode:plugin',
  description: 'A jupyter lab/notebook front-end extension for manipulating code cell source and output',
  autoStart: true,
  requires: [ILatexTypesetter],
  activate: (app: JupyterFrontEnd, latexTypesetter: ILatexTypesetter) => {
    console.log('JupyterLab extension icode is activated!');
    NotebookActions.executed.connect((_, args) => {
      
      const { cell } = args;
      const { success } = args;

      // get type of cell {code, markdown}
      let type = cell.model.type;
      console.log(type);

      // store boolean of whether cell model type is code
      let is_code_cell_model_bool = isCodeCellModel(cell.model);
      console.log(is_code_cell_model_bool);

      // check if cell model type is code
      if (is_code_cell_model_bool) {

        if (success) {

          // if code cell, cast cell model to CodeCellModel for getting outputs
          let codeCell = cell;
          let codeCellModel = <CodeCellModel>codeCell.model;
          let outputs = codeCellModel.sharedModel.outputs;
          // console.log(outputs);

          // iterate over outputs array of objects
          for (let i = 0; i < outputs.length; i++) {
            
            // console.log(outputs[i]);
            let output_text = outputs[i]['text'];
            let output_data = outputs[i]['data'];

            // check if output_text is defined
            if (output_text) {

              // do something with output_text
              console.log(output_text);
              
            }
            
            // check if output_data is defined
            if (output_data) {

              // do something with output_data
              console.log(output_data);

              // output_data is an object
              // console.log(typeof(output_data));

              // let entries = Object.entries(output_data);
              // console.log(entries);

              // let keys = Object.keys(output_data);
              // console.log(keys);

              let values = Object.values(output_data);
              console.log(values);

              // get result from values array
              let result = values[0];
              console.log(result);

              // codeCellModel.sharedModel.setOutputs(Text,'1');
              codeCellModel.sharedModel.setOutputs;
              
            }

          }
          
        }

      }

    });
  }
};

export default plugin;
1 Like