Skip to content

Commit e7a9663

Browse files
committed
display images: show as image each expression result that implements Go interface 'image.Image'
1 parent c702f1b commit e7a9663

File tree

4 files changed

+83
-12
lines changed

4 files changed

+83
-12
lines changed

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,16 +188,10 @@ $ docker run -it -p 8888:8888 -v /path/to/local/notebooks:/path/to/notebooks/in/
188188
gophernotes uses [gomacro](https://github.com/cosmos72/gomacro) under the hood to evaluate Go code interactively. You can evaluate most any Go code with gomacro, but there are some limitation, which are discussed in further detail [here](https://github.com/cosmos72/gomacro#current-status). Most noteably, gophernotes does NOT support:
189189
190190
- third party packages when running natively on Mac and Windows - This is a current limitation of the Go `plugin` package.
191-
- unexported struct fields
192-
- interfaces - They can be declared, but nothing more: there is no way to implement them or call their methods
193-
- extracting methods from types - For example time.Duration.String should return a func(time.Duration) string but currently gives an error. Instead extracting methods from objects is supported: time.Duration(1s).String correctly returns a func() string
194-
- goto
195-
- named return values
196-
- named imports like:
197-
198-
```
199-
import tf "github.com/tensorflow/tensorflow/tensorflow/go"
200-
```
191+
- unexported struct fields are emulated
192+
- interpreted interfaces - They can be declared, but nothing more: there is no way to implement them or call their methods
193+
Note: interfaces imported from standard library or third party packages are compiled, and fully functional.
194+
- goto is only partially implemented
201195
202196
## Troubleshooting
203197

image.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"image"
7+
"image/png"
8+
"log"
9+
)
10+
11+
// publishImages sends a "display_data" broadcast message for each image.Image found in vals,
12+
// then replaces it with a placeholder string equal to reflect.TypeOf(val).String()
13+
// to avoid overloading the front-end with huge amounts of output text:
14+
// fmt.Sprint(val) is often very large for an image
15+
func publishImages(vals []interface{}, receipt msgReceipt) []interface{} {
16+
for i, val := range vals {
17+
if img, ok := val.(image.Image); ok {
18+
err := publishImage(img, receipt)
19+
if err != nil {
20+
log.Printf("Error publishing image: %v\n", err)
21+
} else {
22+
vals[i] = fmt.Sprintf("%T", val)
23+
}
24+
}
25+
}
26+
return vals
27+
}
28+
29+
// publishImages sends a "display_data" broadcast message for given image.
30+
func publishImage(img image.Image, receipt msgReceipt) error {
31+
bytes, mime, err := encodePng(img)
32+
if err != nil {
33+
return err
34+
}
35+
data := bundledMIMEData{
36+
mime: bytes,
37+
}
38+
metadata := bundledMIMEData{
39+
mime: imageMetadata(img),
40+
}
41+
return receipt.PublishDisplayData(data, metadata)
42+
}
43+
44+
// encodePng converts an image.Image to PNG []byte
45+
func encodePng(img image.Image) (data []byte, mime string, err error) {
46+
var buf bytes.Buffer
47+
err = png.Encode(&buf, img)
48+
if err != nil {
49+
return nil, "", err
50+
}
51+
return buf.Bytes(), "image/png", nil
52+
}
53+
54+
// imageMetadata returns image size, represented as bundledMIMEData{"width": width, "height": height}
55+
func imageMetadata(img image.Image) bundledMIMEData {
56+
rect := img.Bounds()
57+
return bundledMIMEData{
58+
"width": rect.Dx(),
59+
"height": rect.Dy(),
60+
}
61+
}

kernel.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,6 @@ func handleExecuteRequest(ir *interp.Interp, receipt msgReceipt) error {
347347

348348
vals, executionErr := doEval(ir, code)
349349

350-
//TODO if value is a certain type like image then display it instead
351-
352350
// Close and restore the streams.
353351
wOut.Close()
354352
os.Stdout = oldStdout
@@ -360,6 +358,9 @@ func handleExecuteRequest(ir *interp.Interp, receipt msgReceipt) error {
360358
writersWG.Wait()
361359

362360
if executionErr == nil {
361+
// if one or more value is image.Image, display it instead
362+
vals = publishImages(vals, receipt)
363+
363364
content["status"] = "ok"
364365
content["user_expressions"] = make(map[string]string)
365366

messages.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,21 @@ func (receipt *msgReceipt) PublishExecutionError(err string, trace []string) err
269269
)
270270
}
271271

272+
// PublishDisplayData publishes a single image.
273+
func (receipt *msgReceipt) PublishDisplayData(data, metadata bundledMIMEData) error {
274+
return receipt.Publish("display_data",
275+
struct {
276+
Data bundledMIMEData `json:"data"`
277+
Metadata bundledMIMEData `json:"metadata"`
278+
Transient bundledMIMEData `json:"transient"`
279+
}{
280+
Data: data,
281+
Metadata: metadata,
282+
Transient: make(bundledMIMEData),
283+
},
284+
)
285+
}
286+
272287
const (
273288
// StreamStdout defines the stream name for standard out on the front-end. It
274289
// is used in `PublishWriteStream` to specify the stream to write to.

0 commit comments

Comments
 (0)