Создание контура, wxPython
Я хочу сделать режим, чтобы рисовать полигоны можно было не только точками, а и кистью. Написать в лоб в принципе несложно, поставив, например, событие wx.EVT_MOTION, но тогда в огромном количестве возрастёт число точек полигона. Есть ли готовые функции, которые бы построили аппроксимирующий данную траекторию полигон с небольшим числом точек? Если готовых нет, то подскажите, пожалуйста, алгоритм, который будет оптимально искать ключевые точки траектории. Да, полигон необязательно выпуклый, может быть и впуклый, но без самопересечений.
Я не знаю в wxPython готовой функции для такого. Но вообще есть алгоритмы. Например:
http://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm
Думаю, оптимизировать лучше не в реальном времени, а когда пользователь проведёт линию.
собственно ниже приведу свой код, авось кому пригодится
class segmentWindow(wx.Window):
def __init__(self, parent, **kwargs):
"""
@param parent: parent windows
@type parent: wx.Window
@keyword slice: номер среза
@type slice: int
@keyword pat: пациент
@type pat: patient
@keyword selectionType: тип оконтуривания (dots, solid)
@type selectionType: string
@keyword contour: сохранённые контура
@type contour: dict
"""
wx.Window.__init__(self, parent, size=wx.Size(640,640))
self.slice = kwargs.get('slice')
self.organType = 'tumour'
self.pat = kwargs.get('pat', patient.patientInfo())
self.selectionType = kwargs.get('selectionType', 'dots')
self.organs = organs.organs()
self.contour = kwargs.get('contour', {})
self.initBuffer()
self.curCont = []
self.clicked = False
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_LEFT_DOWN, self.onLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.onLeftUp)
self.Bind(wx.EVT_MOTION, self.onMotion)
self.brushColor = wx.RED_BRUSH
def setSelectionType(self, mode):
"""
Устанавливает текущий режим оконтуривания
@param mode: режим (dots, solid)
@type mode: string
"""
self.selectionType = mode
def setOrganType(self, organ):
"""
Устанавливает параметры текущего органа
@param organ: орган
@type organ: organ
"""
self.organType = organ['name']
try:
if (len(self.contour[self.organType]) < 1):
self.contour[self.organType] = []
except:
self.contour[self.organType] = []
def clear(self):
"""
Очистка
"""
self.contour = {}
self.initBuffer()
def setBrush(self, color):
"""
Цвет закраски
@param color: цвет
@type color: tuple or wx.Color
"""
if ('tuple' == type(color).__name__ and 3 == len(color)):
color = wx.Colour(color[0], color[1], color[2])
self.brushColor = wx.Brush(color)
def initBuffer(self):
"""
Рисует подложку, восстанавливает контура
"""
size = self.GetClientSize()
self.buffer = wx.EmptyBitmap(max(1,size.width), max(1,size.height))
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush('#FFFFFF'))
dc.Clear()
ls = os.listdir(self.pat.getPatDir()+'/Slices')
slice = ls[self.slice]
bitmap=wx.Bitmap(self.pat.getPatDir()+'/Slices/' + slice)
dc.DrawBitmap(bitmap,0,0,True)
for i in self.contour:
if len(self.contour) > 1:
self.setBrush(self.organs.getColor(name=i)['colorTuple'])
dc.SetBrush(self.brushColor)
dc.DrawPolygon(self.contour, 0, 0)
str = _("Slice %d") %self.slice
text = wx.StaticText(self, -1, str, (0, 0))
font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.NORMAL)
text.SetFont(font)
self.Refresh()
try:
wx.GetApp().GetTopWindow().updateProgress()
except:
pass
def onPaint(self, event):
"""
Создаёт пустой объект DC
@param event: event
@type event: wx.Event
"""
dc = wx.BufferedPaintDC(self, self.buffer)
def onLeftDown(self, event):
"""
Обработчик нажатия мыши\n
Если режим dots - рисует кружок, если режим solid - запоминает первую нарисованную точку
@param event: event
@type event: wx.Event
"""
if (0 == self.contour.__len__()):
dlg = GMD.GenericMessageDialog(wx.GetApp().GetTopWindow(),
_('Please select target organ first'),
_('Select organ first'),
GMD.GMD_USE_GRADIENTBUTTONS | wx.OK | wx.ICON_INFORMATION)
result = dlg.ShowModal()
dlg.Destroy()
return
if ('dots' == self.selectionType):
self.GetParent().setSavedState(False)
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
dc.BeginDrawing()
x, y = event.GetPosition()
coords = (x, y)
self.contour[self.organType].append(coords)
dc.DrawCircle(x,y,4)
dc.EndDrawing()
elif ('solid' == self.selectionType):
self.clicked = True
x, y = event.GetPosition()
self.curCont.append((x,y))
pass
def onLeftUp(self, event):
"""
Обработчик отпускания мыши\n
Если режим dots - возврат, если режим solid - добавляет последнюю координату, запускает функцию упрощения
@param event: event
@type event: wx.Event
@see: logic.lib
"""
if ('dots' == self.selectionType):
event.Skip()
return
self.clicked = False
x, y = event.GetPosition()
self.curCont.append((x,y))
self.contour[self.organType] = mylib.simplify_points(self.curCont, 3)
self.curCont = []
def onMotion(self, event):
"""
Событие по движению мыши\n
Если режим dots - возврат, если режим solid - добавляет текущую координату, рисует линию от старой до новой точки
"""
if ('dots' == self.selectionType):
event.Skip()
return
if (not self.clicked):
event.Skip()
return
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
dc.BeginDrawing()
clr = self.organs.getColor(name=self.organType)['colorTuple']
pen = wx.Pen(wx.Color(clr[0], clr[1], clr[2]), 3, wx.SOLID)
dc.SetPen(pen)
x, y = event.GetPosition()
self.curCont.append((x,y))
dc.DrawLine(self.curCont[-1][0],self.curCont[-1][1], x, y)
dc.EndDrawing()
def collect(self):
"""
Функция-обработчик
@return: {contour}
@rtype: dict
"""
return {'contour' : self.contour}
в logic.lib код из предыдущего поста (simplify_points), для показа зарисованного полигона надо вызвать initBuffer() (у меня из родителя по кнопке сохранить вызывается)
http://ru.wikipedia.org/wiki/Алгоритм_Рамера-Дугласа-Пекера
Быстро сработал. Уважаю.
Лучше новые создавай - так будет проще искать тем, у кого возникнут подобные вопросы.
Слишком много подзадач в одном вопросе. Например, для полупрозрачности может помочь alpha, которая есть в классах типа wx.Colour, wx.Bitmap. А цифры линейки можно рисовать двумя цветами (например, белым с тенью). Хотя на очень пёстрых картинках и такое может не помочь. Но надо экспериментировать.
Возможно, ответ будет запоздалым, но я не совсем понял этот "упрощенный" вопрос.
Зачем что-то дёргать? Возьмём простой пример: цифры на черно-белом градиенте:
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size = (270, 270))
wx.EVT_PAINT(self, self.OnPaint)
self.Centre()
def OnPaint(self, event):
dc = wx.PaintDC(self)
w, h = self.GetClientSize()
dc.GradientFillLinear(wx.Rect(0, 0, w, h), wx.WHITE, wx.BLACK)
dc.SetFont(wx.FFont(pointSize=14, family=wx.FONTFAMILY_MODERN,
face="Courier New"))
def draw_digits(shift):
for i in xrange(10):
s = str(i)
w, h = dc.GetTextExtent(s)
dc.DrawText(s, 50 + i*20 - w//2 + shift, 100 + shift)
dc.SetTextForeground(wx.BLACK)
draw_digits(0)
dc.SetTextForeground(wx.WHITE)
draw_digits(-1)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'Memento')
frame.Show(True)
self.SetTopWindow(frame)
return True
app = MyApp(0)
app.MainLoop()
Тут у меня пока всё читается. Линейку можно вообще сделать полностью прозрачной, лишь обозначив контуром, а можно с альфой мудрить.
А ни кто и не говорит, что в этом смысл. Ты, вероятно, код не читал. У меня там черно-белые цифры читаются и на белом фоне и на черном. В этом смысл.
Надо делить задачу на куски. Чтобы цифры были контрастными независимо от фона - одна задача, и решение я привёл. "Чтобы линейка ездила" - другая. Чтобы линейка поворачивала под углом - третья и т.д.
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size = (512, 512))
self.dc = None
wx.EVT_PAINT(self, self.OnPaint)
self.Bind(wx.EVT_MOTION, self.onMotion)
self.Centre()
def OnPaint(self, event):
self.dc = wx.PaintDC(self)
bitmap=wx.Bitmap('C:\\Users\\mrxak\\PycharmProjects\\NPPlan\\Data\\4e12c9055157510e2c000000\\Slices\\slice.00')
self.dc.DrawBitmap(bitmap,0,0,True)
def onMotion(self, event):
w, h = self.GetClientSize()
# dc.GradientFillLinear(wx.Rect(0, 0, w, h), wx.WHITE, wx.BLACK)
if (self.dc is None):
return
self.dc.SetFont(wx.FFont(pointSize=14, family=wx.FONTFAMILY_MODERN,
face="Courier New"))
x, y = event.GetPosition()
def draw_digits(shift, x, y):
for i in xrange(10):
s = str(i)
w, h = self.dc.GetTextExtent(s)
self.dc.DrawText(s, x + i*20 - w//2 + shift, y + shift)
self.dc.SetTextForeground(wx.BLACK)
draw_digits(0, x, y)
self.dc.SetTextForeground(wx.WHITE)
draw_digits(-1, x, y)
получается это дело так:
[ATTACH=CONFIG]5292[/ATTACH]
и работает быстро)) но не стирается
а если перерисовывать битмап в OnMotion, то есть 12-13 строки вставить после 15й, то начинает подлагивать... собственно поэтому и спрашивал, можно ли этот текст линейки рисовать поверх всех DC, чтобы нижнюю картинку не перерисовывать)
Upd: пришла тут мысль, что хорошо, да и с точки зрения логики всей программы, рисовать всю эту линейку вообще в родительском фрейме.. но внезапно оказалось, что тогда евенты у дочерних не отрабатывают... типа слой DC перекрывает и работают только евенты его фрейма..
а если перерисовывать битмап в OnMotion, то есть 12-13 строки вставить после 15й, то начинает подлагивать... собственно поэтому и спрашивал, можно ли этот текст линейки рисовать поверх всех DC, чтобы нижнюю картинку не перерисовывать)
Ну, обычно же рисуют в буферный DC (пусть это будет self.memoryDC, например), который сразу на экран не выводят. Выводят только при OnPaint, как-то так:
dc.Blit(0, 0, w, h, self.memoryDC, 0, 0).
Буферных можно сделать несколько. В одном - картинка, в другом - она же с линейкой.
В конце OnMotion ставят ставят self.Refresh(False) чтобы вызвать событие PAINT.
Ну ещё есть некоторые тонкости с обновлением буферного DC при изменении размеров окна. Но в целом это гуглится по чему-то вроде DoubleBufferedDrawing.