Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Создание контура, wxPython

271
28 июля 2011 года
MrXaK
721 / / 31.12.2002
У меня есть окно, в котором поверх рисунка ставятся точки (по wx.EVT_LEFT_DOWN), а затем сначала в окне поверх рисунка рисуется полигон (рисунок отображается посредством wx.BufferedDC, полигон рисуется через dc.DrawPolygon). Затем уже в самом рисунке посредством библиотеки Image при помощи t = ImageDraw.Draw(Im) t.polygon(contours, fill=color) рисуется такой же полигон на самом рисунке.
Я хочу сделать режим, чтобы рисовать полигоны можно было не только точками, а и кистью. Написать в лоб в принципе несложно, поставив, например, событие wx.EVT_MOTION, но тогда в огромном количестве возрастёт число точек полигона. Есть ли готовые функции, которые бы построили аппроксимирующий данную траекторию полигон с небольшим числом точек? Если готовых нет, то подскажите, пожалуйста, алгоритм, который будет оптимально искать ключевые точки траектории. Да, полигон необязательно выпуклый, может быть и впуклый, но без самопересечений.
87
28 июля 2011 года
Kogrom
2.7K / / 02.02.2008
Насколько я понимаю, задача сводится к упрощению ломаной. Если так, то она не привязана к wxPython и, возможно, в Общих Вопросах дали бы хороший ответ.
Я не знаю в wxPython готовой функции для такого. Но вообще есть алгоритмы. Например:
http://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm
Думаю, оптимизировать лучше не в реальном времени, а когда пользователь проведёт линию.
87
28 июля 2011 года
Kogrom
2.7K / / 02.02.2008
Вот, например, реализация:
http://mappinghacks.com/code/dp.py.txt

Сам не испытывал.
271
29 июля 2011 года
MrXaK
721 / / 31.12.2002
Да, спасибо, всё шикарно работает)
собственно ниже приведу свой код, авось кому пригодится
Код:
import logic.lib as mylib

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() (у меня из родителя по кнопке сохранить вызывается)
271
29 июля 2011 года
MrXaK
721 / / 31.12.2002
алгоритм мне понравился, так что я перевёл статью с английской вики
http://ru.wikipedia.org/wiki/Алгоритм_Рамера-Дугласа-Пекера
87
29 июля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Mr.Hacker
алгоритм мне понравился, так что я перевёл статью с английской вики
http://ru.wikipedia.org/wiki/Алгоритм_Рамера-Дугласа-Пекера



Быстро сработал. Уважаю.

271
22 августа 2011 года
MrXaK
721 / / 31.12.2002
не контур, но тему продолжу) теперь поверх этого окна надо наложить линеечку, как в фотошопе, которая двигалась бы с мышкой, была бы полупрозрачна, но в то же время желательно, чтобы меняла цвет в зависимости от фона без пересчёта координат в этом фоне (а фон, то бишь картинки - на одной прямой могут проходить все значения от белого до чёрного, без цветов), и показывала бы сантиметры)) в рамках одного запуска программы сантиметры одинаковые, в рамках разных запусков есть, грубо говоря, число, которое означает, сколько сантиметров в пикселе картинки)
87
23 августа 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Mr.Hacker
не контур, но тему продолжу)



Лучше новые создавай - так будет проще искать тем, у кого возникнут подобные вопросы.

Цитата: Mr.Hacker
теперь поверх этого окна надо наложить линеечку, как в фотошопе, которая двигалась бы с мышкой, была бы полупрозрачна, но в то же время желательно, чтобы меняла цвет в зависимости от фона без пересчёта координат в этом фоне (а фон, то бишь картинки - на одной прямой могут проходить все значения от белого до чёрного, без цветов), и показывала бы сантиметры)) в рамках одного запуска программы сантиметры одинаковые, в рамках разных запусков есть, грубо говоря, число, которое означает, сколько сантиметров в пикселе картинки)



Слишком много подзадач в одном вопросе. Например, для полупрозрачности может помочь alpha, которая есть в классах типа wx.Colour, wx.Bitmap. А цифры линейки можно рисовать двумя цветами (например, белым с тенью). Хотя на очень пёстрых картинках и такое может не помочь. Но надо экспериментировать.

271
23 августа 2011 года
MrXaK
721 / / 31.12.2002
Ок, тогда всё-таки здесь попроще сформулирую? Можно сделать линейку оверлеем с учётом того, что под этим оверлеем чёрно-белое изображение и не дёргать его (изображение) вручную для пересчёта цветов линейки?
87
25 августа 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Mr.Hacker
Ок, тогда всё-таки здесь попроще сформулирую? Можно сделать линейку оверлеем с учётом того, что под этим оверлеем чёрно-белое изображение и не дёргать его (изображение) вручную для пересчёта цветов линейки?



Возможно, ответ будет запоздалым, но я не совсем понял этот "упрощенный" вопрос.
Зачем что-то дёргать? Возьмём простой пример: цифры на черно-белом градиенте:

Код:
import wx

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()


Тут у меня пока всё читается. Линейку можно вообще сделать полностью прозрачной, лишь обозначив контуром, а можно с альфой мудрить.
271
25 августа 2011 года
MrXaK
721 / / 31.12.2002
не, смысл не чтобы статическая линейка по бокам была, а чтобы она вместе с мышкой ездила)) и проходила через точки координат мышки)) можно вот код выше запихнуть в обработчик wx.EVT_MOTION, но он в каких-то местах будет неконтрастным в связи с фоном, а пересчитывать по каждому onMotion цвет каждой точки - вот это как раз и будет долго
87
25 августа 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Mr.Hacker
не, смысл не чтобы статическая линейка по бокам была



А ни кто и не говорит, что в этом смысл. Ты, вероятно, код не читал. У меня там черно-белые цифры читаются и на белом фоне и на черном. В этом смысл.

Цитата: Mr.Hacker
а чтобы она вместе с мышкой ездила)) и проходила через точки координат мышки)) можно вот код выше запихнуть в обработчик wx.EVT_MOTION, но он в каких-то местах будет неконтрастным в связи с фоном, а пересчитывать по каждому onMotion цвет каждой точки - вот это как раз и будет долго



Надо делить задачу на куски. Чтобы цифры были контрастными независимо от фона - одна задача, и решение я привёл. "Чтобы линейка ездила" - другая. Чтобы линейка поворачивала под углом - третья и т.д.

271
25 августа 2011 года
MrXaK
721 / / 31.12.2002
да, контрастные они действительно) спасибо)) я написал так:
Код:
class MyFrame(wx.Frame):
    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 перекрывает и работают только евенты его фрейма..
87
26 августа 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Mr.Hacker
и работает быстро)) но не стирается

а если перерисовывать битмап в 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.

271
26 августа 2011 года
MrXaK
721 / / 31.12.2002
а, точно, спасибо) чё-то не подумал, что можно буфферить уже отрисованный)
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог