Skip to content

Commit

Permalink
Handle varying window aspect ratios more carefully
Browse files Browse the repository at this point in the history
  • Loading branch information
apontzen committed Sep 2, 2023
1 parent 1ac98e1 commit fbcac7c
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 46 deletions.
11 changes: 9 additions & 2 deletions src/topsy/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@
class VisualizerCanvas(WgpuCanvas):
def __init__(self, *args, **kwargs):
self._visualizer : Visualizer = kwargs.pop("visualizer")
super().__init__(*args, **kwargs)

self._last_x = 0
self._last_y = 0
# The below are dummy values that will be updated by the initial resize event
self.width_physical, self.height_physical = 640,480
self.pixel_ratio = 1

super().__init__(*args, **kwargs)

def handle_event(self, event):
if event['event_type']=='pointer_move':
Expand Down Expand Up @@ -54,4 +59,6 @@ def mouse_wheel(self, delta_x, delta_y):
self._visualizer.scale*=np.exp(delta_y/1000)

def resize(self, width, height, pixel_ratio=1):
pass
self.width_physical = width*pixel_ratio
self.height_physical = height*pixel_ratio
self.pixel_ratio = pixel_ratio
19 changes: 15 additions & 4 deletions src/topsy/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@


class ColorbarOverlay(Overlay):
def __init__(self, visualizer, vmin, vmax, colormap, label, *, dpi=200, **kwargs):
self.dpi = dpi
def __init__(self, visualizer, vmin, vmax, colormap, label, *, dpi_logical=72, **kwargs):
self.dpi_logical = dpi_logical
self.kwargs = kwargs
self._aspect_ratio = 0.15
self._aspect_ratio = 0.2
self.vmin = vmin
self.vmax = vmax
self.colormap = colormap
self.label = label
self._last_width = None
self._last_height = None

super().__init__(visualizer)

Expand All @@ -23,9 +25,18 @@ def get_clipspace_coordinates(self, pixel_width, pixel_height):
height = 2.0
width = 2.0*pixel_height*im.shape[1]/im.shape[0]/pixel_width
x,y = 1.0-width,-1.0
if self._last_width!=pixel_width or self._last_height!=pixel_height:
# contents is the wrong size
self.update()
self._last_width = pixel_width
self._last_height = pixel_height
return x, y, width, height
def render_contents(self):
fig = plt.figure(figsize=(10 * self._aspect_ratio, 10), dpi=200,
dpi_physical = self.dpi_logical*self._visualizer.canvas.pixel_ratio

fig = plt.figure(figsize=(self._visualizer.canvas.height_physical * self._aspect_ratio/dpi_physical,
self._visualizer.canvas.height_physical/dpi_physical),
dpi=dpi_physical,
facecolor=(1.0, 1.0, 1.0, 0.5))

cmap = matplotlib.colormaps[self.colormap]
Expand Down
30 changes: 18 additions & 12 deletions src/topsy/colormap.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _setup_texture(self, num_points=config.COLORMAP_NUM_SAMPLES):
)

def _setup_render_pipeline(self):
self._vmin_vmax_buffer = self._device.create_buffer(size =4 * 2,
self._parameter_buffer = self._device.create_buffer(size =4 * 3,
usage=wgpu.BufferUsage.UNIFORM | wgpu.BufferUsage.COPY_DST)

self._bind_group_layout = \
Expand Down Expand Up @@ -112,7 +112,7 @@ def _setup_render_pipeline(self):
},
{
"binding": 4,
"visibility": wgpu.ShaderStage.FRAGMENT,
"visibility": wgpu.ShaderStage.FRAGMENT | wgpu.ShaderStage.VERTEX,
"buffer": {"type": wgpu.BufferBindingType.uniform}
}
]
Expand All @@ -139,9 +139,9 @@ def _setup_render_pipeline(self):
"resource": self._input_interpolation,
},
{"binding": 4,
"resource": {"buffer": self._vmin_vmax_buffer,
"resource": {"buffer": self._parameter_buffer,
"offset": 0,
"size": self._vmin_vmax_buffer.size}
"size": self._parameter_buffer.size}
}
]
)
Expand Down Expand Up @@ -192,6 +192,8 @@ def _setup_render_pipeline(self):

def encode_render_pass(self, command_encoder):
display_texture = self._visualizer.context.get_current_texture()

self._update_parameter_buffer(display_texture.size[0], display_texture.size[1])
colormap_render_pass = command_encoder.begin_render_pass(
color_attachments=[
{
Expand Down Expand Up @@ -238,13 +240,17 @@ def set_vmin_vmax(self):
logger.warning("Press 'r' in the window to try again")
self.vmin, self.vmax = 0.0, 1.0

self._update_vmin_vmax_buffer()

def _update_parameter_buffer(self, width, height):
parameter_dtype = [("vmin", np.float32, (1,)),
("vmax", np.float32, (1,)),
("window_aspect_ratio", np.float32, (1,))]

parameters = np.zeros((), dtype=parameter_dtype)
parameters["vmin"] = self.vmin
parameters["vmax"] = self.vmax

self._visualizer.context.get_current_texture()

def _update_vmin_vmax_buffer(self):
vmin_vmax_dtype = [("vmin", np.float32, (1,)),
("vmax", np.float32, (1,))]
vmin_vmax = np.zeros((), dtype=vmin_vmax_dtype)
vmin_vmax["vmin"] = self.vmin
vmin_vmax["vmax"] = self.vmax
self._device.queue.write_buffer(self._vmin_vmax_buffer, 0, vmin_vmax)
parameters["window_aspect_ratio"] = float(width)/height
self._device.queue.write_buffer(self._parameter_buffer, 0, parameters)
1 change: 1 addition & 0 deletions src/topsy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

TARGET_FPS = 30 # will use downsampling to achieve this
FULL_RESOLUTION_RENDER_AFTER = 0.3 # inactivity seconds to wait before rendering without downs
STATUS_LINE_UPDATE_INTERVAL = 0.2 # seconds

COLORBAR_ASPECT_RATIO = 0.15
COLORMAP_NUM_SAMPLES = 1000
15 changes: 12 additions & 3 deletions src/topsy/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,18 @@ def _setup_texture(self):
def _setup_params_buffer(self):
self._overlay_params_buffer = self._device.create_buffer(
label="overlay_params_buffer",
size=4*4,
size=4*8,
usage=wgpu.BufferUsage.UNIFORM | wgpu.BufferUsage.COPY_DST
)

def get_texturespace_coordinates(self, width, height) -> tuple[float,float,float,float]:
return (0.0,0.0,1.0,1.0)

def _update_params_buffer(self, width, height):
x, y, w, h = self.get_clipspace_coordinates(width, height)
self._device.queue.write_buffer(self._overlay_params_buffer, 0, np.array([x,y,w,h], dtype=np.float32).tobytes())
x_t, y_t, w_t, h_t = self.get_texturespace_coordinates(width, height)
self._device.queue.write_buffer(self._overlay_params_buffer, 0,
np.array([x,y,w,h,x_t,y_t,w_t,h_t], dtype=np.float32).tobytes())
def _setup_render_pipeline(self):


Expand Down Expand Up @@ -138,7 +143,7 @@ def _setup_render_pipeline(self):
"resource": {
"buffer": self._overlay_params_buffer,
"offset": 0,
"size": 4*4,
"size": 4*8,
},
},
{
Expand Down Expand Up @@ -222,6 +227,10 @@ def get_contents(self) -> np.ndarray:
if self._contents is None:
self._contents = self.render_contents()
return self._contents

def invalidate_contents(self):
"""Mark the contents as invalid, needing a re-render"""
self._contents = None
@abstractmethod
def render_contents(self) -> np.ndarray:
"""Must return a 2D image with RGBA channels for display."""
Expand Down
10 changes: 10 additions & 0 deletions src/topsy/scalebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ def __init__(self, visualizer: Visualizer):
def encode_render_pass(self, command_encoder: wgpu.GPUCommandEncoder):
physical_scalebar_length = self._recommend_physical_scalebar_length()
self._bar.length = physical_scalebar_length / self._visualizer.scale
# note that the visualizer scale refers to a square rendering target
# however only part of this is shown in the final window if the window
# aspect ratio isn't 1:1. So we now need to correct for this effect.
# The full x extent is shown if the width is greater than the height, so
# no correction is needed then. If the height is greater than the width,
# then the x extent is scaled by the ratio of the height to the width.

if self._visualizer.canvas.width_physical < self._visualizer.canvas.height_physical:
self._bar.length *= self._visualizer.canvas.height_physical / self._visualizer.canvas.width_physical

self._update_scalebar_label(physical_scalebar_length)

self._label.encode_render_pass(command_encoder)
Expand Down
44 changes: 28 additions & 16 deletions src/topsy/shaders/colormap.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
struct ColormapParams {
vmin: f32,
vmax: f32
vmax: f32,
window_aspect_ratio: f32
};

struct VertexOutput {
Expand All @@ -13,6 +14,22 @@ struct FragmentOutput {
}


@group(0) @binding(0)
var image_texture: texture_2d<f32>;

@group(0) @binding(1)
var image_sampler: sampler;

@group(0) @binding(2)
var colormap_texture: texture_1d<f32>;

@group(0) @binding(3)
var colormap_sampler: sampler;

@group(0) @binding(4)
var<uniform> colormap_params: ColormapParams;


@vertex
fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
var pos = array<vec2<f32>, 4>(
Expand All @@ -22,6 +39,16 @@ fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
vec2(1.0, 1.0)
);

if(colormap_params.window_aspect_ratio>1.0) {
for(var i = 0u; i<4u; i=i+1u) {
pos[i].y = pos[i].y*colormap_params.window_aspect_ratio;
}
} else {
for(var i = 0u; i<4u; i=i+1u) {
pos[i].x = pos[i].x/colormap_params.window_aspect_ratio;
}
}

var texc = array<vec2<f32>, 4>(
vec2(0.0, 1.0),
vec2(0.0, 0.0),
Expand All @@ -37,21 +64,6 @@ fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
return output;
}

@group(0) @binding(0)
var image_texture: texture_2d<f32>;

@group(0) @binding(1)
var image_sampler: sampler;

@group(0) @binding(2)
var colormap_texture: texture_1d<f32>;

@group(0) @binding(3)
var colormap_sampler: sampler;

@group(0) @binding(4)
var<uniform> colormap_params: ColormapParams;


@fragment
fn fragment_main(input: VertexOutput) -> FragmentOutput {
Expand Down
17 changes: 9 additions & 8 deletions src/topsy/shaders/overlay.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
struct OverlayParams {
origin: vec2<f32>,
extent: vec2<f32>
clipspace_origin: vec2<f32>,
clipspace_extent: vec2<f32>,
texturespace_origin: vec2<f32>,
texturespace_extent: vec2<f32>,
};

@group(0) @binding(0)
Expand All @@ -14,7 +16,7 @@ struct VertexOutput {

@vertex
fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
var texc = array<vec2<f32>, 4>(
var offsets = array<vec2<f32>, 4>(
vec2(0.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
Expand All @@ -23,13 +25,12 @@ fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {

var output: VertexOutput;

output.texcoord = texc[vertexIndex];

var posOffset = output.texcoord;
var posOffset = offsets[vertexIndex];
posOffset.y = 1.0 - posOffset.y;
posOffset *= overlay_params.extent;
posOffset *= overlay_params.clipspace_extent;

output.pos = vec4<f32>(overlay_params.origin + posOffset, 0.0, 1.0);
output.pos = vec4<f32>(overlay_params.clipspace_origin + posOffset, 0.0, 1.0);
output.texcoord = overlay_params.texturespace_origin + offsets[vertexIndex]*overlay_params.texturespace_extent;

return output;
}
Expand Down
2 changes: 1 addition & 1 deletion src/topsy/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def draw(self):

def _update_and_display_status(self, command_encoder):
now = time.time()
if now - self._last_status_update > 0.2:
if now - self._last_status_update > config.STATUS_LINE_UPDATE_INTERVAL:
self._last_status_update = now
self._status.text = f"${1.0 / self._render_timer.running_mean_duration:.0f}$ fps"
if self._sph.downsample_factor > 1:
Expand Down

0 comments on commit fbcac7c

Please sign in to comment.