Home > other >  Tkinter: changing canvas pixel size
Tkinter: changing canvas pixel size

Time:10-20

Here is what I want to do:

I am designing an application that allows, among other things, to view DICOM images. What I will call "image" is actually a set of files that contain 2D arrays corresponding to slices. I am using Tkinter to provide an UI. My application only requires to display binary images.

In order to display the slices, I use tk.Canvas which allows very fast display of images. Indeed, I need the most optimised displaying device since I want to the user to be able to travel across slices using the mouse wheel.

The problem is: when displaying a slice, the canvas is always allocating the same dimensions to pixels and therefore, images with lower resolution appear very small. What I want to do is to prevent the user from killing his/her eyes by resizing the canvas.

I thought of course of using PIL.Image().resize() on the image that is then converted to PIL.ImageTk() but this causes two problems:

  1. The greater the resizing, the more time is needed to perform the process and therefore the less the viewer is optimised
  2. This resizing action actually modifies the number of pixels, which loses the original resolution. I do not want this to happen since I require to retrieve mouse position as it hovers over the canvas, in terms of pixels in the original resolution

The solution in my opinion would therefore be to modify the pixel size of the canvas. If it is possible to define it from the start, then resizing would not be necessary and there would be no optimisation problem.

But I have not been able to find a way to modify this. Would anyone have an idea?

I am providing only the frame and the imager of my project if it can help:

Frame:

import PIL.Image
import PIL.ImageTk
import numpy as np

from gui.statusbar import *

from tkinter.messagebox import showinfo


class DicomViewerFrame(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)

        self.parent = parent

        self.image_ax = None
        self.image_sag = None
        self.photo_ax = None
        self.photo_sag = None
        self.canvas_axial = None
        self.canvas_sagittal = None
        self.imager = None

        self.arr_axial = None
        self.arr_sagittal = None
        self.index_axial = None
        self.index_sagittal = None

        self.status_axial = None
        self.status_sagittal = None

        self.roi_definition = True

        self.upper = 0
        self.lower = 0

        self.selection_axial = None
        self.selection_sagittal = None
        self.start_x = self.start_y = self.start_z = 0
        self.end_x = self.end_y = self.end_z = 0
        self.roi = None

        self.offset = 700

        self.init_viewer_frame()

    def init_viewer_frame(self):

        # Image canvas
        self.canvas_axial = Canvas(self, bd=0, highlightthickness=0)
        self.canvas_axial.grid(row=0, column=0, sticky="nw")
        self.canvas_axial.bind("<MouseWheel>", self.scroll_axial_images)
        self.canvas_axial.config(width=self.offset)
        if self.roi_definition:
            self.canvas_axial.bind("<B1-Motion>", self.on_move_press_axial)
            self.canvas_axial.bind("<ButtonPress-1>", self.on_button_press_axial)
            self.canvas_axial.bind("<ButtonRelease-1>", self.on_button_release)

        self.canvas_sagittal = Canvas(self, bd=0, highlightthickness=0)
        self.canvas_sagittal.grid(row=0, column=1, sticky="nw")
        self.canvas_sagittal.bind("<MouseWheel>", self.scroll_sagittal_images)
        if self.roi_definition:
            self.canvas_sagittal.bind("<B1-Motion>", self.on_move_press_sagittal)
            self.canvas_sagittal.bind("<ButtonPress-1>", self.on_button_press_sagittal)
            self.canvas_sagittal.bind("<ButtonRelease-1>", self.on_button_release)

        # Status bar
        self.status_axial = StatusBar(self)
        self.status_axial.grid(row=3, column=0, sticky="w")
        self.status_sagittal = StatusBar(self)
        self.status_sagittal.grid(row=3, column=1, sticky="w")

        self.canvas_axial.bind('<Motion>', self.motion_axial)
        self.canvas_sagittal.bind('<Motion>', self.motion_sagittal)

    def on_button_press_axial(self, event):
        self.canvas_axial.delete(self.selection_axial)
        self.canvas_sagittal.delete(self.selection_sagittal)

        # save mouse drag start position
        self.start_x = event.x
        self.start_y = event.y

        self.selection_axial = self.canvas_axial.create_rectangle(self.end_x, self.end_y, 0, 0, outline="green")
        self.selection_sagittal = self.canvas_sagittal.create_rectangle(0, self.start_x, self.arr_sagittal.shape[1],
                                                                        self.end_x, outline="green")

    def on_button_press_sagittal(self, event):
        self.canvas_sagittal.delete(self.selection_sagittal)

        # save mouse drag start position
        self.start_z = event.x

        self.selection_sagittal = self.canvas_sagittal.create_rectangle(self.start_z, self.start_x, 0,
                                                                        self.end_x, outline="green")

    def on_move_press_axial(self, event):
        curX, curY = (event.x, event.y)
        self.end_x = curX
        self.end_y = curY

        self.motion_axial(event)

        # expand rectangle as you drag the mouse
        self.canvas_axial.coords(self.selection_axial, self.start_x, self.start_y, curX, curY)
        self.canvas_sagittal.coords(self.selection_sagittal, 0, self.start_x, self.arr_sagittal.shape[1], curX)

    def on_move_press_sagittal(self, event):
        curZ = event.x
        self.end_z = curZ

        self.motion_sagittal(event)

        # expand rectangle as you drag the mouse
        self.canvas_sagittal.coords(self.selection_sagittal, self.start_z, self.start_x, curZ, self.end_x)

    def on_button_release(self, event):
        roi_axial = self.canvas_axial.bbox(self.selection_axial)
        roi_sagittal = self.canvas_sagittal.bbox(self.selection_sagittal)
        self.roi = ((roi_axial[0], roi_axial[1], roi_sagittal[0]), (roi_axial[2], roi_axial[3], roi_sagittal[2]))

    def show_image(self, array_axial, index_axial, array_sagittal, index_sagittal):
        self.upper = int(self.parent.pcd_preparer.get_current_upper().get())
        self.lower = int(self.parent.pcd_preparer.get_current_lower().get())

        if array_axial is None:
            return
        if array_sagittal is None:
            return

        # Convert numpy array into a PhotoImage and add it to canvas
        self.image_ax = PIL.Image.fromarray(array_axial)
        self.photo_ax = PIL.ImageTk.PhotoImage(self.image_ax)
        self.image_sag = PIL.Image.fromarray(array_sagittal)
        self.photo_sag = PIL.ImageTk.PhotoImage(self.image_sag)

        self.canvas_axial.delete("IMG")
        self.canvas_axial.create_image(0, 0, image=self.photo_ax, anchor=NW, tags="IMG")
        self.canvas_axial.create_text(40, 10, fill="green", text="Slice "   str(index_axial), font=10)
        self.canvas_axial.create_text(40, 40, fill="green", text="Axial", font=10)

        self.canvas_sagittal.delete("IMG")
        self.canvas_sagittal.create_image(0, 0, image=self.photo_sag, anchor=NW, tags="IMG")
        self.canvas_sagittal.create_text(40, 10, fill="green", text="x = "   str(index_sagittal), font=10)
        self.canvas_sagittal.create_text(40, 40, fill="green", text="Sagittal", font=10)

        width_ax = self.image_ax.width
        height_ax = self.image_ax.height
        width_sag = self.image_sag.width
        height_sag = self.image_sag.height

        self.canvas_axial.configure(width=width_ax, height=height_ax)
        self.canvas_sagittal.configure(width=width_sag, height=height_sag)

        # We need to at least fit the entire image, but don't shrink if we don't have to
        width_ax = max(self.parent.winfo_width(), width_ax)
        height_ax = max(self.parent.winfo_height(), height_ax   StatusBar.height)
        width_sag = max(self.parent.winfo_width(), width_sag)
        height_sag = max(self.parent.winfo_height(), height_sag   StatusBar.height)

        # Resize root window and prevent resizing smaller than the image
        newsize = "{}x{}".format(width_ax   width_sag, height_ax   StatusBar.height)

        self.parent.geometry(newsize)
        # self.parent.minsize(width_ax   width_sag, height_ax   height_sag)

        if self.selection_axial is not None:
            self.selection_axial = self.canvas_axial.create_rectangle(self.start_x, self.start_y, self.end_x,
                                                                      self.end_y, outline="green")
        if self.selection_sagittal is not None:
            self.selection_sagittal = self.canvas_sagittal.create_rectangle(self.start_z, self.start_x, self.end_z,
                                                                         self.end_x, outline="green")

    def scroll_sagittal_images(self, e):
        self.imager.index_sagittal  = int(e.delta / 120)
        self.arr_sagittal, self.index_sagittal = self.imager.get_current_sagittal_image(self.upper, self.lower)
        self.show_image(self.arr_axial, self.index_axial, self.arr_sagittal, self.index_sagittal)

    def scroll_axial_images(self, e):
        self.imager.index_axial  = int(e.delta / 120)
        self.arr_axial, self.index_axial = self.imager.get_current_axial_image(self.upper, self.lower)
        self.show_image(self.arr_axial, self.index_axial, self.arr_sagittal, self.index_sagittal)

    def change_range(self):
        self.arr_axial, self.index_axial = self.imager.get_current_axial_image(self.upper, self.lower)
        self.arr_sagittal, self.index_sagittal = self.imager.get_current_sagittal_image(self.upper, self.lower)
        self.show_image(self.arr_axial, self.index_axial, self.arr_sagittal, self.index_sagittal)

    def set_imager(self, im):
        self.imager = im

    def motion_axial(self, event):
        x, y = event.x, event.y
        self.status_axial.set('x = {}, y = {}'.format(x, y))

    def motion_sagittal(self, event):
        z, y = event.x, event.y
        self.status_sagittal.set('y = {}, z = {}'.format(y, z))

Imager:

import numpy as np


class DicomImager:
    def __init__(self, datasets):
        self.values = None

        self.datasets = datasets
        self._index_axial = 0
        self._index_sagittal = 0
        self._window_width = 1
        self._window_center = 0

        self.size = (int(datasets[0].Rows), int(datasets[0].Columns), len(datasets))
        self.spacings = (float(datasets[0].PixelSpacing[0]),
                         float(datasets[0].PixelSpacing[1]),
                         float(datasets[0].SliceThickness))

        self.axes = (np.arange(0.0, (self.size[0]   1) * self.spacings[0], self.spacings[0]),
                     np.arange(0.0, (self.size[2]   1) * self.spacings[2], self.spacings[2]),
                     np.arange(0.0, (self.size[1]   1) * self.spacings[1], self.spacings[1]))

        # Load pixel data
        self.values = np.zeros(self.size, dtype='int32')
        for i, d in enumerate(datasets):
            # Also performs rescaling. 'unsafe' since it converts from float64 to int32
            np.copyto(self.values[:, :, i], d.pixel_array, 'unsafe')

        self.max_value = np.amax(self.values)
        self.min_value = np.amin(self.values)

    @property
    def index_sagittal(self):
        return self._index_sagittal

    @index_sagittal.setter
    def index_sagittal(self, value):

        while value < 0:
            value  = self.size[0]

        self._index_sagittal = value % self.size[0]

    @property
    def index_axial(self):
        return self._index_axial

    @index_axial.setter
    def index_axial(self, value):

        while value < 0:
            value  = self.size[2]

        self._index_axial = value % self.size[2]

    @property
    def window_width(self):
        return self._window_width

    @window_width.setter
    def window_width(self, value):
        self._window_width = max(value, 1)

    @property
    def window_center(self):
        return self._window_center

    @window_center.setter
    def window_center(self, value):
        self._window_center = value

    def get_sagittal_image(self, index, upper, lower):
        # int32 true values (HU or brightness units)
        img = self.values[index, :, :]

        res1 = np.zeros(img.shape)
        res1[img < upper] = 1
        res1[img < lower] = 0

        # Cast to RGB image so that Tkinter can handle it
        res = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
        res[:, :, 0] = res[:, :, 1] = res[:, :, 2] = res1 * 255

        return res

    def get_axial_image(self, index, upper, lower):
        # int32 true values (HU or brightness units)
        img = self.values[:, :, index]

        res1 = np.zeros(img.shape)
        res1[img < upper] = 1
        res1[img < lower] = 0

        # Cast to RGB image so that Tkinter can handle it
        res = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
        res[:, :, 0] = res[:, :, 1] = res[:, :, 2] = res1 * 255

        return res

    def get_current_sagittal_image(self, upper, lower):
        return self.get_sagittal_image(self._index_sagittal, upper, lower), self._index_sagittal

    def get_current_axial_image(self, upper, lower):
        return self.get_axial_image(self._index_axial, upper, lower), self._index_axial

CodePudding user response:

The solution in my opinion would therefore be to modify the pixel size of the canvas... But I have not been able to find a way to modify this. Would anyone have an idea?

There is no way to modify the size of a pixel in the canvas. Your only option is to resize the image.

  • Related