Changing image smoothing option

Hi,

I’m hoping someone can help me as I’m not technically savvy enough to do it myself. I want to turn on the smoothing that occurs here… i.e I don’t want to see the individual pixels but rather the interpolated version of the image when zoomed in (the image that was “wrong”)

I can see there’s options for this within canvas.ts I believe but I don’t know how I’d go about setting it to the right value within the standard python code itself. If anyone can point me in the right direction that would be great. Currently running my code as per here… image.py — Bokeh 2.4.2 Documentation

Cheers
IG

I don’t know how I’d go about setting it to the right value within the standard python code itself.

There is no way to set this from Python code. It is only accessible directly from JavaScript, e.g. via setting ctx.setImageSmoothingEnabled(True) directly on the underlying HTML Canvas DOM element. However, even that would not really help, as the various image glyphs always force image smoothing off before drawing:

const old_smoothing = ctx.getImageSmoothingEnabled()
ctx.setImageSmoothingEnabled(false)

// drawing code here

ctx.setImageSmoothingEnabled(old_smoothing)

There is no easy path to enable smoothing on images currently, I am afraid. I believe the only possible route would be to create a custom extension subclass of the image glyph that does not disable smoothing automatically.

Otherwise all I can suggest is to make a feature request to make this configurable somehow.

I am trying to make a PR which sets ctx.setImageSmoothingEnabled(this.model.smoothing) in bokeh/image.ts at e8b22ef161bae542ca06fe77cb12129ca7e3d870 · bokeh/bokeh · GitHub
This being my first dive into the bokeh codebase, my plan/strategy was pretty much to find places in the code base where global_alpha is defined and define smoothing similarly these places.
I have found the following places:

$ grep -ri "global_alpha" .
./bokeh/models/glyphs.py:    _args = ('image', 'x', 'y', 'dw', 'dh', 'global_alpha', 'dilate')
./bokeh/models/glyphs.py:    global_alpha = Float(1.0, help="""
./bokeh/models/glyphs.py:    _args = ('image', 'x', 'y', 'dw', 'dh', 'global_alpha', 'dilate')
./bokeh/models/glyphs.py:    global_alpha = Float(1.0, help="""
./bokeh/models/glyphs.py:    _args = ('url', 'x', 'y', 'w', 'h', 'angle', 'global_alpha', 'dilate')
./bokeh/models/glyphs.py:    global_alpha = Float(1.0, help="""
./bokeh/plotting/helpers.py:        # Assign global_alpha from alpha if glyph type is an image
./bokeh/plotting/helpers.py:            kwargs['global_alpha'] = kwargs['alpha']
./bokehjs/src/lib/models/glyphs/image.ts:    this.connect(this.model.properties.global_alpha.change, () => this.renderer.request_render())
./bokehjs/src/lib/models/glyphs/image.ts:    ctx.globalAlpha = this.model.global_alpha
./bokehjs/src/lib/models/glyphs/image.ts:    global_alpha: p.Property<number>
./bokehjs/src/lib/models/glyphs/image_base.ts:    global_alpha: p.Property<number>
./bokehjs/src/lib/models/glyphs/image_base.ts:      global_alpha: [ p.Number,    1.0   ],
./bokehjs/src/lib/models/glyphs/image_rgba.ts:    this.connect(this.model.properties.global_alpha.change, () => this.renderer.request_render())
./bokehjs/src/lib/models/glyphs/image_rgba.ts:    ctx.globalAlpha = this.model.global_alpha
./bokehjs/src/lib/models/glyphs/image_rgba.ts:    global_alpha: p.Property<number>
./bokehjs/src/lib/models/glyphs/image_url.ts:    this.connect(this.model.properties.global_alpha.change, () => this.renderer.request_render())
./bokehjs/src/lib/models/glyphs/image_url.ts:    ctx.globalAlpha = this.model.global_alpha
./bokehjs/src/lib/models/glyphs/image_url.ts:    global_alpha: p.Property<number>
./bokehjs/src/lib/models/glyphs/image_url.ts:      global_alpha:   [ p.Number,    1.0        ],
./bokehjs/test/models/glyphs/image_url.ts:      it("should have global_alpha=1.0", () => {
./bokehjs/test/models/glyphs/image_url.ts:        expect(image_url.global_alpha).to.be.equal(1.0)
./examples/models/file/image_url.py:image1 = ImageURL(url="url", x="x1", y="y1", w="w1", h="h1", anchor="center", global_alpha=0.2)
./tests/unit/bokeh/models/test_glyphs.py:        "global_alpha",
./tests/unit/bokeh/models/test_glyphs.py:        "global_alpha",
./tests/unit/bokeh/models/test_glyphs.py:    assert glyph.global_alpha == 1.0
./tests/unit/bokeh/models/test_glyphs.py:        "global_alpha",

Will this work at all or is it just to naive and will not work at all?

@Bokeh_coder it’s definitely possible to add a new property for this. Here are roughly all the steps:

  • Define a property on the Python side for Image and ImageRGBA. This will be a few new lines for each class, along the lines of:
    smoothing = Bool(default=False, help="""
    Some help string for the docs
    """)
    
  • Add properties and implementation to use them on the BokehJS side for Image and ImageRGBA this will be very similar the the JS code for global_alpha. This gist of it is: The value will read off the Bokeh property and set the corresponding property on the HTML canvas whenever there is a render.

Since this task will require modifying BokehJS, the first step is to follow the Getting Set Up chapter in the Dev Guide.

Really pleased you are taking a look at this, I figured it was probably too niche to put in as a feature request. One question, and I’m sure the answer is no, but is it at all possible to have the option for only applying smoothing in one direction (even more niche)? I presume it’s unlikely but don’t ask don’t get.
Cheers

I was mistaken that this required an enum for the policy, it’s just a bool flag. I have simplified the steps above accordingly.

@sirspoon AFAIK HTML canvas only supports smoothing “on” and “off” If you wanted e.g. directional smoothing I think you would need to do that yourself with scikit-image, etc. before passing to Bokeh.

I thought it might not be possible. No worries. I’m actually already using scipy to do some interpolation when I couldn’t get the smoothing to work previously but the problem is it obviously increases the resultant filesize significantly. Having it smooth the image itself when it’s displaying it rather than increasing the data points means it should be much smaller filesize/faster. One workaround for smoothing in one dimension only might be for me to display lots of images next to eachother rather than one on its own… might slow it down though but I’ll test it. Thanks again.

I’ll be honest I am a but confused on this point. If smoothing is done by the canvas, the image size does not increase. I assume it just performs some sort of neighbor average convolution on the image. So I would imagine, if doing things “by hand” that the same would hold true, and in particular that the image size would not change (perhaps modulo some small boundary effects)

Sorry, maybe I wasn’t being clear- I couldn’t get the smoothing of the column-datasource based image to work previously as it was well beyond my skill level - because of this it looked blocky when zoomed in. So my workaround was to run an interpolation on the input dataset to make it appear smoother in one direction. The number of data points went up so the resultant html file was larger. If the alternate option of the HTML canvas doing it itself will in the future be incorporated then the interpolation is no longer needed. Bit clearer?

Ah, yes if you are upping the resolution to handle more zoom levels that makes sense. I had assumed you were looking only at a single zoom level. It occurs to me this might also be something that DataShader could help with, by recomputing things based on zoom level. (If things can be in a notebook or a bokeh server app). That’s getting off-topic for this post, though.

I’m a little confused if I wanna apply something like this Interpolations for imshow/matshow — Matplotlib 3.1.2 documentation in an image, is not available way with only image.py in bokeh. Any clue my friends?