-
Notifications
You must be signed in to change notification settings - Fork 263
display images, HTML... and MIME rendering #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…rash on import of complicates packages as "gonum.org/v1/plot/plotter", and other improvements
…Go interface 'image.Image'
This is great @cosmos72. Thank you so much! I have been swamped, but I'm hoping to test this soon. @SpencerPark, have you been able to test this? |
…omacro/fast/Interp.RunExpr()
…es/tree/mime-rendering to implement rendering of HTML, JSON, JPEG, PNG, SVG, LaTeX... New functions: display.PNG([]byte), display.JPEG([]byte), display.HTML(string), display.SVG(string) ...
I just updated my display_image branch (and thus this pull request) by merging and simplifying @SpencerPark contribution #110 See #17 for discussion |
From SpencerPark <spinnr95@gmail.com>: Lock writes to the sockets to fix a bug with interleaved publishes.
display.go
Outdated
// See http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.display | ||
// for a good overview of the support types. Note: This is missing _repr_markdown_ and _repr_javascript_. | ||
|
||
var globalReceipt *msgReceipt // ugly global variable. any alternative? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think restricting this to just rendering (returning a mime bundle) and pulling the actual display portion will clean this up a lot.
display.go
Outdated
mimeType: data, | ||
}, make(bundledMIMEData)) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
render2
and render3
would then return map[string]interface{}
instead of publishing.
image.go
Outdated
@@ -27,7 +27,7 @@ func publishImages(vals []interface{}, receipt msgReceipt) []interface{} { | |||
} | |||
|
|||
// publishImages sends a "display_data" broadcast message for given image. | |||
func publishImage(img image.Image, receipt msgReceipt) error { | |||
func publishImage(img image.Image, receipt *msgReceipt) error { | |||
bytes, mime, err := encodePng(img) | |||
if err != nil { | |||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions can move into the same package for rendering and ideally keep them more or less pure. This separation will be really helpful both for testing and maintaining.
if err != nil { | ||
log.Print(err) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like in f696d76 that replaces the display function with a closure that knows about the eval context removes the global. Not sure of any repercussions gomacro will have as a result but taking advantage of the more dynamic interpreter seemed like a great use for gomacro.
kernel.go
Outdated
@@ -359,7 +372,7 @@ func handleExecuteRequest(ir *interp.Interp, receipt msgReceipt) error { | |||
|
|||
if executionErr == nil { | |||
// if one or more value is image.Image, display it instead | |||
vals = publishImages(vals, receipt) | |||
vals = publishImages(vals, &receipt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If all of the functions simply render instead of display this is the place we get to take advantage of that. We need a type assertion but I think checking for a struct is fine. If we are trying to return a mimebundle we can get the rich output in the Out[_]
in addition to the display calls.
I might have reviewed at the wrong commit... anyways I like the changes but I think the separation of rendering and displaying is really important. I also had some unit tests in the works including the helpers for it which I would like to bring over here. This is such a popular feature that we should really make sure to get something stable right off the bat. |
You reviewed the right commit, don't worry. Yes, I saw you injected into the interpreter closures that kept a reference to the current msgReceipt. At least syntactically, it's much cleaner than my global variable. But the real question is: what's the lifetime of the msgReceipt passed to handleExecuteRequest() ? Closures on the msgReceipt will remain available to the interpreter even after About splitting the rendering (returning a mime bundle) from the actual display portion: I am not fond of exposing very complex API to interpreted code - that's also why I tried to simplify your implementation. The most complex function I exposed to the interpreter is In summary, I think the best approach depends on msgReceipt lifetime, i.e. whether it can be used or not after handleExecuteRequest() returns |
Good point. I believe the behavior is undefined in the spec and so we can't rely on the server to do anything with it but I don't think there is any harm in using it. In either case it might be worth setting it to
I do agree with this in most cases but the reason I am so strong on it is because of the very long running precedent that Jupyter has set with IPython's display mechanics. The display function and it's behavior should essentially give access to some print stream for richer output in the same way that returning something (an expression at the end of a cell) should. This is why the rendering/display separation is required as something can be rendered and used as the ExecuteResult instead of DisplayData. I'm fine with it being simplified to always return something like
Hopefully we can hide as much of that as possible which I propose we do with a |
Thanks, it's clearer now. I guess I should read IPython API...
plus some other utility functions that create such DisplayData objects, That makes sense. |
…layData) error' function that sends multimedia to Jupyter for displaying from the various factory functions 'func display.*(...) DisplayData' that create and return DisplayData values. Also remove the global variable globalReceipt and use a closure instead.
kernel.go
Outdated
// if one or more value is image.Image, display it instead | ||
vals = publishImages(vals, &receipt) | ||
// if one or more value is image.Image or DisplayData, display it instead | ||
vals = publishImagesAndDisplayData(vals, &receipt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the Execution Result message format which is display data plus a bit extra. This is a minor detail but if you notice the Out[_]
is missing when a value is returned because here it is being displayed rather than put in an execution result.
@cosmos72 that image is beautiful! I have the one minor comment above but that is looking really great. |
Actually I intentionally replaced all DisplayData and image.Image returned by Interp.RunExpr() with nil before sending the results to ExecutionResult, because such types are intercepted by I guess ExecutionResult supports images and multimedia too, but since it has a single data map[string]interface{} it cannot display more than one value per mime type - no way to show two PNGs, for example. Which alternative do you propose? |
Yes this was another thing Dan and I talked a lot about back when trying to design an API for this. The IPython semantics are such that when a single result is returned by the eval, that result is rendered nicely. If multiple then a string representation is returned. We opted for Thanks for putting up with all these requests :) |
You mean that you propose the following algorithm for values returned by the REPL ? "if a single non-nil value is returned by REPL (other values, if present, are nil), and such value is a DisplayData or an image.Image, it is displayed graphically. If multiple non-nil values are returned, they are displayed as string" If that's what you propose, I politely disagree, because I find it confusing for users and unnecessarily limiting. Python does not have multiple return values, so any "multiple values" returned by an expression are actually wrapped in a tuple, list, dictionary or object - IMHO in such case it makes much more sense not to extract them for graphical displaying. Go multiple return values are thus a language feature with no Python counterpart, and should be considered when designing an API. My proposal, which is implemented in the current pull request, is: "if one or more values returned by REPL is a DisplayData or an image.Image, it is displayed graphically and replaced with a placeholder (currently 'nil') in the returned values. Then the returned values are also passed as string to ExecutionResult, unless they are all nil or placeholders" This has the advantage of not special-casing the single-value results, and it can display multiple images / html / ... returned by a single expression. If a user wants to see the string representation of a DisplayData or image.Image (and I guess such need will be rare beyond debugging/studying gophernotes or the package 'image'), IMHO it's quite straightforward for him/her to come up with the idea to call fmt.Sprint(val) |
Correct, that is what I propose.
Sure the implementation is with tuples, but with syntax for building that tuple without parentheses it's really not fair to say Python doesn't have multiple return values. You can write code that assumes multiple return values without ever needing to know there was a tuple conversion in the middle. The fact that it is a tuple just makes it play nicer with Python. The part that makes this approach a bit more Go friendly is considering
I disagree with this approach. There is a message in the protocol dedicated for cell results and this is the place it needs to be used. If multiple images (for example) need to be displayed then those need to be captured in variables and displayed separately. Since images can't be displayed side-by-side anyways the implicit transposition is also (in my opinion) not very natural. The default behavior should always be a string representation. In some cases where we are sure the intent is something nicer then we can display that. If a string is a return value there is no doubt that it would be returned (in an |
…ly if it's the only non-nil result
I am not convinced, but for the sake of finding an agreement I modified the pull request as you asked: |
I don't want a disagreement to hold up this feature. You know Go very well and so if you are strong on this then I think we should bring it into gophernotes.
One last note on this last commit: if we are going with the single output approach it should be put into an On an unrelated note, thanks for all the work lately. You are bringing life back into this project and it's really great to see. |
Ah, ok. I read your comment previously about using Well, I did not want to get stuck in a discussion "A: let's do my way. B: No, let's do my way", and I also don't want to get stuck in the opposite "A: let's do your way. B: No, let's do your way" The single-result functions are almost ready, let's use them. In case, we can extend to multiple results later. |
…lay.Data, publish it with "execute_result" Jupyter message, not with "display_data"
modified to use |
merged |
show as image each expression result that implements Go interface image.Image