Calysto / matlab_kernel

Jupyter Kernel for Matlab
Other
469 stars 76 forks source link

flush plots before script finishes #133

Open kb1ooo opened 4 years ago

kb1ooo commented 4 years ago

Is it possible to push/flush inline plots to cell output while a script is running. E.g. if you were to make plots at every iteration of a loop, they won't render until the loop is completed. Is there a way (without doing native plots) to flush plots? This is pretty important for looking at progress of iterative optimization runs, e.g. deep learning training.

Thanks

blink1073 commented 4 years ago

In theory, we could inject a matlab function that prints figures to a known location and then have a python thread that looks for changes in that directory, then triggers an image display on the kernel. You would then be able to call that function from within your loop. If someone were willing to take that effort up, I could offer pointers.

kb1ooo commented 4 years ago

@blink1073 ok, interesting. Do you think it would be possible to push updates to the same plot as well? I might be willing to take this on depending on the effort involved.

kb1ooo commented 4 years ago

@blink1073 regarding your suggestion, would it not be possible to do something synchronous? As long as you are injecting a custom "plotting" matlab function, is there no way to invoke an image display directly after that call?

kb1ooo commented 4 years ago

@blink1073 what about the following (inspired by a great hack a web based Terminal program called wetty uses, which allows you to download files via writing to the terminal in between escape sequences). So, what if you inject a custom matlab function that prints the figure somewhere (like you suggested) and then writes the filename to stdout in between some escape sequences. Modify your _PseudoStream class to intercept the escape sequence, pull the filename out and call Display on the image. Otherwise, the _PseudoStream prints to the stream as it does now.

dsblank commented 4 years ago

I had a similar idea that I listed either here, or in the matlab_kernel's parent. I was always hoping that we could do it in some kind of "standard" way, so that it was as hacky as it could be :) Let me see if I can find some notes...

kb1ooo commented 4 years ago

@blink1073 @dsblank I made a first pass at this on my fork based on the approach I described above. Please check out my fork if you would like, and let me know what you think. I heavily commented the kernel code, but I'll repeat some of it here. I wrote a variation of the _PseudoStream class called _PseudoStreamFig which looks for start '\033[5i' and end '\033[4i' escape sequence delimiters in the stdout stream. Anything not between those delimiters it passes to the stdout writer just as the original _PseudoStream class did. In between the delimiters, it looks for a URI of the form fig://urlencoded_filename.png/gcf?id=12&rm=1, which communicates the figure filename, the gcf id (which I would like to use in the future to update figure if possible through the metakernel), and then rm=1 or rm=0 to signify whether the file should be removed after rendering.

So, to communicate that a fig should be rendered in jupyter you call a function from matlab which looks something like:

function jupFigRender(fig)

    if nargin == 0
        fig = gcf;
    end

    gcfid = num2str(get(fig,'Number'));
    dpi = 96;
    filename = [tempname '.png'];
    print(fig,'-dpng',sprintf('-r%i', dpi), filename);
    drawnow('update'); % flushes stdout buffer
    fprintf(1,'%s[5ifig://%s/gcf?id=%s&rm=1%s[4i',...
        27,urlencode(filename),gcfid,27)
    drawnow('update'); % flushes stdout buffer
end

If you have suggestions on how to elegantly make something like this function a no-op when you aren't running code from jupyter, that would be helpful. Also, I'd really like to update this to be able to assign a display id and then also use update_display from ipython. Is this possible through the metakernel interface?

blink1073 commented 4 years ago

Looks good so far!

kb1ooo commented 4 years ago

Looks good so far!

* You could [set](http://www.ece.northwestern.edu/local-apps/matlabhelp/techdoc/ref/set.html) a property on `0` during kernel startup and check for that property in the script.

Ok cool, so you mean add something like 'UserData': 'jupyter' to the default properties dictionary in handle_plot_settings?

* We don't currently support `update_display`, but here's what you'd need to do:

  * Update [`Display`](https://github.com/Calysto/metakernel/blob/master/metakernel/_metakernel.py#L625) to include [`transient`](https://jupyter-client.readthedocs.io/en/stable/messaging.html#display-data)
  * Add an `UpdateDisplay` method to that class that generates an `UpdateDisplay` [message](https://jupyter-client.readthedocs.io/en/stable/messaging.html#update-display-data).

Great, thanks.

blink1073 commented 4 years ago

Ok cool, so you mean add something like 'UserData': 'jupyter' to the default properties dictionary in handle_plot_settings?

Yeah, that sounds right.