2016-12-08 3 views
2

Я загружаю фигуру jpeg (large: 1300x2000) в matplotlib, рисуя сетку из 50x50 квадратов над ней и нажимая на каждый квадрат, чтобы присвоить цветному коду. Тем не менее, я замечаю, что программа значительно отстает от моих кликов и занимает до 30 секунд, чтобы догнать, если я быстро разберусь на 50 квадратов с разумной скоростью. Мне интересно, сможет ли кто-то ускорить работу. Ниже приведен мой сценарий, который готов к работе, если вы скопируете/вставьте его (и имеете scipy, numpy, matplotlib, pillow и tkinter).Ускорьте мой интерактивный рисунок matplotlib

Любые советы приветствуются. Я ученый-медик, так пожалуйста, прости меня, если код не очень хорошо объяснил:

import matplotlib 
import matplotlib.pyplot as plt 
import tkinter 
import tkinter.filedialog 
from matplotlib.figure import Figure 
import math, sys 
import numpy as np 
import scipy.io as sio 
from PIL import Image 
from numpy import arange, sin, pi 
#from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 
#matplotlib.matplotlib_fname() 
import os, re 

global stridesize, classnumber,x, im,fn, plt, fig, mask 

classnumber = 1 



def onmove(eve): 
    global x,im, plt 
    print(eve.ydata) 
    print(eve.button) 
    if (eve.ydata !=None) and (eve.xdata !=None): 
     if eve.button==1: 
      print(eve.button) 
      xcoord = int(eve.xdata) 
      ycoord = int(eve.ydata) 
      startX = math.floor(xcoord/stridesize)*stridesize 
      startY = math.floor(ycoord/stridesize)*stridesize 
      # print(eve.xdata, int(eve.ydata), stridesize) 
      if(classnumber==1): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([255,0,0]) 
      if(classnumber==2): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,255,0]) 
      if(classnumber==3): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,0,255]) 
      if(classnumber==4): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([255,0,255]) 
      if(classnumber==5): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([255,255,0]) 
      if(classnumber==6): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,255,255]) 
      if(classnumber==7): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([100,255,50]) 


     if eve.button==3: 
      xcoord = int(eve.xdata) 
      ycoord = int(eve.ydata) 
      startX = math.floor(xcoord/stridesize)*stridesize 
      startY = math.floor(ycoord/stridesize)*stridesize 
      print(eve.xdata, int(eve.ydata), stridesize) 
      mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,0,0]) 
     im.set_data(mask) 
     fig.canvas.draw() 




def onclick(event): 

    if (event.ydata !=None) and (event.xdata !=None): 
     global x, im, fig 
     if event.button==1: 
      xcoord = int(event.xdata) 
      ycoord = int(event.ydata) 
      startX = math.floor(xcoord/stridesize)*stridesize 
      startY = math.floor(ycoord/stridesize)*stridesize 
      print(event.xdata, int(event.ydata), stridesize) 
      if(classnumber==1): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([255,0,0]) 
      if(classnumber==2): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,255,0]) 
      if(classnumber==3): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,0,255]) 
      if(classnumber==4): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([255,0,255]) 
      if(classnumber==5): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([255,255,0]) 
      if(classnumber==6): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,255,255]) 
      if(classnumber==7): 
       mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([100,255,50]) 
      im.set_data(mask) 

     if event.button==3: 
      xcoord = int(event.xdata) 
      ycoord = int(event.ydata) 
      startX = math.floor(xcoord/stridesize)*stridesize 
      startY = math.floor(ycoord/stridesize)*stridesize 
      print(event.xdata, int(event.ydata), stridesize) 
      mask[startY:startY+stridesize,startX:startX+stridesize,:]=np.array([0,0,0]) 
      im.set_data(mask) 
     fig.canvas.draw() 


def onpress(event): 
    global classnumber, mask 
    if (event.key == 'e'): 
     print("YO") 
     mask[:,:,:]=0; 
     im.set_data(mask) 
     fig.canvas.draw() 

    if (event.key=='s'): 
     savemask(fn) 
    if (event.key=='r'): 
     plt.figure(); 
     plt.imshow(mask); 
     plt.show(); 
    if int(event.key) > 0 and int(event.key) <9 : 
     classnumber = int(event.key) 
     print(classnumber) 


def onrelease(event): 
    print(event.button) 
# im.set_data(mask) 




def savemask(fn): 
    # matrixname =os.path.basename(filename) 
    # matrixname = re.sub(r'\.jpg','',matrixname) 
    pre, ext = os.path.splitext(fn) 
    savename_default = os.path.basename(pre) 
    options = {} 
    options['defaultextension'] = '' 
    options['filetypes'] = [('mat files', '.mat')] 
    options['initialdir'] = '' 
    options['initialfile'] = savename_default 
    options['title'] = 'Save file' 


    f = tkinter.filedialog.asksaveasfile(**options) 
    if f is None: # asksaveasfile return `None` if aadialog closed with "cancel". 
     return 
    name = f.name 
    sio.savemat(name,{'mask':mask},do_compression=True) 
    f.close() 




root = tkinter.Tk() 
root.withdraw() 

options = {} 

options['defaultextension'] = '.jpg' 

options['filetypes'] = [('Jpeg', '.jpg')] 

options['initialdir'] = 'C:\\' 
options['initialfile']= '' 
options['parent'] = root 

options['title'] = 'This is a title' 


fn= tkinter.filedialog.askopenfilename(**options) 


img = Image.open(fn) 
x = np.asarray(img) 
x.setflags(write=1) 
#masksize= (x.shape[0],x.shape[1],4) 
mask= np.zeros(x.shape,'uint8') 
#mask[:,:,3]=0.2 
fig = plt.figure() 
fig.suptitle(r'Key codes: 1 = Tumour, 2 = stroma-hypocellular, 3=stroma cellular (inflammatory)' '\n4 = proteinaceous, 5= red cells, 6,7: anyother,''\nRight click: clear square''\n r: review mask, e: erase mask, o : open mask image, s : save mask image;') 


im=plt.imshow(x) 
im=plt.imshow(mask,alpha=.25) 
ax = plt.gca(); 

stridesize = 50; 

plt.rcParams['keymap.save']='' 
ax.set_yticks(np.arange(0, x.shape[0], stridesize)); 
ax.set_xticks(np.arange(0, x.shape[1], stridesize)); 

cid = fig.canvas.mpl_connect('button_press_event', onclick) 
cod = fig.canvas.mpl_connect('key_press_event', onpress) 
#cdd = fig.canvas.mpl_connect('motion_notify_event', onmove) 
cdr = fig.canvas.mpl_connect('button_release_event', onrelease) 

plt.grid(b=True, which='both', color='black',linestyle='-') 
# 
plt.show() 

plt.ion() 
+0

Для тех, кто заинтересован в том, чтобы знать, что было в корне неправильно с помощью кода выше, изменение одной линии удвоило скорость. Поэтому я изменил каждый вызов: fig.canvas.draw() на fig.canvas.draw_idle() – Maelstorm

ответ

3

Во-первых, я бы рекомендовал избегать использования global переменных любой ценой. Вы можете заменить его, используя вместо этого class. Найти сильфонные полностью рабочий обобщенную версию того, что ваш код намерен делать:

import numpy as np 

import matplotlib 
matplotlib.use('Qt4Agg') 

from matplotlib import pyplot as plt 
from matplotlib.colors import ListedColormap 

class ColorCode(object): 

    def __init__(self, block_size=(50,50), colors=['red', 'green', 'blue'], alpha=0.3): 
     self.by, self.bx = block_size # block size 
     self.selected = 0 # selected color 
     self.colors = colors 
     self.cmap = ListedColormap(colors) # color map for labels 
     self.mask = None # annotation mask 
     self.alpha = alpha 
     # Plots 
     self.fig = plt.figure() 
     self.ax = self.fig.gca() 
     # Events 
     self.fig.canvas.mpl_connect('button_press_event', self.on_click) 
     self.fig.canvas.mpl_connect('key_press_event', self.on_key) 

    def color_code(self, img): 
     self.imshape = img.shape[:2] 
     self.mask = np.full(img.shape[:2], -1, np.int32) # masked labels 
     self.ax.imshow(img) # show image 
     self.ax.imshow(np.ma.masked_where(self.mask < 0, self.mask), cmap=self.cmap, 
         alpha=self.alpha, vmin=0, vmax=len(self.colors)) # show mask 
     # Run 
     plt.show(block=True) 
     return self.mask 

    def on_click(self, event): 
     if not event.inaxes or self.mask is None: 
      return 
     # Get corresponding coordinates 
     py, px = int(event.ydata), int(event.xdata) 
     cy, cx = py//self.by, px//self.bx # grid coordinates 
     ymin = cy * self.by 
     ymax = min((cy+1) * self.by, self.imshape[0]) 
     xmin = cx * self.bx 
     xmax = min((cx+1) * self.bx, self.imshape[1]) 
     # Update mask 
     if event.button == 1: 
      self.mask[ymin:ymax, xmin:xmax] = self.selected 
     elif event.button == 3: 
      self.mask[ymin:ymax, xmin:xmax] = -1 
     # Update figure 
     self.ax.images[1].set_data(np.ma.masked_where(self.mask < 0, self.mask)) 
     self.fig.canvas.draw_idle() 

    def on_key(self, event): 
     ikey = int(event.key) 
     if 0 <= ikey < len(self.colors): 
      self.selected = ikey 

Основные отличия с вашим кодом являются:

  1. Он не использует глобальные переменные, вместо этого, он использует класс -variables. Сделать его более безопасным для запуска и упростить продление/изменение.

  2. Вместо окраски 3-мерную mask для аннотаций, аннотации сохраняются в виде 2-мерный mask, где каждый пиксель имеет значение в диапазоне [1, len(colors)], указывающее на какой цвет он принадлежит. Затем цвета добавляются на график, используя ListedColormap для установки пользовательской цветовой карты для вашего участка.

  3. Он рисует изображение и накладывает поверх него маску сегментации. Первоначально маска заполняется -1, то есть у нее нет метки. Используя numpy's masked array, вы можете замаскировать там, где mask < 0, не показать, что на участке, делая участок прозрачным, где mask < 0 и окрашен в противном случае.

  4. Список возможных цветов предоставляется в качестве параметра для класса. Это позволит вам выбрать цвет от 0 до len(colors) с максимум 10 цветами (так как в настоящее время он привязан к номерам на клавиатуре).

  5. fig.canvas.draw_idle намного лучше, чем fig.canvas.draw. Последний блокирует программу, пока не закончит рисование.

  6. Как все в классе, код выглядит намного чище.

Можно назвать код как:

>>> random_image = np.random.randn(1000,2000, 3) 
>>> result = ColorCode().color_code(random_image) 

и result будет содержать маркировку mask, где каждый пиксель имеет номер, указывающий с ведьмами цвета она была меченой (-1, если нет). Наконец, другие параметры могут быть переданы в конструктор ColorCode, такой как block_size=(100,100) для разных размеров блока, alpha=0.5 для меньшей непрозрачности в маске (или None as alpha=1).

Надеюсь, что это сработает для вас, или, по крайней мере, вы можете получить от него некоторые идеи.

+0

Спасибо большое. Конечно, улучшена скорость, по крайней мере, дважды – Maelstorm

+0

@Maelstorm отредактировал вопрос, чтобы добавить разницу между * левым кликом * и * правым кликом *. Теперь вы можете удалить * щелчок правой кнопкой мыши *. Кроме того, я думаю, что это может улучшить эффективность, если вместо 'click + redraw' вы нажимаете + перемещаете мышь, сохраняя путь + релиз + перерисовать', чтобы вы могли перетащить мышь по всем интересным областям, нажимая кнопку, и когда они будут выпущены, все они будут окрашены в цвет. Но было бы сложно закодировать. –

+0

Можно ли использовать другой сервер? У меня есть Py3 и PyQt5, но я продолжаю получать ошибки PyQt4, даже меняя «matplotlib.use()». – percusse

Смежные вопросы