From 96538e6803d5415761ead626fbee27ff1edafd14 Mon Sep 17 00:00:00 2001 From: Ark Lu <39932933+qiyoulu@users.noreply.github.com> Date: Tue, 20 Dec 2022 10:47:56 -0500 Subject: [PATCH 1/6] update install.sh for dnf to work with wx --- install.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index 7fc2ae7..42b1c99 100755 --- a/install.sh +++ b/install.sh @@ -4,12 +4,15 @@ if which brew &> /dev/null; then brew install portaudio ffmpeg libav elif which apt-get &> /dev/null; then sudo apt-get update - sudo apt-get install \ + sudo apt-get install python3 \ portaudio19-dev libportaudio2 \ - ffmpeg \ - libav-tools + ffmpeg libav-tools +elif which dnf &> /dex/null; then + sudo dnf up + sudo dnf in python3-devel python3-pillow-tk \ + portaudio-devel ffmpeg python3-wxpython4 else echo "Your package manager is not currently supported." - echo "Currently supported managers are: brew, apt." + echo "Currently supported managers are: brew, apt, dnf." exit 1 fi From f4291a8cf819bcd5ed35e1dc46bd342b7dd030b2 Mon Sep 17 00:00:00 2001 From: qiyoulu Date: Tue, 27 Dec 2022 19:14:16 -0500 Subject: [PATCH 2/6] remove libav-tools dependency --- install.sh | 9 ++-- ultratrace.egg-info/PKG-INFO | 9 ++++ ultratrace.egg-info/SOURCES.txt | 63 ++++++++++++++++++++++++ ultratrace.egg-info/dependency_links.txt | 1 + ultratrace.egg-info/entry_points.txt | 2 + ultratrace.egg-info/requires.txt | 28 +++++++++++ ultratrace.egg-info/top_level.txt | 1 + 7 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 ultratrace.egg-info/PKG-INFO create mode 100644 ultratrace.egg-info/SOURCES.txt create mode 100644 ultratrace.egg-info/dependency_links.txt create mode 100644 ultratrace.egg-info/entry_points.txt create mode 100644 ultratrace.egg-info/requires.txt create mode 100644 ultratrace.egg-info/top_level.txt diff --git a/install.sh b/install.sh index 42b1c99..585368d 100755 --- a/install.sh +++ b/install.sh @@ -4,13 +4,12 @@ if which brew &> /dev/null; then brew install portaudio ffmpeg libav elif which apt-get &> /dev/null; then sudo apt-get update - sudo apt-get install python3 \ - portaudio19-dev libportaudio2 \ - ffmpeg libav-tools + sudo apt-get install python3 ffmpeg \ + portaudio19-dev libportaudio2 wxpython-tools elif which dnf &> /dex/null; then sudo dnf up - sudo dnf in python3-devel python3-pillow-tk \ - portaudio-devel ffmpeg python3-wxpython4 + sudo dnf in python3-devel ffmpeg-devel \ + portaudio-devel python3-wxpython4 else echo "Your package manager is not currently supported." echo "Currently supported managers are: brew, apt, dnf." diff --git a/ultratrace.egg-info/PKG-INFO b/ultratrace.egg-info/PKG-INFO new file mode 100644 index 0000000..9cc2f98 --- /dev/null +++ b/ultratrace.egg-info/PKG-INFO @@ -0,0 +1,9 @@ +Metadata-Version: 2.1 +Name: ultratrace +Version: 0.0.1 +Summary: A tool for manually annotating ultrasound tongue imaging (UTI) data +Author: Jonathan Washington +Author-email: jwashin1@swarthmore.edu +Provides-Extra: dev +License-File: LICENSE +License-File: AUTHORS diff --git a/ultratrace.egg-info/SOURCES.txt b/ultratrace.egg-info/SOURCES.txt new file mode 100644 index 0000000..e3938b3 --- /dev/null +++ b/ultratrace.egg-info/SOURCES.txt @@ -0,0 +1,63 @@ +AUTHORS +LICENSE +README.md +setup.cfg +setup.py +ultratrace.egg-info/PKG-INFO +ultratrace.egg-info/SOURCES.txt +ultratrace.egg-info/dependency_links.txt +ultratrace.egg-info/entry_points.txt +ultratrace.egg-info/requires.txt +ultratrace.egg-info/top_level.txt +ultratrace2/gui/__init__.py +ultratrace2/gui/themes.py +ultratrace2/gui/widgets/__init__.py +ultratrace2/gui/widgets/audio.py +ultratrace2/gui/widgets/container.py +ultratrace2/gui/widgets/control.py +ultratrace2/gui/widgets/dicom.py +ultratrace2/gui/widgets/div.py +ultratrace2/gui/widgets/filedialog.py +ultratrace2/gui/widgets/frame.py +ultratrace2/gui/widgets/spectrogram.py +ultratrace2/gui/widgets/textgrid.py +ultratrace2/gui/widgets/trace.py +ultratrace2/gui/widgets/undo.py +ultratrace2/gui/widgets/video.py +ultratrace2/gui/widgets/xhair.py +ultratrace2/gui/widgets/zoom_frame.py +ultratrace2/model/__init__.py +ultratrace2/model/color.py +ultratrace2/model/project.py +ultratrace2/model/trace.py +ultratrace2/model/xhair.py +ultratrace2/model/files/__init__.py +ultratrace2/model/files/bundle.py +ultratrace2/model/files/registry.py +ultratrace2/model/files/loaders/__init__.py +ultratrace2/model/files/loaders/base.py +ultratrace2/model/files/loaders/dicom.py +ultratrace2/model/files/loaders/flac.py +ultratrace2/model/files/loaders/measurement.py +ultratrace2/model/files/loaders/mp3.py +ultratrace2/model/files/loaders/ogg.py +ultratrace2/model/files/loaders/pydub.py +ultratrace2/model/files/loaders/textgrid.py +ultratrace2/model/files/loaders/wav.py +ultratrace2/model/files/loaders/tests/__init__.py +ultratrace2/model/files/loaders/tests/test_dicom.py +ultratrace2/model/files/loaders/tests/test_flac.py +ultratrace2/model/files/loaders/tests/test_mp3.py +ultratrace2/model/files/loaders/tests/test_ogg.py +ultratrace2/model/files/loaders/tests/test_textgrid.py +ultratrace2/model/files/loaders/tests/test_wav.py +ultratrace2/model/files/tests/__init__.py +ultratrace2/model/files/tests/test_bundle.py +ultratrace2/model/tests/__init__.py +ultratrace2/model/tests/test_color.py +ultratrace2/model/tests/test_project.py +ultratrace2/model/tests/test_trace.py +ultratrace2/model/tests/test_xhair.py +ultratrace2/tests/__init__.py +ultratrace2/tests/test_app.py +ultratrace2/utils/__init__.py \ No newline at end of file diff --git a/ultratrace.egg-info/dependency_links.txt b/ultratrace.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ultratrace.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/ultratrace.egg-info/entry_points.txt b/ultratrace.egg-info/entry_points.txt new file mode 100644 index 0000000..76ab3ea --- /dev/null +++ b/ultratrace.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +ultratrace2 = ultratrace2.__main__:main diff --git a/ultratrace.egg-info/requires.txt b/ultratrace.egg-info/requires.txt new file mode 100644 index 0000000..1a708f7 --- /dev/null +++ b/ultratrace.egg-info/requires.txt @@ -0,0 +1,28 @@ +cycler>=0.10.0 +kiwisolver>=1.1.0 +matplotlib>=3.1.2 +numpy>=1.17.4 +Pillow>=6.2.1 +praat-parselmouth>=0.4.0 +PyAudio==0.2.11 +pydicom==1.3.0 +pydub==0.23.1 +pyparsing==2.4.5 +python-dateutil==2.8.1 +six==1.13.0 +TextGrid==1.5 +tqdm==4.40.2 +ttkthemes==2.4.0 +xparser==0.0.4 +python-magic==0.4.15 + +[dev] +flake8 +black +mypy +pytest +numpy-stubs@ git+https://github.com/numpy/numpy-stubs.git@master +pytest-cov +pytest-mock +boto3 +mypy-boto3-s3 diff --git a/ultratrace.egg-info/top_level.txt b/ultratrace.egg-info/top_level.txt new file mode 100644 index 0000000..0d9dc3c --- /dev/null +++ b/ultratrace.egg-info/top_level.txt @@ -0,0 +1 @@ +ultratrace2 From e8533683173ef60875c3240dd711912e2f678ffe Mon Sep 17 00:00:00 2001 From: qiyoulu Date: Tue, 27 Dec 2022 19:17:04 -0500 Subject: [PATCH 3/6] fix gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 30ae261..755a4fc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ __pycache__ .mypy_cache .nox .coverage +.egg-info # local environment setup .env From 0fd985d85d84582f82362c5f8b08482025fadf21 Mon Sep 17 00:00:00 2001 From: qiyoulu Date: Tue, 27 Dec 2022 19:20:05 -0500 Subject: [PATCH 4/6] fix gitignore --- ultratrace.egg-info/PKG-INFO | 9 ---- ultratrace.egg-info/SOURCES.txt | 63 ------------------------ ultratrace.egg-info/dependency_links.txt | 1 - ultratrace.egg-info/entry_points.txt | 2 - ultratrace.egg-info/requires.txt | 28 ----------- ultratrace.egg-info/top_level.txt | 1 - 6 files changed, 104 deletions(-) delete mode 100644 ultratrace.egg-info/PKG-INFO delete mode 100644 ultratrace.egg-info/SOURCES.txt delete mode 100644 ultratrace.egg-info/dependency_links.txt delete mode 100644 ultratrace.egg-info/entry_points.txt delete mode 100644 ultratrace.egg-info/requires.txt delete mode 100644 ultratrace.egg-info/top_level.txt diff --git a/ultratrace.egg-info/PKG-INFO b/ultratrace.egg-info/PKG-INFO deleted file mode 100644 index 9cc2f98..0000000 --- a/ultratrace.egg-info/PKG-INFO +++ /dev/null @@ -1,9 +0,0 @@ -Metadata-Version: 2.1 -Name: ultratrace -Version: 0.0.1 -Summary: A tool for manually annotating ultrasound tongue imaging (UTI) data -Author: Jonathan Washington -Author-email: jwashin1@swarthmore.edu -Provides-Extra: dev -License-File: LICENSE -License-File: AUTHORS diff --git a/ultratrace.egg-info/SOURCES.txt b/ultratrace.egg-info/SOURCES.txt deleted file mode 100644 index e3938b3..0000000 --- a/ultratrace.egg-info/SOURCES.txt +++ /dev/null @@ -1,63 +0,0 @@ -AUTHORS -LICENSE -README.md -setup.cfg -setup.py -ultratrace.egg-info/PKG-INFO -ultratrace.egg-info/SOURCES.txt -ultratrace.egg-info/dependency_links.txt -ultratrace.egg-info/entry_points.txt -ultratrace.egg-info/requires.txt -ultratrace.egg-info/top_level.txt -ultratrace2/gui/__init__.py -ultratrace2/gui/themes.py -ultratrace2/gui/widgets/__init__.py -ultratrace2/gui/widgets/audio.py -ultratrace2/gui/widgets/container.py -ultratrace2/gui/widgets/control.py -ultratrace2/gui/widgets/dicom.py -ultratrace2/gui/widgets/div.py -ultratrace2/gui/widgets/filedialog.py -ultratrace2/gui/widgets/frame.py -ultratrace2/gui/widgets/spectrogram.py -ultratrace2/gui/widgets/textgrid.py -ultratrace2/gui/widgets/trace.py -ultratrace2/gui/widgets/undo.py -ultratrace2/gui/widgets/video.py -ultratrace2/gui/widgets/xhair.py -ultratrace2/gui/widgets/zoom_frame.py -ultratrace2/model/__init__.py -ultratrace2/model/color.py -ultratrace2/model/project.py -ultratrace2/model/trace.py -ultratrace2/model/xhair.py -ultratrace2/model/files/__init__.py -ultratrace2/model/files/bundle.py -ultratrace2/model/files/registry.py -ultratrace2/model/files/loaders/__init__.py -ultratrace2/model/files/loaders/base.py -ultratrace2/model/files/loaders/dicom.py -ultratrace2/model/files/loaders/flac.py -ultratrace2/model/files/loaders/measurement.py -ultratrace2/model/files/loaders/mp3.py -ultratrace2/model/files/loaders/ogg.py -ultratrace2/model/files/loaders/pydub.py -ultratrace2/model/files/loaders/textgrid.py -ultratrace2/model/files/loaders/wav.py -ultratrace2/model/files/loaders/tests/__init__.py -ultratrace2/model/files/loaders/tests/test_dicom.py -ultratrace2/model/files/loaders/tests/test_flac.py -ultratrace2/model/files/loaders/tests/test_mp3.py -ultratrace2/model/files/loaders/tests/test_ogg.py -ultratrace2/model/files/loaders/tests/test_textgrid.py -ultratrace2/model/files/loaders/tests/test_wav.py -ultratrace2/model/files/tests/__init__.py -ultratrace2/model/files/tests/test_bundle.py -ultratrace2/model/tests/__init__.py -ultratrace2/model/tests/test_color.py -ultratrace2/model/tests/test_project.py -ultratrace2/model/tests/test_trace.py -ultratrace2/model/tests/test_xhair.py -ultratrace2/tests/__init__.py -ultratrace2/tests/test_app.py -ultratrace2/utils/__init__.py \ No newline at end of file diff --git a/ultratrace.egg-info/dependency_links.txt b/ultratrace.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/ultratrace.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ultratrace.egg-info/entry_points.txt b/ultratrace.egg-info/entry_points.txt deleted file mode 100644 index 76ab3ea..0000000 --- a/ultratrace.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -ultratrace2 = ultratrace2.__main__:main diff --git a/ultratrace.egg-info/requires.txt b/ultratrace.egg-info/requires.txt deleted file mode 100644 index 1a708f7..0000000 --- a/ultratrace.egg-info/requires.txt +++ /dev/null @@ -1,28 +0,0 @@ -cycler>=0.10.0 -kiwisolver>=1.1.0 -matplotlib>=3.1.2 -numpy>=1.17.4 -Pillow>=6.2.1 -praat-parselmouth>=0.4.0 -PyAudio==0.2.11 -pydicom==1.3.0 -pydub==0.23.1 -pyparsing==2.4.5 -python-dateutil==2.8.1 -six==1.13.0 -TextGrid==1.5 -tqdm==4.40.2 -ttkthemes==2.4.0 -xparser==0.0.4 -python-magic==0.4.15 - -[dev] -flake8 -black -mypy -pytest -numpy-stubs@ git+https://github.com/numpy/numpy-stubs.git@master -pytest-cov -pytest-mock -boto3 -mypy-boto3-s3 diff --git a/ultratrace.egg-info/top_level.txt b/ultratrace.egg-info/top_level.txt deleted file mode 100644 index 0d9dc3c..0000000 --- a/ultratrace.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -ultratrace2 From 272189103a1f37286a842e826e8a326beddb19da Mon Sep 17 00:00:00 2001 From: qiyoulu Date: Sat, 31 Dec 2022 14:31:05 -0500 Subject: [PATCH 5/6] rm ttk uses --- ultratrace/__init__.py | 0 ultratrace/__main__.py | 615 +++++++++++++++--------------- ultratrace/modules/control.py | 83 ++-- ultratrace/modules/dicom.py | 94 ++--- ultratrace/modules/playback.py | 143 +++---- ultratrace/modules/search.py | 14 +- ultratrace/modules/spectrogram.py | 86 +++-- ultratrace/modules/trace.py | 245 ++++++------ ultratrace/widgets/__init__.py | 10 +- 9 files changed, 663 insertions(+), 627 deletions(-) delete mode 100644 ultratrace/__init__.py diff --git a/ultratrace/__init__.py b/ultratrace/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ultratrace/__main__.py b/ultratrace/__main__.py index 93134ca..ca2fca7 100644 --- a/ultratrace/__main__.py +++ b/ultratrace/__main__.py @@ -1,318 +1,317 @@ #!/usr/bin/env python3 -#import modules +# import modules from . import modules -from . import util +# from . import util from .util.logging import * -from .widgets import Header +# from .widgets import Header import argparse -import os -import PIL +# import os +# import PIL import wx + class Frame(wx.Frame): - - def __init__(self, parent, title): - super(Frame, self).__init__(parent, title=title) - - info( 'initializing UltraTrace' ) - - self.initialise() - - def initialise(self): - - # check if we were passed a command line argument - parser = argparse.ArgumentParser(prog='UltraTrace') - parser.add_argument('path', help='path (unique to a participant) where subdirectories contain raw data', default=None, nargs='?') - - args = parser.parse_args() - - # initialize data module - self.Data = modules.Metadata( self, args.path ) - - # initialize the main app widgets - self.setWidgetDefaults() - self.buildWidgetSkeleton() - - # initialize other modules - #self.Control = modules.Control(self) - self.Trace = modules.Trace(self) - self.Dicom = modules.Dicom(self) - self.Audio = modules.Playback(self) - self.TextGrid = modules.TextGrid(self) - #self.Spectrogram = modules.Spectrogram(self) - #self.Search = modules.Search(self) - - info( ' - loading widgets' ) - - #self.filesUpdate() - - # to deal with resize handler being called multiple times - # in a single window resize - self.isResizing = False - - #self.oldwidth = self.winfo_width() - - #self.after(300,self.afterstartup) - - def setWidgetDefaults(self): - ''' - Need to set up some defaults here before building Tk widgets (this is specifically - true w/r/t the StringVars) - ''' - self.currentFID = 0 # file index w/in list of sorted files - self.frame = 0 # current frame of dicom file - self.isClicked = False # used in handling of canvas click events - self.isDragging = False # used in handling of canvas click events - # self.resized = False #for changing widgets after window resize - self.selectBoxX = False - self.selectBoxY = False - - # some styling - #self.fontStyle = Style() - #if util.get_platform() == 'Darwin': - # self.fontStyle.configure('symbol.TButton', font=('DejaVu Serif', 26)) - #else: - # self.fontStyle.configure('symbol.TButton', font=('DejaVu Serif', 19)) - - # declare string variables - #self.currentFileSV = StringVar(self) - #self.frameSV = StringVar(self) - - # initialize string variables - self.currentFileSV = self.Data.files[ self.currentFID ] - self.frameSV = '1' - - def buildWidgetSkeleton(self): - ''' - Builds the basic skeleton of our app widgets. - - items marked with (*) are built directly in this function - - items marked with (~) are built by the individual modules - # WARNING: out of date diagram - .________________________________________. - | ROOT | - | .____________________________________. | - | | TOP* | | - | | ._______________. .______________. | | - | | | LEFT* | | RIGHT* | | | - | | | - file nav* | | - dicom~ | | | - | | | - frame nav*| | | | | - | | | - traces~ | | | | | - | | | - undo~ | | | | | - | | \_______________/ \______________/ | | - | \____________________________________/ | - | | - | .____________________________________. | - | | BOTTOM* | | - | | - spectrogram~ | | - | | - textgrid~ | | - | \____________________________________/ | - \________________________________________/ - ''' - # main Frame skeleton - self.panel = wx.Panel(self) - self.hbox = wx.BoxSizer(wx.HORIZONTAL) - self.grid = wx.FlexGridSizer(2, 2, 2, 2) - - #self.TOP = wx.Panel(self) - #self.TOP = Frame(self) - #self.TOP.columnconfigure(1,weight=1, minsize=320) - #self.TOP.rowconfigure(0,weight=1, minsize=240) - #self.LEFT = Frame(self.TOP) - ## self.LEFT.rowconfigure(0,weight=1) - ## self.LEFT.columnconfigure(0,weight=1) - #self.RIGHT = Frame(self.TOP) - #self.RIGHT.rowconfigure(0,weight=1) - #self.RIGHT.columnconfigure(0,weight=1) - #self.BOTTOM = Frame(self) - ## self.BOTTOM.columnconfigure(0,weight=1) - #self.BOTTOM.columnconfigure(1,weight=1) - ## self.BOTTOM.rowconfigure(0,weight=1) - ## self.TOP.grid( row=0, column=0, sticky='nw') - ## self.LEFT.grid( row=0, sticky='n' ) - ## self.RIGHT.grid( row=0, column=1) - ## self.BOTTOM.grid( row=1, column=0, sticky='e') - #self.TOP.grid( row=0, column=0, sticky='nesw') - #self.LEFT.grid( row=0, sticky='nesw' ) - #self.RIGHT.grid( row=0, column=1, sticky='nesw') - #self.BOTTOM.grid( row=1, column=0, sticky='nesw') - #self.pady=3 - #self.columnconfigure(0,weight=1) - #self.rowconfigure(0,weight=1) - #text1 = wx.StaticText(panel, label="controls here") - text2 = wx.StaticText(self.panel, label="US here") - text3 = wx.StaticText(self.panel, label="labels here") - text4 = wx.StaticText(self.panel, label="audio and textgrids here") - - self.audioTGBox = wx.BoxSizer(wx.VERTICAL) - self.audioTGBox.Add(text4) - - self.ultrasoundBox = wx.BoxSizer(wx.VERTICAL) - self.ultrasoundBox.Add(text2) - - self.controlBox = wx.BoxSizer(wx.VERTICAL) - #### Buttons - #### FileSelectorBox - ##### FileControlBox - self.fileControlBox = wx.BoxSizer(wx.HORIZONTAL) - self.fileSelectorOpen = wx.Button(self, id=wx.ID_OPEN)#, style=wx.BU_NOTEXT) - self.fileControlBox.Add(self.fileSelectorOpen) - - self.fileSelectorBox = wx.StaticBoxSizer(wx.VERTICAL, self, label="Files") - self.fileSelectorBox.Add(self.fileControlBox) - self.fileSelector = wx.ListCtrl(self, -1, style=wx.LC_REPORT) - self.fileSelector.InsertColumn(0, 'name', width=200) - self.fileSelector.InsertColumn(1, 'annotated', wx.LIST_FORMAT_RIGHT, width=50) - #self.fileSelector.InsertItem(0, 'test') - for filedata in self.Data.files: - self.fileSelector.InsertItem(self.fileSelector.GetItemCount(), filedata) - - self.fileSelectorBox.Add(self.fileSelector, 1, wx.EXPAND) - - #### FrameSelectorBox - self.frameSelectorBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="Frames") - self.frameSelectorPrev = wx.Button(self, label="<", style=wx.BU_EXACTFIT) - self.frameSelectorText = wx.TextCtrl(self, style=wx.TE_RIGHT, value=self.frameSV) - self.frameSelectorNext = wx.Button(self, label=">", style=wx.BU_EXACTFIT) - self.buttonPlayPause = wx.Button(self, label="⏯")# style=wx.BU_EXACTFIT) - self.setCharSize(self.frameSelectorNext, 1) - self.setCharSize(self.frameSelectorText, 5) - self.setCharSize(self.frameSelectorPrev, 1) - self.setCharSize(self.buttonPlayPause, 3) - - #self.frameSelectorBox.AddMany([(self.frameSelectorPrev),(self.frameSelectorText),(self.frameSelectorNext),(self.buttonPlayPause)]) - self.frameSelectorBox.Add(self.frameSelectorPrev, flag=wx.LEFT, border=10) - self.frameSelectorBox.Add(self.frameSelectorText) - self.frameSelectorBox.Add(self.frameSelectorNext, flag=wx.RIGHT, border=10) - self.frameSelectorBox.Add(self.buttonPlayPause, border=10) - - #### AnnotationsBox - self.annotationsBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="Annotations") - self.buttonSelectAll = self.newIconButton(wx.ID_SELECTALL, "gtk-select-all") - self.buttonCopy = self.newIconButton(wx.ID_COPY, wx.ART_COPY) - #self.buttonCopy = wx.Button(self, id=wx.ID_COPY, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - #self.buttonCopy = wx.BitmapButton(self, id=wx.ID_COPY, bitmap=wx.ArtProvider.GetBitmap(wx.ART_COPY), size=(32,32)) - self.buttonPaste = self.newIconButton(wx.ID_PASTE, wx.ART_PASTE) #wx.Button(self, id=wx.ID_PASTE, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - #self.buttonPaste = wx.BitmapButton(self, id=wx.ID_PASTE, bitmap=wx.ArtProvider.GetBitmap(wx.ART_PASTE), size=(32,32)) - self.buttonDelete = self.newIconButton(wx.ID_DELETE, wx.ART_DELETE) #wx.Button(self, id=wx.ID_DELETE, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonZoomIn = self.newIconButton(wx.ID_ZOOM_IN, "gtk-zoom-in") #wx.Button(self, id=wx.ID_ZOOM_IN, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonZoomOut = self.newIconButton(wx.ID_ZOOM_OUT, "gtk-zoom-out") #wx.Button(self, id=wx.ID_ZOOM_OUT, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonZoomFit = self.newIconButton(wx.ID_ZOOM_FIT, "gtk-zoom-fit") #wx.Button(self, id=wx.ID_ZOOM_FIT, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonUndo = self.newIconButton(wx.ID_UNDO, wx.ART_UNDO) - self.buttonRedo = self.newIconButton(wx.ID_REDO, wx.ART_REDO) - - #self.annotationsBox.AddMany([(self.buttonSelectAll),(self.buttonCopy),(self.buttonPaste),(self.buttonDelete)]) - self.annotationsBox.Add(self.buttonSelectAll, flag=wx.LEFT, border=10) - self.annotationsBox.Add(self.buttonCopy, flag=wx.BOTTOM, border=10) - self.annotationsBox.Add(self.buttonPaste) - self.annotationsBox.Add(self.buttonDelete) - self.annotationsBox.Add((20,0)) - self.annotationsBox.Add(self.buttonUndo) - self.annotationsBox.Add(self.buttonRedo, flag=wx.RIGHT, border=10) - - - #### ViewBox - self.viewBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="View") - self.viewBox.Add(self.buttonZoomIn, flag=wx.LEFT, border=10) - self.viewBox.Add(self.buttonZoomOut, flag=wx.BOTTOM, border=10) - self.viewBox.Add(self.buttonZoomFit, flag=wx.RIGHT, border=10) - - #### LayersBox - #self.layersBox = wx.StaticBoxSizer(wx.VERTICAL, self, label="Layers") - - ## add all sections to the control box - self.controlBox.AddMany([(self.fileSelectorBox, 1, wx.EXPAND), ((0,10)), (self.frameSelectorBox,0,wx.EXPAND), ((0,10)), (self.annotationsBox, 0, wx.EXPAND), ((0,10)), (self.viewBox,0,wx.EXPAND), ((0,10)) ])#(self.layersBox,0,wx.EXPAND)]) - - self.grid.AddMany([(self.controlBox, 1, wx.EXPAND), (self.ultrasoundBox, 1, wx.EXPAND), (text3), (self.audioTGBox)]) - - self.grid.AddGrowableRow(0,1) - self.grid.AddGrowableCol(0,1) - - self.hbox.Add(self.grid, proportion=1, flag=wx.ALL|wx.EXPAND, border=2) - self.panel.SetSizer(self.hbox) - self.panel.Fit() - ## navigate between all available filenames in this directory - #self.filesFrame = Frame(self.LEFT)#, pady=7) - #self.filesPrevBtn = Button(self.filesFrame, text='<', command=self.filesPrev, takefocus=0, width="1.5") - #self.filesJumpToMenu = OptionMenu(self.filesFrame, self.currentFileSV, self.Data.files[0], *self.Data.files, command=self.filesJumpTo) - #self.filesNextBtn= Button(self.filesFrame, text='>', command=self.filesNext, takefocus=0, width="1.5") - #self.filesFrame.grid( row=1 ) - #self.filesPrevBtn.grid( row=1, column=0 ) - #self.filesJumpToMenu.grid( row=1, column=1 ) - #self.filesNextBtn.grid(row=1, column=2 ) - #Header(self.filesFrame, text="Recording").grid( row=0, column=0, columnspan=3 ) - - ## navigate between frames - #self.framesFrame = Frame(self.LEFT)#, pady=7) - #self.framesSubframe = Frame(self.framesFrame) - #self.framesPrevBtn = Button(self.framesSubframe, text='<', command=self.framesPrev, takefocus=0, width="1.5") - #self.framesEntryText = Entry(self.framesSubframe, width=5, textvariable=self.frameSV) - #self.framesEntryBtn = Button(self.framesSubframe, text='Go', command=self.framesJumpTo, takefocus=0, width="3") - #self.framesNextBtn= Button(self.framesSubframe, text='>', command=self.framesNext, takefocus=0, width="1.5") - #self.framesHeader = Header(self.framesFrame, text="Frame") - #self.framesFrame.grid( row=3 ) - #self.framesSubframe.grid( row=1 ) - - ## non-module-specific bindings - #if util.get_platform() == 'Linux': - # self.bind('', self.filesPrev ) - # self.bind('', self.filesNext ) - #else: - # self.bind('', self.filesPrev ) - # self.bind('', self.filesNext ) - #self.bind('', self.framesPrev ) - #self.bind('', self.framesNext ) - #self.bind('', self.onBackspace ) - #self.bind('', self.getWinSize) - #self.bind('', self.onRelease) - #self.bind('', self.onDoubleClick) - #self.bind('', self.onEscape ) - ## self.count = 0 - - #self.framesEntryText.bind('', self.unfocusAndJump) - #self.framesEntryText.bind('', lambda ev: self.framesFrame.focus()) - - # force window to front - #self.lift() - - def setCharSize(self, obj, sizeInChar): - size = obj.GetSizeFromTextSize(obj.GetTextExtent('9'*sizeInChar)) - obj.SetInitialSize(size) - obj.SetSize(size) - - def newIconButton(self, wxId, wxBmp): - #button = wx.Button(self, id=wxId, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - #button.SetBitmapLabel(wx.ArtProvider.GetBitmap(wxBmp,wx.ART_MENU)) - button = wx.BitmapButton(self, id=wxId, bitmap=wx.ArtProvider.GetBitmap(wxBmp), size=(32,32)) - return button - - def lift(self): - ''' - Bring window to front (doesn't shift focus to window) - ''' - self.attributes('-topmost', 1) - self.attributes('-topmost', 0) - - def update(self): - pass - - def geometry(self): - pass - - - -if __name__=='__main__': - # app.mainloop() - app = wx.App() - mainFrame = Frame(None, title='UltraTrace') - mainFrame.Show() - while True: - try: - app.MainLoop() - break - except: - print("crashed") - #except UnicodeDecodeError as e: - # error(e) + + def __init__(self, parent, title): + super(Frame, self).__init__(parent, title=title) + + info('initializing UltraTrace') + + self.initialise() + + def initialise(self): + + # check if we were passed a command line argument + parser = argparse.ArgumentParser(prog='UltraTrace') + parser.add_argument('path', help='path (unique to a participant) where subdirectories contain raw data', default=None, nargs='?') + + args = parser.parse_args() + + # initialize data module + self.Data = modules.Metadata(self, args.path) + + # initialize the main app widgets + self.setWidgetDefaults() + self.buildWidgetSkeleton() + + # initialize other modules + # self.Control = modules.Control(self) + self.Trace = modules.Trace(self) + self.Dicom = modules.Dicom(self) + self.Audio = modules.Playback(self) + self.TextGrid = modules.TextGrid(self) + # self.Spectrogram = modules.Spectrogram(self) + # self.Search = modules.Search(self) + + info(' - loading widgets') + + # self.filesUpdate() + + # to deal with resize handler being called multiple times + # in a single window resize + self.isResizing = False + + # self.oldwidth = self.winfo_width() + + # self.after(300,self.afterstartup) + + def setWidgetDefaults(self): + ''' + Need to set up some defaults here before building Tk widgets (this is specifically + true w/r/t the StringVars) + ''' + self.currentFID = 0 # file index w/in list of sorted files + self.frame = 0 # current frame of dicom file + self.isClicked = False # used in handling of canvas click events + self.isDragging = False # used in handling of canvas click events + # self.resized = False #for changing widgets after window resize + self.selectBoxX = False + self.selectBoxY = False + + # some styling + # self.fontStyle = Style() + # if util.get_platform() == 'Darwin': + # self.fontStyle.configure('symbol.TButton', font=('DejaVu Serif', 26)) + # else: + # self.fontStyle.configure('symbol.TButton', font=('DejaVu Serif', 19)) + + # declare string variables + # self.currentFileSV = StringVar(self) + # self.frameSV = StringVar(self) + + # initialize string variables + self.currentFileSV = self.Data.files[self.currentFID] + self.frameSV = '1' + + def buildWidgetSkeleton(self): + ''' + Builds the basic skeleton of our app widgets. + - items marked with (*) are built directly in this function + - items marked with (~) are built by the individual modules + # WARNING: out of date diagram + .________________________________________. + | ROOT | + | .____________________________________. | + | | TOP* | | + | | ._______________. .______________. | | + | | | LEFT* | | RIGHT* | | | + | | | - file nav* | | - dicom~ | | | + | | | - frame nav*| | | | | + | | | - traces~ | | | | | + | | | - undo~ | | | | | + | | \_______________/ \______________/ | | + | \____________________________________/ | + | | + | .____________________________________. | + | | BOTTOM* | | + | | - spectrogram~ | | + | | - textgrid~ | | + | \____________________________________/ | + \________________________________________/ + ''' + # main Frame skeleton + self.panel = wx.Panel(self) + self.hbox = wx.BoxSizer(wx.HORIZONTAL) + self.grid = wx.FlexGridSizer(2, 2, 2, 2) + + # self.TOP = wx.Panel(self) + # self.TOP = Frame(self) + # self.TOP.columnconfigure(1,weight=1, minsize=320) + # self.TOP.rowconfigure(0,weight=1, minsize=240) + # self.LEFT = Frame(self.TOP) + # self.LEFT.rowconfigure(0,weight=1) + # self.LEFT.columnconfigure(0,weight=1) + # self.RIGHT = Frame(self.TOP) + # self.RIGHT.rowconfigure(0,weight=1) + # self.RIGHT.columnconfigure(0,weight=1) + # self.BOTTOM = Frame(self) + # self.BOTTOM.columnconfigure(0,weight=1) + # self.BOTTOM.columnconfigure(1,weight=1) + # self.BOTTOM.rowconfigure(0,weight=1) + # self.TOP.grid( row=0, column=0, sticky='nw') + # self.LEFT.grid( row=0, sticky='n' ) + # self.RIGHT.grid( row=0, column=1) + # self.BOTTOM.grid( row=1, column=0, sticky='e') + # self.TOP.grid( row=0, column=0, sticky='nesw') + # self.LEFT.grid( row=0, sticky='nesw' ) + # self.RIGHT.grid( row=0, column=1, sticky='nesw') + # self.BOTTOM.grid( row=1, column=0, sticky='nesw') + # self.pady=3 + # self.columnconfigure(0,weight=1) + # self.rowconfigure(0,weight=1) + # text1 = wx.StaticText(panel, label="controls here") + text2 = wx.StaticText(self.panel, label="US here") + text3 = wx.StaticText(self.panel, label="labels here") + text4 = wx.StaticText(self.panel, label="audio and textgrids here") + + self.audioTGBox = wx.BoxSizer(wx.VERTICAL) + self.audioTGBox.Add(text4) + + self.ultrasoundBox = wx.BoxSizer(wx.VERTICAL) + self.ultrasoundBox.Add(text2) + + self.controlBox = wx.BoxSizer(wx.VERTICAL) + # Buttons + # FileSelectorBox + # FileControlBox + self.fileControlBox = wx.BoxSizer(wx.HORIZONTAL) + self.fileSelectorOpen = wx.Button(self, id=wx.ID_OPEN) # , style=wx.BU_NOTEXT) + self.fileControlBox.Add(self.fileSelectorOpen) + + self.fileSelectorBox = wx.StaticBoxSizer(wx.VERTICAL, self, label="Files") + self.fileSelectorBox.Add(self.fileControlBox) + self.fileSelector = wx.ListCtrl(self, -1, style=wx.LC_REPORT) + self.fileSelector.InsertColumn(0, 'name', width=200) + self.fileSelector.InsertColumn(1, 'annotated', wx.LIST_FORMAT_RIGHT, width=50) + # self.fileSelector.InsertItem(0, 'test') + for filedata in self.Data.files: + self.fileSelector.InsertItem(self.fileSelector.GetItemCount(), filedata) + + self.fileSelectorBox.Add(self.fileSelector, 1, wx.EXPAND) + + # FrameSelectorBox + self.frameSelectorBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="Frames") + self.frameSelectorPrev = wx.Button(self, label="<", style=wx.BU_EXACTFIT) + self.frameSelectorText = wx.TextCtrl(self, style=wx.TE_RIGHT, value=self.frameSV) + self.frameSelectorNext = wx.Button(self, label=">", style=wx.BU_EXACTFIT) + self.buttonPlayPause = wx.Button(self, label="⏯") # style=wx.BU_EXACTFIT) + self.setCharSize(self.frameSelectorNext, 1) + self.setCharSize(self.frameSelectorText, 5) + self.setCharSize(self.frameSelectorPrev, 1) + self.setCharSize(self.buttonPlayPause, 3) + + # self.frameSelectorBox.AddMany([(self.frameSelectorPrev),(self.frameSelectorText),(self.frameSelectorNext),(self.buttonPlayPause)]) + self.frameSelectorBox.Add(self.frameSelectorPrev, flag=wx.LEFT, border=10) + self.frameSelectorBox.Add(self.frameSelectorText) + self.frameSelectorBox.Add(self.frameSelectorNext, flag=wx.RIGHT, border=10) + self.frameSelectorBox.Add(self.buttonPlayPause, border=10) + + # AnnotationsBox + self.annotationsBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="Annotations") + self.buttonSelectAll = self.newIconButton(wx.ID_SELECTALL, "gtk-select-all") + self.buttonCopy = self.newIconButton(wx.ID_COPY, wx.ART_COPY) + # self.buttonCopy = wx.Button(self, id=wx.ID_COPY, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + # self.buttonCopy = wx.BitmapButton(self, id=wx.ID_COPY, bitmap=wx.ArtProvider.GetBitmap(wx.ART_COPY), size=(32,32)) + self.buttonPaste = self.newIconButton(wx.ID_PASTE, wx.ART_PASTE) # wx.Button(self, id=wx.ID_PASTE, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + # self.buttonPaste = wx.BitmapButton(self, id=wx.ID_PASTE, bitmap=wx.ArtProvider.GetBitmap(wx.ART_PASTE), size=(32,32)) + self.buttonDelete = self.newIconButton(wx.ID_DELETE, wx.ART_DELETE) # wx.Button(self, id=wx.ID_DELETE, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + self.buttonZoomIn = self.newIconButton(wx.ID_ZOOM_IN, "gtk-zoom-in") # wx.Button(self, id=wx.ID_ZOOM_IN, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + self.buttonZoomOut = self.newIconButton(wx.ID_ZOOM_OUT, "gtk-zoom-out") # wx.Button(self, id=wx.ID_ZOOM_OUT, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + self.buttonZoomFit = self.newIconButton(wx.ID_ZOOM_FIT, "gtk-zoom-fit") # wx.Button(self, id=wx.ID_ZOOM_FIT, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + self.buttonUndo = self.newIconButton(wx.ID_UNDO, wx.ART_UNDO) + self.buttonRedo = self.newIconButton(wx.ID_REDO, wx.ART_REDO) + + # self.annotationsBox.AddMany([(self.buttonSelectAll),(self.buttonCopy),(self.buttonPaste),(self.buttonDelete)]) + self.annotationsBox.Add(self.buttonSelectAll, flag=wx.LEFT, border=10) + self.annotationsBox.Add(self.buttonCopy, flag=wx.BOTTOM, border=10) + self.annotationsBox.Add(self.buttonPaste) + self.annotationsBox.Add(self.buttonDelete) + self.annotationsBox.Add((20, 0)) + self.annotationsBox.Add(self.buttonUndo) + self.annotationsBox.Add(self.buttonRedo, flag=wx.RIGHT, border=10) + + # ViewBox + self.viewBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="View") + self.viewBox.Add(self.buttonZoomIn, flag=wx.LEFT, border=10) + self.viewBox.Add(self.buttonZoomOut, flag=wx.BOTTOM, border=10) + self.viewBox.Add(self.buttonZoomFit, flag=wx.RIGHT, border=10) + + # LayersBox + # self.layersBox = wx.StaticBoxSizer(wx.VERTICAL, self, label="Layers") + + # add all sections to the control box + self.controlBox.AddMany([(self.fileSelectorBox, 1, wx.EXPAND), ((0, 10)), (self.frameSelectorBox, 0, wx.EXPAND), ((0, 10)), (self.annotationsBox, 0, wx.EXPAND), ((0, 10)), (self.viewBox, 0, wx.EXPAND), ((0, 10))]) # (self.layersBox,0,wx.EXPAND)]) + + self.grid.AddMany([(self.controlBox, 1, wx.EXPAND), (self.ultrasoundBox, 1, wx.EXPAND), (text3), (self.audioTGBox)]) + + self.grid.AddGrowableRow(0, 1) + self.grid.AddGrowableCol(0, 1) + + self.hbox.Add(self.grid, proportion=1, flag=wx.ALL | wx.EXPAND, border=2) + self.panel.SetSizer(self.hbox) + self.panel.Fit() + # navigate between all available filenames in this directory + # self.filesFrame = Frame(self.LEFT)#, pady=7) + # self.filesPrevBtn = Button(self.filesFrame, text='<', command=self.filesPrev, takefocus=0, width="1.5") + # self.filesJumpToMenu = OptionMenu(self.filesFrame, self.currentFileSV, self.Data.files[0], *self.Data.files, command=self.filesJumpTo) + # self.filesNextBtn= Button(self.filesFrame, text='>', command=self.filesNext, takefocus=0, width="1.5") + # self.filesFrame.grid( row=1 ) + # self.filesPrevBtn.grid( row=1, column=0 ) + # self.filesJumpToMenu.grid( row=1, column=1 ) + # self.filesNextBtn.grid(row=1, column=2 ) + # Header(self.filesFrame, text="Recording").grid( row=0, column=0, columnspan=3 ) + + # navigate between frames + # self.framesFrame = Frame(self.LEFT)#, pady=7) + # self.framesSubframe = Frame(self.framesFrame) + # self.framesPrevBtn = Button(self.framesSubframe, text='<', command=self.framesPrev, takefocus=0, width="1.5") + # self.framesEntryText = Entry(self.framesSubframe, width=5, textvariable=self.frameSV) + # self.framesEntryBtn = Button(self.framesSubframe, text='Go', command=self.framesJumpTo, takefocus=0, width="3") + # self.framesNextBtn= Button(self.framesSubframe, text='>', command=self.framesNext, takefocus=0, width="1.5") + # self.framesHeader = Header(self.framesFrame, text="Frame") + # self.framesFrame.grid( row=3 ) + # self.framesSubframe.grid( row=1 ) + + # non-module-specific bindings + # if util.get_platform() == 'Linux': + # self.bind('', self.filesPrev ) + # self.bind('', self.filesNext ) + # else: + # self.bind('', self.filesPrev ) + # self.bind('', self.filesNext ) + # self.bind('', self.framesPrev ) + # self.bind('', self.framesNext ) + # self.bind('', self.onBackspace ) + # self.bind('', self.getWinSize) + # self.bind('', self.onRelease) + # self.bind('', self.onDoubleClick) + # self.bind('', self.onEscape ) + # self.count = 0 + + # self.framesEntryText.bind('', self.unfocusAndJump) + # self.framesEntryText.bind('', lambda ev: self.framesFrame.focus()) + + # force window to front + # self.lift() + + def setCharSize(self, obj, sizeInChar): + size = obj.GetSizeFromTextSize(obj.GetTextExtent('9' * sizeInChar)) + obj.SetInitialSize(size) + obj.SetSize(size) + + def newIconButton(self, wxId, wxBmp): + # button = wx.Button(self, id=wxId, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) + # button.SetBitmapLabel(wx.ArtProvider.GetBitmap(wxBmp,wx.ART_MENU)) + button = wx.BitmapButton(self, id=wxId, bitmap=wx.ArtProvider.GetBitmap(wxBmp), size=(32, 32)) + return button + + def lift(self): + ''' + Bring window to front (doesn't shift focus to window) + ''' + self.attributes('-topmost', 1) + self.attributes('-topmost', 0) + + def update(self): + pass + + def geometry(self): + pass + + +if __name__ == '__main__': + # app.mainloop() + app = wx.App() + mainFrame = Frame(None, title='UltraTrace') + mainFrame.Show() + while True: + try: + app.MainLoop() + break + except: + print("crashed") + # except UnicodeDecodeError as e: + # error(e) diff --git a/ultratrace/modules/control.py b/ultratrace/modules/control.py index 4757a30..6928cfd 100644 --- a/ultratrace/modules/control.py +++ b/ultratrace/modules/control.py @@ -1,10 +1,11 @@ from .base import Module from .. import util from ..util.logging import * -from ..widgets import Header +# from ..widgets import Header + +# from tkinter.ttk import Button, Frame +# from tkinter import PhotoImage -from tkinter.ttk import Button, Frame -from tkinter import PhotoImage class Control(Module): ''' @@ -19,8 +20,9 @@ class Control(Module): - change frames - Dicom.resetZoom() ''' + def __init__(self, app): - info( ' - initializing module: Control' ) + info(' - initializing module: Control') # reference to our main object containing other functionality managers self.app = app # initialize our stacks @@ -37,42 +39,49 @@ def __init__(self, app): # bind Ctrl+z to UNDO and Ctrl+Shift+Z to REDO if util.get_platform() == 'Linux': - self.app.bind('', self.undo ) - self.app.bind('', self.redo ) + self.app.bind('', self.undo) + self.app.bind('', self.redo) else: - self.app.bind('', self.undo ) - self.app.bind('', self.redo ) + self.app.bind('', self.undo) + self.app.bind('', self.redo) # also make some buttons and bind them - self.frame = Frame(self.app.LEFT)#, pady=7) - self.frame.grid( row=5 ) + self.frame = Frame(self.app.LEFT) # , pady=7) + self.frame.grid(row=5) self.header = Header(self.frame, text="Points") self.selectAllBtn = Button(self.frame, text='⬚', command=self.selectAll, takefocus=0, style="symbol.TButton", width=1.5) self.copyBtn = Button(self.frame, image=self.img_copy, command=self.copy, takefocus=0) # FIXME: add tooltip for "Copy" - self.pasteBtn = Button(self.frame, image=self.img_paste, command=self.paste, takefocus=0) # FIXME: add tooltip for "Paste" + self.pasteBtn = Button(self.frame, image=self.img_paste, command=self.paste, takefocus=0) # FIXME: add tooltip for "Paste" self.undoBtn = Button(self.frame, text='↶', command=self.undo, takefocus=0, width=1.5, style="symbol.TButton") self.redoBtn = Button(self.frame, text='↷', command=self.redo, takefocus=0, width=1.5, style="symbol.TButton") self.updateButtons() + def push(self, item): ''' add an item to the undo-stack and empty out the redo-stack ''' - self.uStack.append( item ) + self.uStack.append(item) self.rStack = [] self.updateButtons() + def reset(self): ''' reset our stacks ''' - self.uStack = [] # undo - self.rStack = [] # redo + self.uStack = [] # undo + self.rStack = [] # redo + def update(self): ''' changing files and changing frames should have the same effect ''' self.reset() + def selectAll(self): self.app.Trace.selectAll() + def copy(self): self.app.Trace.copy() + def paste(self): self.app.Trace.paste() + def undo(self, event=None): ''' perform the undo-ing ''' @@ -82,25 +91,25 @@ def undo(self, event=None): if item['type'] == 'add': chs = item['chs'] for ch in chs: - self.app.Trace.remove( ch ) - self.rStack.append({ 'type':'delete', 'chs':chs }) + self.app.Trace.remove(ch) + self.rStack.append({'type': 'delete', 'chs': chs}) elif item['type'] == 'delete': chs = item['chs'] for ch in chs: ch.draw() - self.rStack.append({ 'type':'add', 'chs':chs }) + self.rStack.append({'type': 'add', 'chs': chs}) elif item['type'] == 'move': chs = item['chs'] coords = item['coords'] for i in range(len(chs)): - chs[i].dragTo( coords[i] ) - self.rStack.append({ 'type':'move', 'chs':chs, 'coords':coords }) + chs[i].dragTo(coords[i]) + self.rStack.append({'type': 'move', 'chs': chs, 'coords': coords}) elif item['type'] == 'recolor': - oldColor = self.app.Trace.recolor( item['trace'], item['color'] ) - self.rStack.append({ 'type':'recolor', 'trace':item['trace'], 'color':oldColor }) + oldColor = self.app.Trace.recolor(item['trace'], item['color']) + self.rStack.append({'type': 'recolor', 'trace': item['trace'], 'color': oldColor}) elif item['type'] == 'rename': - self.app.Trace.renameTrace( newName=item['old'], oldName=item['new'] ) # this is backwards on purpose - self.rStack.append({ 'type':'rename', 'old':item['old'], 'new':item['new'] }) + self.app.Trace.renameTrace(newName=item['old'], oldName=item['new']) # this is backwards on purpose + self.rStack.append({'type': 'rename', 'old': item['old'], 'new': item['new']}) else: error(item) raise NotImplementedError @@ -109,7 +118,8 @@ def undo(self, event=None): self.app.Trace.write() self.updateButtons() else: - warn( 'Nothing to undo!' ) + warn('Nothing to undo!') + def redo(self, event=None): ''' perform the redo-ing ''' @@ -119,25 +129,25 @@ def redo(self, event=None): if item['type'] == 'add': chs = item['chs'] for ch in chs: - self.app.Trace.remove( ch ) - self.uStack.append({ 'type':'delete', 'chs':chs }) + self.app.Trace.remove(ch) + self.uStack.append({'type': 'delete', 'chs': chs}) elif item['type'] == 'delete': chs = item['chs'] for ch in chs: ch.draw() - self.uStack.append({ 'type':'add', 'chs':chs }) + self.uStack.append({'type': 'add', 'chs': chs}) elif item['type'] == 'move': chs = item['chs'] coords = item['coords'] for i in range(len(chs)): - chs[i].dragTo( coords[i] ) - self.uStack.append({ 'type':'move', 'chs':chs, 'coords':coords }) + chs[i].dragTo(coords[i]) + self.uStack.append({'type': 'move', 'chs': chs, 'coords': coords}) elif item['type'] == 'recolor': - oldColor = self.app.Trace.recolor( item['trace'], item['color'] ) - self.uStack.append({ 'type':'recolor', 'trace':item['trace'], 'color':oldColor }) + oldColor = self.app.Trace.recolor(item['trace'], item['color']) + self.uStack.append({'type': 'recolor', 'trace': item['trace'], 'color': oldColor}) elif item['type'] == 'rename': - self.app.Trace.renameTrace( newName=item['new'], oldName=item['old'] ) - self.uStack.append({ 'type':'rename', 'old':item['old'], 'new':item['new'] }) + self.app.Trace.renameTrace(newName=item['new'], oldName=item['old']) + self.uStack.append({'type': 'rename', 'old': item['old'], 'new': item['new']}) else: error(item) raise NotImplementedError @@ -146,7 +156,8 @@ def redo(self, event=None): self.app.Trace.write() self.updateButtons() else: - warn( 'Nothing to redo!' ) + warn('Nothing to redo!') + def updateButtons(self): ''' Don't allow clicking buttons that wrap empty stacks. However, users will @@ -155,17 +166,19 @@ def updateButtons(self): self.undoBtn['state'] = 'normal' if len(self.uStack) else 'disabled' self.redoBtn['state'] = 'normal' if len(self.rStack) else 'disabled' self.grid() + def grid(self): ''' Grid button widgets ''' - self.header.grid(row=0,column=0, columnspan=5) + self.header.grid(row=0, column=0, columnspan=5) self.selectAllBtn.grid(row=1, column=0) self.copyBtn.grid(row=1, column=1, ipady=3) self.pasteBtn.grid(row=1, column=2, ipady=3) self.undoBtn.grid(row=1, column=3) self.redoBtn.grid(row=1, column=4) + def grid_remove(self): ''' Remove button widgets from grid diff --git a/ultratrace/modules/dicom.py b/ultratrace/modules/dicom.py index f63f15c..ece473d 100644 --- a/ultratrace/modules/dicom.py +++ b/ultratrace/modules/dicom.py @@ -1,14 +1,14 @@ from .base import Module -from .. import util +# from .. import util from ..util.logging import * from ..util.framereader import ULTScanLineReader, DicomReader, DicomPNGReader, LABEL_TO_READER, READERS -from ..widgets import Header +# from ..widgets import Header -import os -import PIL +# import os +# import PIL -#from tkinter.ttk import Button, Frame, OptionMenu -#from tkinter import StringVar +# from tkinter.ttk import Button, Frame, OptionMenu +# from tkinter import StringVar import wx @@ -17,54 +17,53 @@ try: from ..widgets import ZoomFrame import numpy as np - import pydicom # pydicom + import pydicom # pydicom LIBS_INSTALLED = True except ImportError as e: warn(e) + class Dicom(Module): def __init__(self, app): - info( ' - initializing module: Dicom') + info(' - initializing module: Dicom') self.app = app if LIBS_INSTALLED: # grid load button - #self.frame_holder = Frame(self.app.LEFT)#, pady=7) - #self.frame_holder.grid( row=2 ) - #self.frame = Frame(self.frame_holder) - #self.frame.pack(expand=True) + # self.frame_holder = Frame(self.app.LEFT)#, pady=7) + # self.frame_holder.grid( row=2 ) + # self.frame = Frame(self.frame_holder) + # self.frame.pack(expand=True) - #self.method = StringVar(self.app) - #self.mode = None - #self.reader = None + # self.method = StringVar(self.app) + # self.mode = None + # self.reader = None # zoom buttons - #self.zbframe = Frame(self.app.LEFT) - #self.zbframe.grid( row=6, column=0) + # self.zbframe = Frame(self.app.LEFT) + # self.zbframe.grid( row=6, column=0) # zoom frame (contains our tracing canvas) - #self.zframe = ZoomFrame(self.app.RIGHT, 1.3, app) + # self.zframe = ZoomFrame(self.app.RIGHT, 1.3, app) # zoom in, zoom out, reset zoom buttons - #self.header = Header(self.zbframe, text="Zoom") - #self.zoomResetBtn = Button(self.zbframe, text='⊜', command=self.zoomReset, width=1.5, style="symbol.TButton", takefocus=0)#, pady=7 ) - #self.zoomInBtn = Button(self.zbframe, text='⊕', command=self.zoomIn, width=1.5, style="symbol.TButton", takefocus=0) - #self.zoomOutBtn = Button(self.zbframe, text='⊝', command=self.zoomOut, width=1.5, style="symbol.TButton", takefocus=0) + # self.header = Header(self.zbframe, text="Zoom") + # self.zoomResetBtn = Button(self.zbframe, text='⊜', command=self.zoomReset, width=1.5, style="symbol.TButton", takefocus=0)#, pady=7 ) + # self.zoomInBtn = Button(self.zbframe, text='⊕', command=self.zoomIn, width=1.5, style="symbol.TButton", takefocus=0) + # self.zoomOutBtn = Button(self.zbframe, text='⊝', command=self.zoomOut, width=1.5, style="symbol.TButton", takefocus=0) # reset zoom keyboard shortcut - #if util.get_platform() == 'Linux': + # if util.get_platform() == 'Linux': # self.app.bind('', self.zoomReset ) - #else: self.app.bind('', self.zoomReset ) - #self.reset() - #self.grid() + # else: self.app.bind('', self.zoomReset ) + # self.reset() + # self.grid() # FIXME: replace this button with the ultrasound frames - self.buttonRedo = wx.BitmapButton(self.app, id=wx.ID_REDO, bitmap=wx.ArtProvider.GetBitmap(wx.ART_REDO), size=(32,32)) + self.buttonRedo = wx.BitmapButton(self.app, id=wx.ID_REDO, bitmap=wx.ArtProvider.GetBitmap(wx.ART_REDO), size=(32, 32)) self.app.ultrasoundBox.Add(self.buttonRedo, flag=wx.BOTTOM, border=10) - - def zoomReset(self, fromButton=False): ''' reset zoom frame canvas and rebind it @@ -77,7 +76,8 @@ def zoomReset(self, fromButton=False): # self.zframe.canvas.bind('', self.app.onMotion ) # we want to go here only after a button press - if fromButton: self.app.framesUpdate() + if fromButton: + self.app.framesUpdate() def zoomIn(self): self.zframe.zoomIn() @@ -109,12 +109,12 @@ def chooseMethod(self, event=None): if self.mode == 'dicom': dcm = self.app.Data.checkFileLevel('.dicom') if dcm: - if cls == DicomPNGReader and self.app.Data.getFileLevel('processed'): - self.reader = cls(dcm, self.app.Data.path) - else: - self.reader = cls(dcm) + if cls == DicomPNGReader and self.app.Data.getFileLevel('processed'): + self.reader = cls(dcm, self.app.Data.path) + else: + self.reader = cls(dcm) else: - self.reader = None + self.reader = None elif self.mode == 'ult': ult = self.app.Data.checkFileLevel('.ult') meta = self.app.Data.checkFileLevel('US.txt') @@ -145,8 +145,8 @@ def getFrameTimes(self): return rd.getFrameTimes() elif self.mode == 'ult': rd = ULTScanLineReader( - self.app.Data.unrelativize(self.app.Data.getFileLevel('.ult')), - self.app.Data.unrelativize(self.app.Data.getFileLevel('US.txt'))) + self.app.Data.unrelativize(self.app.Data.getFileLevel('.ult')), + self.app.Data.unrelativize(self.app.Data.getFileLevel('US.txt'))) return rd.getFrameTimes() else: return [0] @@ -186,16 +186,16 @@ def grid(self): ''' Grid frame navigation, zoom reset, and Control (Undo/Redo) widgets ''' - self.app.framesHeader.grid( row=0 ) - self.app.framesPrevBtn.grid( row=0, column=0 ) - self.app.framesEntryText.grid( row=0, column=1 ) - self.app.framesEntryBtn.grid( row=0, column=2 ) - self.app.framesNextBtn.grid( row=0, column=3 ) - - #self.header.grid(row=0, column=0, columnspan=5) - self.zoomInBtn.grid( row=0, column=3) - self.zoomResetBtn.grid( row=0, column=2 ) - self.zoomOutBtn.grid( row=0, column=1) + self.app.framesHeader.grid(row=0) + self.app.framesPrevBtn.grid(row=0, column=0) + self.app.framesEntryText.grid(row=0, column=1) + self.app.framesEntryBtn.grid(row=0, column=2) + self.app.framesNextBtn.grid(row=0, column=3) + + # self.header.grid(row=0, column=0, columnspan=5) + self.zoomInBtn.grid(row=0, column=3) + self.zoomResetBtn.grid(row=0, column=2) + self.zoomOutBtn.grid(row=0, column=1) self.app.Control.grid() def grid_remove(self): diff --git a/ultratrace/modules/playback.py b/ultratrace/modules/playback.py index 29e0c43..00c095f 100644 --- a/ultratrace/modules/playback.py +++ b/ultratrace/modules/playback.py @@ -1,14 +1,14 @@ from .base import Module from .. import util from ..util.logging import * -from ..widgets import Header +# from ..widgets import Header import queue import threading import time import math -#from tkinter.ttk import Button, Frame +# from tkinter.ttk import Button, Frame AUDIO_LIBS_INSTALLED = False try: @@ -25,15 +25,17 @@ except ImportError as e: warn(e) + class Playback(Module): ''' Module to handle playback of audio and video files. ''' + def __init__(self, app): self.app = app self.current = None if AUDIO_LIBS_INSTALLED: - info( ' - initializing module: Audio' ) + info(' - initializing module: Audio') self.sfile = None self.p = pyaudio.PyAudio() self.currentInterval = None @@ -43,24 +45,24 @@ def __init__(self, app): self.stoprequest = threading.Event() # widget management - #self.frame = Frame(self.app.BOTTOM) - ## JNW FIXME: - #self.frame = Frame(self.app.LEFT)#, pady=7) - - ## JNW FIXME: - #self.header = Header(self.frame, text="Playback") - #self.playBtn = Button(self.frame, text="⏯", command=self.playpauseAV, state='disabled', takefocus=0) # NOTE: not currently appearing - #self.app.bind('', self.playpauseAV ) - #self.app.bind('', self.stopAV ) + # self.frame = Frame(self.app.BOTTOM) + # JNW FIXME: + # self.frame = Frame(self.app.LEFT)#, pady=7) + + # JNW FIXME: + # self.header = Header(self.frame, text="Playback") + # self.playBtn = Button(self.frame, text="⏯", command=self.playpauseAV, state='disabled', takefocus=0) # NOTE: not currently appearing + # self.app.bind('', self.playpauseAV ) + # self.app.bind('', self.stopAV ) if VIDEO_LIBS_INSTALLED: - info( ' - initializing module: Video' ) - ## JNW FIXME: - #self.app.bind('', self.playpauseAV ) - #self.app.bind('', self.stopAV ) - - ## JNW FIXME: - #self.grid() - #self.reset() + info(' - initializing module: Video') + # JNW FIXME: + # self.app.bind('', self.playpauseAV ) + # self.app.bind('', self.stopAV ) + + # JNW FIXME: + # self.grid() + # self.reset() self.app.Update() def update(self): @@ -75,23 +77,23 @@ def reset(self): ''' if AUDIO_LIBS_INSTALLED: self.current = None - audioFallbacks = [ '.wav', '.flac', '.ogg', '.mp3' ] + audioFallbacks = ['.wav', '.flac', '.ogg', '.mp3'] for codec in audioFallbacks: - if self.loadAudio( codec ) == True: - self.playBtn.config( state='normal' ) + if self.loadAudio(codec) == True: + self.playBtn.config(state='normal') return def loadAudio(self, codec): ''' load an audio file with a specific codec ''' - audiofile = self.app.Data.getFileLevel( codec ) + audiofile = self.app.Data.getFileLevel(codec) if audiofile != None: try: audiofile = self.app.Data.unrelativize(audiofile) - self.sfile = AudioSegment.from_file( audiofile ) + self.sfile = AudioSegment.from_file(audiofile) self.current = audiofile - self.duration = len(self.sfile)/1000.0 + self.duration = len(self.sfile) / 1000.0 return True except Exception as e: error('Unable to load audio file: `%s`' % audiofile, e) @@ -102,8 +104,8 @@ def playpauseAV(self, event): ''' # debug(self.started, self.paused, '1858') - if self.started == False or self.currentInterval != self.app.TextGrid.selectedItem: #if we haven't started playing or we're in a new interval - #reset monitoring variables + if self.started == False or self.currentInterval != self.app.TextGrid.selectedItem: # if we haven't started playing or we're in a new interval + # reset monitoring variables self.currentInterval = self.app.TextGrid.selectedItem self.started = False self.paused = False @@ -151,14 +153,14 @@ def readyAudio(self, start, end): ''' ''' - #audio stuff - start_idx = round(float(start)*1000) - end_idx = round(float(end)*1000) + # audio stuff + start_idx = round(float(start) * 1000) + end_idx = round(float(end) * 1000) self.flen = float(self.app.TextGrid.frame_len) fpb = 512 - extrafs = (end_idx-start_idx)%fpb - extrasecs = extrafs/self.sfile.frame_rate - pad = AudioSegment.silent(duration=round(extrasecs*1000)) + extrafs = (end_idx - start_idx) % fpb + extrasecs = extrafs / self.sfile.frame_rate + pad = AudioSegment.silent(duration=round(extrasecs * 1000)) seg_nopad = self.sfile[start_idx:end_idx] self.seg = seg_nopad + pad # seg.append() @@ -166,12 +168,12 @@ def readyAudio(self, start, end): # open stream using callback (3) self.stream = self.p.open(format=self.p.get_format_from_width(self.seg.sample_width), - channels=self.seg.channels, - rate=self.seg.frame_rate, - frames_per_buffer=fpb, - output=True, - start=False, - stream_callback=self.callback) + channels=self.seg.channels, + rate=self.seg.frame_rate, + frames_per_buffer=fpb, + output=True, + start=False, + stream_callback=self.callback) # debug(self.seg.frame_count()/fpb, 'number of chunks') # debug(self.seg.frame_count()%fpb, 'last chunk size') @@ -183,7 +185,7 @@ def readyVideo(self): ''' self.app.Trace.reset() tags = self.app.TextGrid.selectedItem[0].gettags(self.app.TextGrid.selectedItem[1]) - framenums = [tag[5:] for tag in tags if tag[:5]=='frame'] + framenums = [tag[5:] for tag in tags if tag[:5] == 'frame'] self.framestart = int(framenums[0]) imgs = self.app.Dicom.getFrames(framenums) canvas = self.app.Dicom.zframe.canvas @@ -202,16 +204,16 @@ def readyVideo(self): for pt in traces[name]['files'][file][frame]: x = int(pt['x'] * img.width) y = int(pt['y'] * img.height) - draw.line((x-l, y, x+l, y), fill=color) - draw.line((x, y-l, x, y+l), fill=color) + draw.line((x - l, y, x + l, y), fill=color) + draw.line((x, y - l, x, y + l), fill=color) del draw self.pngs.append(ImageTk.PhotoImage(img)) - #video w/audio stuff + # video w/audio stuff self.dicomframe_timer = 0 self.dicomframe_num = 1 self.dicomframeQ = queue.Queue() - self.dicomframeQ.put(self.pngs[0]) #put now, because audio callback puts frames when audio segments end + self.dicomframeQ.put(self.pngs[0]) # put now, because audio callback puts frames when audio segments end # for i in range(len(self.pngs)): # self.dicomframeQ.put(self.pngs[i]) @@ -222,29 +224,29 @@ def callback(self, in_data, frame_count, time_info, status): ''' # self.sync.clear() # self.chunkcount+=1 - data = b''.join([self.seg.get_frame(i) for i in range(self.audioframe, self.audioframe+frame_count)]) + data = b''.join([self.seg.get_frame(i) for i in range(self.audioframe, self.audioframe + frame_count)]) # debug(len(data), 'line 1960') - self.audioframe+=frame_count + self.audioframe += frame_count if self.app.Dicom.isLoaded(): - #check & update video frame + # check & update video frame canvas = self.app.Dicom.zframe.canvas - callbacklen = frame_count/self.seg.frame_rate + callbacklen = frame_count / self.seg.frame_rate self.dicomframe_timer += callbacklen - #go to next frame + # go to next frame if self.dicomframe_timer % self.flen != self.dicomframe_timer and self.dicomframe_num < len(self.pngs): - floor = math.floor(self.dicomframe_timer/self.flen) + floor = math.floor(self.dicomframe_timer / self.flen) # debug(floor, 'line 1961') - self.dicomframe_timer = self.dicomframe_timer-self.flen*floor + self.dicomframe_timer = self.dicomframe_timer - self.flen * floor if floor > 1: - for i in range(floor-1): + for i in range(floor - 1): # debug(self.dicomframe_num+self.framestart+i, 'putting frame into Q') - if self.dicomframe_num+i < len(self.pngs): - self.dicomframeQ.put(self.pngs[self.dicomframe_num+i]) + if self.dicomframe_num + i < len(self.pngs): + self.dicomframeQ.put(self.pngs[self.dicomframe_num + i]) else: self.dicomframeQ.put(self.pngs[self.dicomframe_num]) # self.sync.set() - self.dicomframe_num+=floor + self.dicomframe_num += floor # debug(self.dicomframe_num, len(self.pngs), 'line 1968') # else: #stop video loop @@ -259,7 +261,7 @@ def playAudio(self): if self.app.Dicom.isLoaded(): self.playVideoWithAudio() else: - pass #write a loop that keeps audio playing + pass # write a loop that keeps audio playing # stop stream (6) if self.stoprequest.is_set(): self.stopAV() @@ -282,7 +284,7 @@ def playVideoWithAudio(self): # debug(self.dicomframeQ.qsize(),'line 1991') try: pic = self.dicomframeQ.get(timeout=.5) - canvas.itemconfig(canvas.find_all()[0] , image=pic ) + canvas.itemconfig(canvas.find_all()[0], image=pic) # canvas.lift(pic) # canvas.img = pic canvas.update() @@ -290,7 +292,7 @@ def playVideoWithAudio(self): error(e) # debug(pic, 'displayed') # debug(self.dicomframe_num+self.framestart, 'displayed') - if not self.stoprequest.is_set() or not self.dicomframeQ.empty(): #should this if be at the top? + if not self.stoprequest.is_set() or not self.dicomframeQ.empty(): # should this if be at the top? self.playVideoWithAudio() # canvas.after(10, self.playVideoWithAudio) @@ -301,12 +303,12 @@ def playVideoNoAudio(self): canvas = self.app.Dicom.zframe.canvas # pic = self.dicomframeQ.get() pic = self.dicomframeQ.get(block=False) - canvas.itemconfig( canvas.find_all()[0], image=pic ) + canvas.itemconfig(canvas.find_all()[0], image=pic) canvas.update() - if not self.dicomframeQ.empty() and self.stoprequest.is_set() == False: #should this if be at the top? + if not self.dicomframeQ.empty() and self.stoprequest.is_set() == False: # should this if be at the top? self.playVideoNoAudio() - def stopAV(self,event=None): + def stopAV(self, event=None): self.stoprequest.set() if AUDIO_LIBS_INSTALLED: self.stream.stop_stream() @@ -318,15 +320,14 @@ def stopAV(self,event=None): def grid(self): ''' grid widgets ''' - ## JNW: FIXME: - #self.frame.grid( row=8 ) - #self.header.grid() - #self.playBtn.grid() + # JNW: FIXME: + # self.frame.grid( row=8 ) + # self.header.grid() + # self.playBtn.grid() def grid_remove(self): ''' remove widgets from grid ''' - ## JNW: FIXME: - #self.frame.grid_remove() - #self.header.grid_remove() - #self.playBtn.grid_remove() - + # JNW: FIXME: + # self.frame.grid_remove() + # self.header.grid_remove() + # self.playBtn.grid_remove() diff --git a/ultratrace/modules/search.py b/ultratrace/modules/search.py index 49563a3..ce2cda9 100644 --- a/ultratrace/modules/search.py +++ b/ultratrace/modules/search.py @@ -7,6 +7,7 @@ from tkinter import Toplevel, StringVar, Grid from tkinter.ttk import Treeview, Button, Entry, Label, Scrollbar + class Search(Module): def __init__(self, app): self.app = app @@ -20,9 +21,11 @@ def __init__(self, app): # it should call SearchModule.loadIntervals() self.intervals = [] self.loadIntervals() + def handleClose(self, event=None): self.window.destroy() self.window = None + def createWindow(self): self.window = Toplevel(self.app) self.window.title('Search') @@ -46,11 +49,13 @@ def createWindow(self): Grid.rowconfigure(self.window, 2, weight=1) Grid.columnconfigure(self.window, 0, weight=1) self.scroll.grid(row=2, column=3, sticky='ns') + def openSearch(self): if self.window == None: self.createWindow() self.window.lift() self.input.focus() + def loadIntervals(self): filecount = len(self.app.Data.getTopLevel('files')) self.intervals = [] @@ -64,6 +69,7 @@ def loadIntervals(self): for el in tier: if el.mark: self.intervals.append((el, tier.name, filename)) + def search(self, event=None): if self.regex.get() == '': self.results = [] @@ -74,17 +80,19 @@ def search(self, event=None): s = pat.search(i[0].mark) if s: disp = i[0].mark - a = max(0, s.start()-self.context_size) - b = min(s.end()+self.context_size, len(disp)) - self.results.append(i + (('...' if a > 0 else '')+disp[a:b]+('...' if b < len(disp) else ''),)) + a = max(0, s.start() - self.context_size) + b = min(s.end() + self.context_size, len(disp)) + self.results.append(i + (('...' if a > 0 else '') + disp[a:b] + ('...' if b < len(disp) else ''),)) self.resultCount.configure(text='%s results' % len(self.results)) for kid in self.resultList.get_children(): self.resultList.delete(kid) for row, res in enumerate(self.results): ls = (res[2], res[1], '%s-%s' % (res[0].minTime, res[0].maxTime), res[3]) self.resultList.insert('', 'end', iid=str(row), values=ls) + def onClick(self, event=None): self.jumpTo(int(self.resultList.selection()[0])) + def jumpTo(self, index): self.app.filesJumpTo(self.results[index][2]) self.app.TextGrid.selectedTier.set(self.results[index][1]) diff --git a/ultratrace/modules/spectrogram.py b/ultratrace/modules/spectrogram.py index 4dae4df..f305587 100644 --- a/ultratrace/modules/spectrogram.py +++ b/ultratrace/modules/spectrogram.py @@ -22,16 +22,17 @@ except ImportError as e: warn(e) + class Spectrogram(Module): - def __init__(self,app): - info( ' - initializing module: Spectrogram' ) + def __init__(self, app): + info(' - initializing module: Spectrogram') self.app = app self.frame = Frame(self.app.BOTTOM) - self.frame.grid( row=0, column=1, pady=(self.app.pady*2,self.app.pady/2) ) + self.frame.grid(row=0, column=1, pady=(self.app.pady * 2, self.app.pady / 2)) self.axis_frame = Frame(self.app.BOTTOM) - self.axis_frame.grid( row=0, column=0, sticky='e', pady=(self.app.pady*2,self.app.pady/2) ) + self.axis_frame.grid(row=0, column=0, sticky='e', pady=(self.app.pady * 2, self.app.pady / 2)) self.canvas_width = self.app.TextGrid.canvas_width self.canvas_height = 106 self.canvas = Canvas(self.frame, width=self.canvas_width, height=self.canvas_height, background='gray', highlightthickness=0) @@ -44,24 +45,24 @@ def __init__(self,app): self.oldSelected = None self.doDefaults() - #make spinboxes & buttons for spectrogram specs + # make spinboxes & buttons for spectrogram specs self.spinwin = Frame(self.axis_frame) - #spinboxes + # spinboxes axis_ceil_box = Spinbox(self.spinwin, textvariable=self.spec_freq_max, command=self.drawSpectrogram, width=7, increment=100, from_=0, to_=100000) - axis_ceil_box.bind('',self.drawSpectrogram) - axis_ceil_box.bind('',lambda ev: self.spinwin.focus()) + axis_ceil_box.bind('', self.drawSpectrogram) + axis_ceil_box.bind('', lambda ev: self.spinwin.focus()) wl_box = Spinbox(self.spinwin, textvariable=self.wl, command=self.drawSpectrogram, width=7, increment=0.0005, from_=0, to_=1) - wl_box.bind('',self.drawSpectrogram) - wl_box.bind('',lambda ev: self.spinwin.focus()) + wl_box.bind('', self.drawSpectrogram) + wl_box.bind('', lambda ev: self.spinwin.focus()) dyn_range_box = Spinbox(self.spinwin, textvariable=self.dyn_range, command=self.drawSpectrogram, width=7, increment=10, from_=0, to_=10000) - dyn_range_box.bind('',self.drawSpectrogram) - dyn_range_box.bind('',lambda ev: self.spinwin.focus()) - #buttons + dyn_range_box.bind('', self.drawSpectrogram) + dyn_range_box.bind('', lambda ev: self.spinwin.focus()) + # buttons default_btn = Button(self.spinwin, text='Standards', command=self.restoreDefaults, takefocus=0) apply_btn = Button(self.spinwin, text='Apply', command=self.drawSpectrogram, takefocus=0, width=6) # self.axis_frame.create_window(wwidth,self.canvas_height, window=self.spinwin, anchor='ne') - #grid spinboxes & buttons on subframe + # grid spinboxes & buttons on subframe axis_ceil_box.grid(row=0, columnspan=2, sticky='ne') wl_box.grid(row=1, columnspan=2, sticky='ne') dyn_range_box.grid(row=2, columnspan=2, sticky='ne') @@ -124,7 +125,7 @@ def drawSpectrogram(self, event=None): # but we've defined ts as duration / 10000, so duration % ts = 0 # so the amount to increase the length by is ts * floor( wl / ts ) # at either end - D.S. - extra = self.ts * math.floor( wl / self.ts ) + extra = self.ts * math.floor(wl / self.ts) start_time = max(0, real_start - extra) end_time = min(real_end + extra, sound.get_total_duration()) sound_clip = sound.extract_part(from_time=start_time, to_time=end_time) @@ -138,7 +139,7 @@ def drawSpectrogram(self, event=None): mx = self.spectrogram.max() dyn = self.dyn_range.get() # debug(self.spectrogram.min(), self.spectrogram.max()) - self.spectrogram = self.spectrogram.clip(mx-dyn, mx) - mx + self.spectrogram = self.spectrogram.clip(mx - dyn, mx) - mx # debug(self.spectrogram.min(), self.spectrogram.max()) self.spectrogram *= (-255.0 / dyn) # self.spectrogram += 60 @@ -150,7 +151,7 @@ def drawSpectrogram(self, event=None): # contrast = ImageEnhance.Contrast(img) # img = contrast.enhance(5) # self.canvas_height = img.height - img = img.resize((int(self.canvas_width*(duration / screen_duration)), self.canvas_height)) + img = img.resize((int(self.canvas_width * (duration / screen_duration)), self.canvas_height)) photo_img = ImageTk.PhotoImage(img) self.canvas.config(height=self.canvas_height) @@ -163,11 +164,11 @@ def drawSpectrogram(self, event=None): coord *= 1 - ((screen_end - real_end) / screen_duration) img = self.canvas.create_image(coord, self.canvas_height, anchor='se', image=photo_img) self.img = photo_img - #pass on selected-ness + # pass on selected-ness if self.app.TextGrid.selectedItem: if self.app.TextGrid.selectedItem[0] == self.canvas: self.app.TextGrid.selectedItem = (self.canvas, img) - #pass on tags + # pass on tags for tag in tags: self.canvas.addtag_all(tag) @@ -181,17 +182,17 @@ def drawInterval(self): widg = self.app.TextGrid.selectedItem[0] itm = self.app.TextGrid.selectedItem[1] - if widg in self.app.TextGrid.tier_pairs: #if widg is label + if widg in self.app.TextGrid.tier_pairs: # if widg is label itvl_canvas = self.app.TextGrid.tier_pairs[widg] for i in itvl_canvas.find_withtag('line'): loc = itvl_canvas.coords(i)[0] self.canvas.create_line(loc, 0, loc, self.canvas_height, tags='line', fill='blue') - elif widg in self.app.TextGrid.tier_pairs.values(): #if widg is textgrid canvas - if itm-1 in widg.find_all(): - l_loc = widg.coords(itm-1)[0] + elif widg in self.app.TextGrid.tier_pairs.values(): # if widg is textgrid canvas + if itm - 1 in widg.find_all(): + l_loc = widg.coords(itm - 1)[0] self.canvas.create_line(l_loc, 0, l_loc, self.canvas_height, tags='line', fill='blue') - if itm+1 in widg.find_all(): - r_loc = widg.coords(itm+1)[0] + if itm + 1 in widg.find_all(): + r_loc = widg.coords(itm + 1)[0] self.canvas.create_line(r_loc, 0, r_loc, self.canvas_height, tags='line', fill='blue') elif widg == self.canvas: l_time, r_time = self.app.TextGrid.getMinMaxTime() @@ -200,55 +201,56 @@ def drawInterval(self): self.canvas.create_line(l_loc, 0, l_loc, self.canvas_height, tags='line', fill='blue') self.canvas.create_line(r_loc, 0, r_loc, self.canvas_height, tags='line', fill='blue') - #draw selected frame - if self.app.TextGrid.firstFrame <= self.app.frame <= self.app.TextGrid.lastFrame : + # draw selected frame + if self.app.TextGrid.firstFrame <= self.app.frame <= self.app.TextGrid.lastFrame: xcoord = self.app.TextGrid.frames_canvas.coords(self.app.TextGrid.highlighted_frame)[0] - self.canvas.create_line(xcoord,0,xcoord,self.canvas_height, tags='line', fill='red') - #draw line where user last clicked on spectrogram + self.canvas.create_line(xcoord, 0, xcoord, self.canvas_height, tags='line', fill='red') + # draw line where user last clicked on spectrogram if self.clicktime != -1 and self.specClick == False: x = self.timeToX(self.clicktime) - self.canvas.create_line(x,0,x,self.canvas_height, tags='line', fill='green') + self.canvas.create_line(x, 0, x, self.canvas_height, tags='line', fill='green') def jumpToFrame(self, event): ''' ''' - #restore textgrid selected interval between clicks + # restore textgrid selected interval between clicks if not self.app.TextGrid.selectedItem: key = next(iter(self.app.TextGrid.tier_pairs)) wdg = self.app.TextGrid.tier_pairs[key] - self.app.TextGrid.selectedItem = (wdg,wdg.find_all()[0]) + self.app.TextGrid.selectedItem = (wdg, wdg.find_all()[0]) self.app.TextGrid.setSelectedIntvlFrames(self.app.TextGrid.selectedItem) if self.app.TextGrid.selectedItem[0] == self.canvas: self.app.TextGrid.selectedItem = self.oldSelected self.app.TextGrid.setSelectedIntvlFrames(self.app.TextGrid.selectedItem) - #prevents wiping of canvases because of mouse click + # prevents wiping of canvases because of mouse click # self.app.resized = False # draw line at click location x = self.canvas.canvasx(event.x) self.clicktime = self.xToTime(x) - #jump to new frame + # jump to new frame frame = self.app.TextGrid.my_find_closest(self.app.TextGrid.frames_canvas, self.canvas.canvasx(event.x)) framenum = self.app.TextGrid.frames_canvas.gettags(frame)[0][5:] - self.app.frame=int(framenum) + self.app.frame = int(framenum) self.app.framesUpdate() - #remember which interval was selected before specgram click - if event.state==1: + # remember which interval was selected before specgram click + if event.state == 1: self.oldSelected = self.app.TextGrid.selectedItem - #for selecting & zooming interval (w/ shift) + # for selecting & zooming interval (w/ shift) self.specClick = True def xToTime(self, x): ''' converts from a x coordinate (relative to the canvas) to the timestamp at that coordinate''' - return (x*float(self.app.TextGrid.end - self.app.TextGrid.start)/self.canvas_width) + float(self.app.TextGrid.start) - def timeToX(self,time): + return (x * float(self.app.TextGrid.end - self.app.TextGrid.start) / self.canvas_width) + float(self.app.TextGrid.start) + + def timeToX(self, time): ''' converts from a time to the x coordinate on a canvas representing that time''' - return self.canvas_width*(time - float(self.app.TextGrid.start))/float(self.app.TextGrid.end - self.app.TextGrid.start) + return self.canvas_width * (time - float(self.app.TextGrid.start)) / float(self.app.TextGrid.end - self.app.TextGrid.start) def grid(self): ''' Put tkinter items on app ''' self.canvas.grid(row=0, column=0, sticky='news') - self.spinwin.grid(row=0,column=0,sticky='ne') + self.spinwin.grid(row=0, column=0, sticky='ne') # self.axis_canvas.grid(row=0,column=0,sticky='se') def grid_remove(self): diff --git a/ultratrace/modules/trace.py b/ultratrace/modules/trace.py index 35664e9..5ae9b54 100644 --- a/ultratrace/modules/trace.py +++ b/ultratrace/modules/trace.py @@ -1,32 +1,34 @@ -from .base import Module +# from .base import Module from .. import util from ..util.logging import * -from ..widgets import Crosshairs, Header +from ..widgets import Crosshairs # , Header import PIL import random -#from tkinter.ttk import Button, Entry, Frame, Scrollbar -#from tkinter import Listbox, StringVar +# from tkinter.ttk import Button, Entry, Frame, Scrollbar +# from tkinter import Listbox, StringVar import wx + class Trace(Module): ''' Module to manage all of the different traces (with unique names/colors) and the Crosshairs objects associated to each one. In particular, handles creation/modfication of traces and crosshairs. ''' + def __init__(self, app): - info( ' - initializing module: Trace' ) + info(' - initializing module: Trace') self.app = app self.displayedColour = None - #self.app.Data.getCurrentTraceColor() + # self.app.Data.getCurrentTraceColor() # array of trace names for this directory - self.available = self.app.Data.getTopLevel( 'traces' ) - self.available = {} if self.available==None else self.available + self.available = self.app.Data.getTopLevel('traces') + self.available = {} if self.available == None else self.available # dictionary to hold trace -> [crosshairs] data self.crosshairs = {} @@ -38,13 +40,13 @@ def __init__(self, app): self.copied = [] # declare & init trace string variable - #self.traceSV = StringVar() - #self.traceSV.set( '' ) + # self.traceSV = StringVar() + # self.traceSV.set( '' ) self.tracesSV = '' # frame for (most of) our widgets - #self.frame = Frame(self.app.LEFT)#, pady=7, padx=7) - #self.frame.grid( row=4 ) + # self.frame = Frame(self.app.LEFT)#, pady=7, padx=7) + # self.frame.grid( row=4 ) self.app.layersBox = wx.StaticBoxSizer(wx.VERTICAL, self.app, label="Layers") self.layers = wx.ListCtrl(self.app, -1, style=wx.LC_REPORT) self.layers.InsertColumn(0, 'name', width=60) @@ -52,36 +54,36 @@ def __init__(self, app): self.layers.InsertColumn(2, 'visible', wx.LIST_FORMAT_RIGHT, width=30) self.layers.InsertColumn(3, 'colour', wx.LIST_FORMAT_RIGHT, width=30) self.layers.InsertColumn(4, 'delete', wx.LIST_FORMAT_RIGHT, width=30) - # self.layers.InsertItem(0, "test") - self.il = wx.ImageList(16,16) - #il.Add(wx.ArtProvider.GetBitmap("gtk-emblem-default",wx.ART_MENU)) - #hargle = self.il.Add(wx.ArtProvider.GetBitmap("gtk-zoom-in",wx.ART_MENU)) + # self.layers.InsertItem(0, "test") + self.il = wx.ImageList(16, 16) + # il.Add(wx.ArtProvider.GetBitmap("gtk-emblem-default",wx.ART_MENU)) + # hargle = self.il.Add(wx.ArtProvider.GetBitmap("gtk-zoom-in",wx.ART_MENU)) # FIXME: Find a way to set a default emblem - #self.ICON_DEFAULT = self.il.Add(wx.ArtProvider.GetBitmap("emblem-default",wx.ART_MENU)) - self.ICON_DEFAULT = self.il.Add(wx.EmptyBitmap(16,16)) + # self.ICON_DEFAULT = self.il.Add(wx.ArtProvider.GetBitmap("emblem-default",wx.ART_MENU)) + self.ICON_DEFAULT = self.il.Add(wx.EmptyBitmap(16, 16)) self.layers.AssignImageList(self.il, wx.IMAGE_LIST_SMALL) - #thisLayer = self.newLayer(self.layers, "text", "#008888", default=True) + # thisLayer = self.newLayer(self.layers, "text", "#008888", default=True) # listbox to contain all of our traces - #lbframe = Frame(self.frame) - #self.scrollbar = Scrollbar(lbframe) - #self.listbox = Listbox(lbframe, yscrollcommand=self.scrollbar.set, width=12, height=5, exportselection=False, takefocus=0) - #self.scrollbar.config(command=self.listbox.yview) + # lbframe = Frame(self.frame) + # self.scrollbar = Scrollbar(lbframe) + # self.listbox = Listbox(lbframe, yscrollcommand=self.scrollbar.set, width=12, height=5, exportselection=False, takefocus=0) + # self.scrollbar.config(command=self.listbox.yview) # FIXME -JNW for trace in self.available: thisLayer = self.newLayer(self.layers, trace, "#008888", default=True) - #for trace in self.available: + # for trace in self.available: # self.listbox.insert('end', trace) - #for i, item in enumerate(self.listbox.get(0, 'end')): + # for i, item in enumerate(self.listbox.get(0, 'end')): # # select our "default trace" # if item==self.app.Data.getTopLevel( 'defaultTraceName' ): # self.listbox.selection_clear(0, 'end') # self.listbox.select_set( i ) # break - #else: + # else: # self.listbox.select_set(0) # # select whatever is listed first # # TODO: this assumes that the list of traces is non-empty @@ -89,15 +91,13 @@ def __init__(self, app): self.app.layersBox.Add(self.layers) self.app.controlBox.Add(self.app.layersBox) self.app.Update() - #self.app.SetSizerAndFit(self.app.controlBox) + # self.app.SetSizerAndFit(self.app.controlBox) self.app.SetSizerAndFit(self.app.hbox) - - - ## this module is responsible for so many widgets that we need a different - ## strategy for keeping track of everything that needs constistent grid() / - ## grid_remove() behavior - #self.TkWidgets = [ + # this module is responsible for so many widgets that we need a different + # strategy for keeping track of everything that needs constistent grid() / + # grid_remove() behavior + # self.TkWidgets = [ # self.getWidget( Header(self.frame, text="Landmarks"), row=5, column=0, columnspan=4 ), # self.getWidget( lbframe, row=10, column=0, rowspan=50 ), # self.getWidget( Button(self.frame, text='Set as default', command=self.setDefaultTraceName, takefocus=0), row=10, column=1, columnspan=3 ), @@ -108,21 +108,21 @@ def __init__(self, app): # self.getWidget( Button(self.frame, text='+', command=self.newTrace, takefocus=0, width=1.5), row=100, column=1, sticky='w' ), # self.getWidget( Button(self.frame, text='Rename', command=self.renameTrace, takefocus=0, width="7"), row=100, column=2, columnspan=2 ) ] - ## there's probably a better way to do this than indexing into self.TkWidgets - #self.TkWidgets[3]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) - #self.TkWidgets[3]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) - #self.TkWidgets[6]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) - #self.TkWidgets[6]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) + # there's probably a better way to do this than indexing into self.TkWidgets + # self.TkWidgets[3]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) + # self.TkWidgets[3]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) + # self.TkWidgets[6]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) + # self.TkWidgets[6]['widget'].bind('', lambda ev: self.TkWidgets[0]['widget'].focus()) - #if util.get_platform() == 'Linux': + # if util.get_platform() == 'Linux': # self.app.bind('', self.recolor ) # self.app.bind('', self.copy ) # self.app.bind('', self.paste ) - #else: + # else: # self.app.bind('', self.recolor ) # self.app.bind('', self.copy ) # self.app.bind('', self.paste ) - #self.grid() + # self.grid() def newColourSquare(self, w, h, colour): clr = PIL.ImageColor.getcolor(colour, "RGBA") @@ -130,17 +130,17 @@ def newColourSquare(self, w, h, colour): def newLayer(self, layers, name, colour, default=False): item = layers.InsertItem(layers.GetItemCount(), name) - #layers.SetItemBackgroundColour(item, wx.Colour(colour)) + # layers.SetItemBackgroundColour(item, wx.Colour(colour)) if default: layers.SetItem(item, 1, "", self.ICON_DEFAULT) layers.SetItem(item, 2, "👁") colourSquare = self.newColourSquare(16, 16, colour) - ###bmp = wx.EmptyBitmap(w, h) - ###dc = wx.MemoryDC(bmp) - ###dc.SetPen(wx.Pen(wx.RED,1)) - ###dc.DrawPolygon([(0,0),(0,10),(10,10),(10,0)], fill_style=wx.WINDING_RULE) - ###box = dc.GetAsBitmap() - ###dc.SelectObject(wx.NullBitmap) + # bmp = wx.EmptyBitmap(w, h) + # dc = wx.MemoryDC(bmp) + # dc.SetPen(wx.Pen(wx.RED,1)) + # dc.DrawPolygon([(0,0),(0,10),(10,10),(10,0)], fill_style=wx.WINDING_RULE) + # box = dc.GetAsBitmap() + # dc.SelectObject(wx.NullBitmap) boxIdx = self.il.Add(colourSquare) layers.SetItem(item, 3, "", boxIdx) return item @@ -148,18 +148,19 @@ def newLayer(self, layers, name, colour, default=False): def update(self): ''' on change frames ''' # self.grid() - #NOTE this is called during zoom and pan - #this means the crosshairs are redrawn for every call, which is a lot - #we could probably just move them instead - self.reset() # clear our crosshairs + # NOTE this is called during zoom and pan + # this means the crosshairs are redrawn for every call, which is a lot + # we could probably just move them instead + self.reset() # clear our crosshairs self.read() # read from file - #self.frame.update() - #debug("TraceModule", self.frame.winfo_width()) + # self.frame.update() + # debug("TraceModule", self.frame.winfo_width()) + def reset(self): ''' on change files ''' # undraw all the crosshairs for trace in self.crosshairs: - for ch in self.crosshairs[ trace ]: + for ch in self.crosshairs[trace]: ch.undraw() # and empty out our trackers self.crosshairs = {} @@ -170,13 +171,14 @@ def add(self, x, y, _trace=None, transform=True): add a crosshair to the zoom frame canvas ''' - trace = self.getCurrentTraceName() if _trace==None else _trace - color = self.available[ trace ]['color'] - ch = Crosshairs( self.app.Dicom.zframe, x, y, color, transform ) + trace = self.getCurrentTraceName() if _trace == None else _trace + color = self.available[trace]['color'] + ch = Crosshairs(self.app.Dicom.zframe, x, y, color, transform) if trace not in self.crosshairs: - self.crosshairs[ trace ] = [] - self.crosshairs[ trace ].append( ch ) + self.crosshairs[trace] = [] + self.crosshairs[trace].append(ch) return ch + def remove(self, ch, write=True): ''' remove a crosshair from the zoom frame canvas ... doesn't actually remove it @@ -187,15 +189,16 @@ def remove(self, ch, write=True): if write: self.write() return ch + def move(self): ''' called when window resizes to move to correct relative locations''' # trace = self.getCurrentTraceName() if self.crosshairs: for trace in self.crosshairs: - for ch in self.crosshairs[ trace ]: - truex,truey = ch.getTrueCoords() - ch.x,ch.y = ch.transformTrueToCoords(truex, truey) - ch.dragTo((ch.x,ch.y)) + for ch in self.crosshairs[trace]: + truex, truey = ch.getTrueCoords() + ch.x, ch.y = ch.transformTrueToCoords(truex, truey) + ch.dragTo((ch.x, ch.y)) def read(self): ''' @@ -206,14 +209,15 @@ def read(self): try: newCrosshairs = [] for item in self.app.Data.getTraceCurrentFrame(trace): - ch = self.add( item['x'], item['y'], _trace=trace, transform=False ) + ch = self.add(item['x'], item['y'], _trace=trace, transform=False) if trace not in self.crosshairs: - self.crosshairs[ trace ] = [] - self.crosshairs[ trace ].append( ch ) - newCrosshairs.append( ch ) - self.app.Control.push({ 'type':'add', 'chs':newCrosshairs }) + self.crosshairs[trace] = [] + self.crosshairs[trace].append(ch) + newCrosshairs.append(ch) + self.app.Control.push({'type': 'add', 'chs': newCrosshairs}) except KeyError: pass + def write(self): ''' write out the coordinates of all of our crosshairs to the metadata file: @@ -224,15 +228,15 @@ def write(self): # prepare trace data in format for metadata array if trace in self.crosshairs: - for ch in self.crosshairs[ trace ]: + for ch in self.crosshairs[trace]: if ch.isVisible: - x,y = ch.getTrueCoords() - data = { 'x':x, 'y':y } + x, y = ch.getTrueCoords() + data = {'x': x, 'y': y} if data not in traces: # add trace to temporary array for including in metadata array traces.append(data) # add to metadata array and update file - self.app.Data.setCurrentTraceCurrentFrame( traces ) + self.app.Data.setCurrentTraceCurrentFrame(traces) # update tier labels for number of annotated frames self.app.TextGrid.updateTierLabels() @@ -243,18 +247,20 @@ def getCurrentTraceName(self): try: return self.listbox.get(self.listbox.curselection()) - except Exception as e: # tkinter.TclError? + except Exception as e: # tkinter.TclError? error('Can\'t select from empty listbox!', e) + def setDefaultTraceName(self): ''' wrapper for changing the default trace ''' - self.app.Data.setTopLevel( 'defaultTraceName', self.getCurrentTraceName() ) + self.app.Data.setTopLevel('defaultTraceName', self.getCurrentTraceName()) def select(self, ch): ''' select a crosshairs ''' ch.select() self.selected.add(ch) + def selectAll(self): ''' select all crosshairs ''' if self.getCurrentTraceName() in self.crosshairs: @@ -265,6 +271,7 @@ def unselect(self, ch): ''' unselect a crosshairs ''' ch.unselect() self.selected.remove(ch) + def unselectAll(self): ''' unselect all crosshairs ''' for ch in self.selected: @@ -295,12 +302,13 @@ def getNearClickAllTraces(self, click): if nearby != None: # switch to that trace and exit the loop for i, item in enumerate(self.listbox.get(0, 'end')): - if item==trace: + if item == trace: self.listbox.selection_clear(0, 'end') - self.listbox.select_set( i ) + self.listbox.select_set(i) return nearby return None + def getNearClickOneTrace(self, click, trace): ''' takes a click object and a trace and returns a list of crosshairs within @@ -310,20 +318,20 @@ def getNearClickOneTrace(self, click, trace): # see if we clicked near any existing crosshairs possibleSelections = {} if trace in self.crosshairs: - for ch in self.crosshairs[ trace ]: + for ch in self.crosshairs[trace]: d = ch.getDistance(click) if d < util.CROSSHAIR_SELECT_RADIUS: if d in possibleSelections: - possibleSelections[d].append( ch ) + possibleSelections[d].append(ch) else: - possibleSelections[d] = [ ch ] + possibleSelections[d] = [ch] # if we did ... if possibleSelections != {}: # ... get the closest one ... dMin = sorted(possibleSelections.keys())[0] # ... in case of a tie, select a random one - ch = random.choice( possibleSelections[dMin] ) + ch = random.choice(possibleSelections[dMin]) return ch return None @@ -335,40 +343,42 @@ def copy(self, event=None): if len(self.selected) > 0: for ch in self.selected: self.copied.append(ch.getTrueCoords()) + def paste(self, event=None): ''' pastes copied crosshairs and add them to undo/redo buffer ''' if len(self.copied) > 0: newChs = [] for xy in self.copied: - ch = self.add(xy[0],xy[1], transform=False) + ch = self.add(xy[0], xy[1], transform=False) newChs.append(ch) self.write() - self.app.Control.push({ 'type':'add', 'chs':newChs }) + self.app.Control.push({'type': 'add', 'chs': newChs}) def recolor(self, event=None, trace=None, color=None): ''' change the color of a particular trace ''' - trace = self.getCurrentTraceName() if trace==None else trace + trace = self.getCurrentTraceName() if trace == None else trace # grab a new color and save our old color (for generating Control data) - newColor = self.getRandomHexColor() if color==None else color + newColor = self.getRandomHexColor() if color == None else color oldColor = self.app.Data.getCurrentTraceColor() - self.available[ trace ]['color'] = newColor - self.app.Data.setTraceColor( trace, newColor ) + self.available[trace]['color'] = newColor + self.app.Data.setTraceColor(trace, newColor) if trace in self.crosshairs: - for ch in self.crosshairs[ trace ]: - ch.recolor( newColor ) + for ch in self.crosshairs[trace]: + ch.recolor(newColor) - if trace==None or color == None: - self.app.Control.push({ 'type':'recolor', 'trace':self.getCurrentTraceName(), 'color':oldColor }) + if trace == None or color == None: + self.app.Control.push({'type': 'recolor', 'trace': self.getCurrentTraceName(), 'color': oldColor}) self.redoQueue = [] # FIXME: get this to update the widget self.app.Trace.displayedColour = newColor # FIXME: also get the widget to update the colour! return oldColor + def clear(self): ''' remove all crosshairs for the current trace ''' @@ -376,13 +386,14 @@ def clear(self): deleted = [] trace = self.getCurrentTraceName() if trace in self.crosshairs: - for ch in self.crosshairs[ trace ]: + for ch in self.crosshairs[trace]: if ch.isVisible: - deleted.append( ch ) - self.remove( ch, write=False ) + deleted.append(ch) + self.remove(ch, write=False) self.write() - self.app.Control.push({ 'type':'delete', 'chs':deleted }) + self.app.Control.push({'type': 'delete', 'chs': deleted}) + def newTrace(self): ''' add a new trace to our listbox ''' @@ -396,30 +407,31 @@ def newTrace(self): color = self.getRandomHexColor() # save the new trace name and color to metadata & update vars - self.available[ name ] = { 'color':color, 'files':{} } - self.app.Data.setTopLevel( 'traces', self.available ) + self.available[name] = {'color': color, 'files': {}} + self.app.Data.setTopLevel('traces', self.available) self.traceSV.set('') # update our listbox self.listbox.insert('end', name) self.listbox.selection_clear(0, 'end') - self.listbox.select_set( len(self.available)-1 ) + self.listbox.select_set(len(self.available) - 1) + def renameTrace(self, oldName=None, newName=None): ''' change a trace name from oldName -> newName ''' - fromUndo = (oldName!=None or newName!=None) - oldName = self.getCurrentTraceName() if oldName==None else oldName - newName = self.traceSV.get()[:12] if newName==None else newName + fromUndo = (oldName != None or newName != None) + oldName = self.getCurrentTraceName() if oldName == None else oldName + newName = self.traceSV.get()[:12] if newName == None else newName # don't overwrite anything if newName not in self.available and len(newName) > 0: # get data from the old name and change the dictionary key in the metadata - data = self.available.pop( oldName ) - self.available[ newName ] = data - self.app.Data.setTopLevel( 'traces', self.available ) - if oldName==self.app.Data.getTopLevel( 'defaultTraceName' ): - self.app.Data.setTopLevel( 'defaultTraceName', newName ) + data = self.available.pop(oldName) + self.available[newName] = data + self.app.Data.setTopLevel('traces', self.available) + if oldName == self.app.Data.getTopLevel('defaultTraceName'): + self.app.Data.setTopLevel('defaultTraceName', newName) self.traceSV.set('') # update our listbox @@ -429,34 +441,35 @@ def renameTrace(self, oldName=None, newName=None): self.listbox.selection_clear(0, 'end') self.listbox.select_set(index) - if not( fromUndo ): - self.app.Control.push({ 'type':'rename', 'old':oldName, 'new':newName }) + if not (fromUndo): + self.app.Control.push({'type': 'rename', 'old': oldName, 'new': newName}) def getRandomHexColor(self): ''' helper for getting a random color ''' return '#%06x' % random.randint(0, 0xFFFFFF) - def getWidget(self, widget, row=0, column=0, rowspan=1, columnspan=1, sticky=() ): + + def getWidget(self, widget, row=0, column=0, rowspan=1, columnspan=1, sticky=()): ''' helper for managing all of our widgets ''' return { - 'widget' : widget, - 'row' : row, + 'widget': widget, + 'row': row, 'rowspan': rowspan, - 'column' : column, - 'columnspan':columnspan, - 'sticky' : sticky } + 'column': column, + 'columnspan': columnspan, + 'sticky': sticky} def grid(self): ''' grid all of our widgets ''' for item in self.TkWidgets: item['widget'].grid( row=item['row'], column=item['column'], rowspan=item['rowspan'], - columnspan=item['columnspan'], sticky=item['sticky'] ) + columnspan=item['columnspan'], sticky=item['sticky']) self.listbox.pack(side='left', fill='y') self.scrollbar.pack(side='right', fill='y') + def grid_remove(self): ''' remove all of our widgets from the grid ''' for item in self.TkWidgets: item['widget'].grid_remove() self.listbox.packforget() self.scrollbar.packforget() - diff --git a/ultratrace/widgets/__init__.py b/ultratrace/widgets/__init__.py index ab84739..52d4661 100644 --- a/ultratrace/widgets/__init__.py +++ b/ultratrace/widgets/__init__.py @@ -1,5 +1,5 @@ -from .canvas_tooltip import CanvasTooltip -from .crosshairs import Crosshairs -from .header import Header -from .rect_tracker import RectTracker -from .zoom_frame import ZoomFrame +# from .canvas_tooltip import CanvasTooltip +# from .crosshairs import Crosshairs +# from .header import Header +# from .rect_tracker import RectTracker +# from .zoom_frame import ZoomFrame From b3e7a66da0d4c6705f232d2c75b35c2df3f0877e Mon Sep 17 00:00:00 2001 From: qlu1 Date: Sat, 4 Feb 2023 19:19:14 -0500 Subject: [PATCH 6/6] use logging for printing info and clean up code in main --- ultratrace/__main__.py | 319 ++++------------------------------------- 1 file changed, 29 insertions(+), 290 deletions(-) diff --git a/ultratrace/__main__.py b/ultratrace/__main__.py index ca2fca7..80e9dac 100644 --- a/ultratrace/__main__.py +++ b/ultratrace/__main__.py @@ -1,317 +1,56 @@ #!/usr/bin/env python3 -# import modules -from . import modules -# from . import util -from .util.logging import * -# from .widgets import Header - import argparse -# import os -# import PIL - import wx +import modules +from logging import info -class Frame(wx.Frame): +class Frame(wx.Frame): def __init__(self, parent, title): - super(Frame, self).__init__(parent, title=title) - - info('initializing UltraTrace') - + super().__init__(parent, title=title) + info('Initializing UltraTrace') self.initialise() def initialise(self): - - # check if we were passed a command line argument parser = argparse.ArgumentParser(prog='UltraTrace') - parser.add_argument('path', help='path (unique to a participant) where subdirectories contain raw data', default=None, nargs='?') - + parser.add_argument('path', help='Path (unique to a participant) where subdirectories contain raw data', + default=None, nargs='?') args = parser.parse_args() - # initialize data module self.Data = modules.Metadata(self, args.path) - # initialize the main app widgets - self.setWidgetDefaults() - self.buildWidgetSkeleton() + self.set_widget_defaults() + self.build_widget_skeleton() - # initialize other modules - # self.Control = modules.Control(self) self.Trace = modules.Trace(self) self.Dicom = modules.Dicom(self) self.Audio = modules.Playback(self) self.TextGrid = modules.TextGrid(self) - # self.Spectrogram = modules.Spectrogram(self) - # self.Search = modules.Search(self) - - info(' - loading widgets') - - # self.filesUpdate() - - # to deal with resize handler being called multiple times - # in a single window resize - self.isResizing = False - - # self.oldwidth = self.winfo_width() - - # self.after(300,self.afterstartup) - - def setWidgetDefaults(self): - ''' - Need to set up some defaults here before building Tk widgets (this is specifically - true w/r/t the StringVars) - ''' - self.currentFID = 0 # file index w/in list of sorted files - self.frame = 0 # current frame of dicom file - self.isClicked = False # used in handling of canvas click events - self.isDragging = False # used in handling of canvas click events - # self.resized = False #for changing widgets after window resize - self.selectBoxX = False - self.selectBoxY = False - - # some styling - # self.fontStyle = Style() - # if util.get_platform() == 'Darwin': - # self.fontStyle.configure('symbol.TButton', font=('DejaVu Serif', 26)) - # else: - # self.fontStyle.configure('symbol.TButton', font=('DejaVu Serif', 19)) - - # declare string variables - # self.currentFileSV = StringVar(self) - # self.frameSV = StringVar(self) - - # initialize string variables - self.currentFileSV = self.Data.files[self.currentFID] - self.frameSV = '1' - - def buildWidgetSkeleton(self): - ''' - Builds the basic skeleton of our app widgets. - - items marked with (*) are built directly in this function - - items marked with (~) are built by the individual modules - # WARNING: out of date diagram - .________________________________________. - | ROOT | - | .____________________________________. | - | | TOP* | | - | | ._______________. .______________. | | - | | | LEFT* | | RIGHT* | | | - | | | - file nav* | | - dicom~ | | | - | | | - frame nav*| | | | | - | | | - traces~ | | | | | - | | | - undo~ | | | | | - | | \_______________/ \______________/ | | - | \____________________________________/ | - | | - | .____________________________________. | - | | BOTTOM* | | - | | - spectrogram~ | | - | | - textgrid~ | | - | \____________________________________/ | - \________________________________________/ - ''' - # main Frame skeleton - self.panel = wx.Panel(self) - self.hbox = wx.BoxSizer(wx.HORIZONTAL) - self.grid = wx.FlexGridSizer(2, 2, 2, 2) - - # self.TOP = wx.Panel(self) - # self.TOP = Frame(self) - # self.TOP.columnconfigure(1,weight=1, minsize=320) - # self.TOP.rowconfigure(0,weight=1, minsize=240) - # self.LEFT = Frame(self.TOP) - # self.LEFT.rowconfigure(0,weight=1) - # self.LEFT.columnconfigure(0,weight=1) - # self.RIGHT = Frame(self.TOP) - # self.RIGHT.rowconfigure(0,weight=1) - # self.RIGHT.columnconfigure(0,weight=1) - # self.BOTTOM = Frame(self) - # self.BOTTOM.columnconfigure(0,weight=1) - # self.BOTTOM.columnconfigure(1,weight=1) - # self.BOTTOM.rowconfigure(0,weight=1) - # self.TOP.grid( row=0, column=0, sticky='nw') - # self.LEFT.grid( row=0, sticky='n' ) - # self.RIGHT.grid( row=0, column=1) - # self.BOTTOM.grid( row=1, column=0, sticky='e') - # self.TOP.grid( row=0, column=0, sticky='nesw') - # self.LEFT.grid( row=0, sticky='nesw' ) - # self.RIGHT.grid( row=0, column=1, sticky='nesw') - # self.BOTTOM.grid( row=1, column=0, sticky='nesw') - # self.pady=3 - # self.columnconfigure(0,weight=1) - # self.rowconfigure(0,weight=1) - # text1 = wx.StaticText(panel, label="controls here") - text2 = wx.StaticText(self.panel, label="US here") - text3 = wx.StaticText(self.panel, label="labels here") - text4 = wx.StaticText(self.panel, label="audio and textgrids here") - - self.audioTGBox = wx.BoxSizer(wx.VERTICAL) - self.audioTGBox.Add(text4) - - self.ultrasoundBox = wx.BoxSizer(wx.VERTICAL) - self.ultrasoundBox.Add(text2) - - self.controlBox = wx.BoxSizer(wx.VERTICAL) - # Buttons - # FileSelectorBox - # FileControlBox - self.fileControlBox = wx.BoxSizer(wx.HORIZONTAL) - self.fileSelectorOpen = wx.Button(self, id=wx.ID_OPEN) # , style=wx.BU_NOTEXT) - self.fileControlBox.Add(self.fileSelectorOpen) - - self.fileSelectorBox = wx.StaticBoxSizer(wx.VERTICAL, self, label="Files") - self.fileSelectorBox.Add(self.fileControlBox) - self.fileSelector = wx.ListCtrl(self, -1, style=wx.LC_REPORT) - self.fileSelector.InsertColumn(0, 'name', width=200) - self.fileSelector.InsertColumn(1, 'annotated', wx.LIST_FORMAT_RIGHT, width=50) - # self.fileSelector.InsertItem(0, 'test') - for filedata in self.Data.files: - self.fileSelector.InsertItem(self.fileSelector.GetItemCount(), filedata) - - self.fileSelectorBox.Add(self.fileSelector, 1, wx.EXPAND) - - # FrameSelectorBox - self.frameSelectorBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="Frames") - self.frameSelectorPrev = wx.Button(self, label="<", style=wx.BU_EXACTFIT) - self.frameSelectorText = wx.TextCtrl(self, style=wx.TE_RIGHT, value=self.frameSV) - self.frameSelectorNext = wx.Button(self, label=">", style=wx.BU_EXACTFIT) - self.buttonPlayPause = wx.Button(self, label="⏯") # style=wx.BU_EXACTFIT) - self.setCharSize(self.frameSelectorNext, 1) - self.setCharSize(self.frameSelectorText, 5) - self.setCharSize(self.frameSelectorPrev, 1) - self.setCharSize(self.buttonPlayPause, 3) - - # self.frameSelectorBox.AddMany([(self.frameSelectorPrev),(self.frameSelectorText),(self.frameSelectorNext),(self.buttonPlayPause)]) - self.frameSelectorBox.Add(self.frameSelectorPrev, flag=wx.LEFT, border=10) - self.frameSelectorBox.Add(self.frameSelectorText) - self.frameSelectorBox.Add(self.frameSelectorNext, flag=wx.RIGHT, border=10) - self.frameSelectorBox.Add(self.buttonPlayPause, border=10) - - # AnnotationsBox - self.annotationsBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="Annotations") - self.buttonSelectAll = self.newIconButton(wx.ID_SELECTALL, "gtk-select-all") - self.buttonCopy = self.newIconButton(wx.ID_COPY, wx.ART_COPY) - # self.buttonCopy = wx.Button(self, id=wx.ID_COPY, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - # self.buttonCopy = wx.BitmapButton(self, id=wx.ID_COPY, bitmap=wx.ArtProvider.GetBitmap(wx.ART_COPY), size=(32,32)) - self.buttonPaste = self.newIconButton(wx.ID_PASTE, wx.ART_PASTE) # wx.Button(self, id=wx.ID_PASTE, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - # self.buttonPaste = wx.BitmapButton(self, id=wx.ID_PASTE, bitmap=wx.ArtProvider.GetBitmap(wx.ART_PASTE), size=(32,32)) - self.buttonDelete = self.newIconButton(wx.ID_DELETE, wx.ART_DELETE) # wx.Button(self, id=wx.ID_DELETE, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonZoomIn = self.newIconButton(wx.ID_ZOOM_IN, "gtk-zoom-in") # wx.Button(self, id=wx.ID_ZOOM_IN, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonZoomOut = self.newIconButton(wx.ID_ZOOM_OUT, "gtk-zoom-out") # wx.Button(self, id=wx.ID_ZOOM_OUT, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonZoomFit = self.newIconButton(wx.ID_ZOOM_FIT, "gtk-zoom-fit") # wx.Button(self, id=wx.ID_ZOOM_FIT, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - self.buttonUndo = self.newIconButton(wx.ID_UNDO, wx.ART_UNDO) - self.buttonRedo = self.newIconButton(wx.ID_REDO, wx.ART_REDO) - - # self.annotationsBox.AddMany([(self.buttonSelectAll),(self.buttonCopy),(self.buttonPaste),(self.buttonDelete)]) - self.annotationsBox.Add(self.buttonSelectAll, flag=wx.LEFT, border=10) - self.annotationsBox.Add(self.buttonCopy, flag=wx.BOTTOM, border=10) - self.annotationsBox.Add(self.buttonPaste) - self.annotationsBox.Add(self.buttonDelete) - self.annotationsBox.Add((20, 0)) - self.annotationsBox.Add(self.buttonUndo) - self.annotationsBox.Add(self.buttonRedo, flag=wx.RIGHT, border=10) - - # ViewBox - self.viewBox = wx.StaticBoxSizer(wx.HORIZONTAL, self, label="View") - self.viewBox.Add(self.buttonZoomIn, flag=wx.LEFT, border=10) - self.viewBox.Add(self.buttonZoomOut, flag=wx.BOTTOM, border=10) - self.viewBox.Add(self.buttonZoomFit, flag=wx.RIGHT, border=10) - - # LayersBox - # self.layersBox = wx.StaticBoxSizer(wx.VERTICAL, self, label="Layers") - - # add all sections to the control box - self.controlBox.AddMany([(self.fileSelectorBox, 1, wx.EXPAND), ((0, 10)), (self.frameSelectorBox, 0, wx.EXPAND), ((0, 10)), (self.annotationsBox, 0, wx.EXPAND), ((0, 10)), (self.viewBox, 0, wx.EXPAND), ((0, 10))]) # (self.layersBox,0,wx.EXPAND)]) - - self.grid.AddMany([(self.controlBox, 1, wx.EXPAND), (self.ultrasoundBox, 1, wx.EXPAND), (text3), (self.audioTGBox)]) - - self.grid.AddGrowableRow(0, 1) - self.grid.AddGrowableCol(0, 1) - - self.hbox.Add(self.grid, proportion=1, flag=wx.ALL | wx.EXPAND, border=2) - self.panel.SetSizer(self.hbox) - self.panel.Fit() - # navigate between all available filenames in this directory - # self.filesFrame = Frame(self.LEFT)#, pady=7) - # self.filesPrevBtn = Button(self.filesFrame, text='<', command=self.filesPrev, takefocus=0, width="1.5") - # self.filesJumpToMenu = OptionMenu(self.filesFrame, self.currentFileSV, self.Data.files[0], *self.Data.files, command=self.filesJumpTo) - # self.filesNextBtn= Button(self.filesFrame, text='>', command=self.filesNext, takefocus=0, width="1.5") - # self.filesFrame.grid( row=1 ) - # self.filesPrevBtn.grid( row=1, column=0 ) - # self.filesJumpToMenu.grid( row=1, column=1 ) - # self.filesNextBtn.grid(row=1, column=2 ) - # Header(self.filesFrame, text="Recording").grid( row=0, column=0, columnspan=3 ) - - # navigate between frames - # self.framesFrame = Frame(self.LEFT)#, pady=7) - # self.framesSubframe = Frame(self.framesFrame) - # self.framesPrevBtn = Button(self.framesSubframe, text='<', command=self.framesPrev, takefocus=0, width="1.5") - # self.framesEntryText = Entry(self.framesSubframe, width=5, textvariable=self.frameSV) - # self.framesEntryBtn = Button(self.framesSubframe, text='Go', command=self.framesJumpTo, takefocus=0, width="3") - # self.framesNextBtn= Button(self.framesSubframe, text='>', command=self.framesNext, takefocus=0, width="1.5") - # self.framesHeader = Header(self.framesFrame, text="Frame") - # self.framesFrame.grid( row=3 ) - # self.framesSubframe.grid( row=1 ) - - # non-module-specific bindings - # if util.get_platform() == 'Linux': - # self.bind('', self.filesPrev ) - # self.bind('', self.filesNext ) - # else: - # self.bind('', self.filesPrev ) - # self.bind('', self.filesNext ) - # self.bind('', self.framesPrev ) - # self.bind('', self.framesNext ) - # self.bind('', self.onBackspace ) - # self.bind('', self.getWinSize) - # self.bind('', self.onRelease) - # self.bind('', self.onDoubleClick) - # self.bind('', self.onEscape ) - # self.count = 0 - - # self.framesEntryText.bind('', self.unfocusAndJump) - # self.framesEntryText.bind('', lambda ev: self.framesFrame.focus()) - - # force window to front - # self.lift() - - def setCharSize(self, obj, sizeInChar): - size = obj.GetSizeFromTextSize(obj.GetTextExtent('9' * sizeInChar)) - obj.SetInitialSize(size) - obj.SetSize(size) - def newIconButton(self, wxId, wxBmp): - # button = wx.Button(self, id=wxId, style=wx.BU_NOTEXT | wx.BU_EXACTFIT) - # button.SetBitmapLabel(wx.ArtProvider.GetBitmap(wxBmp,wx.ART_MENU)) - button = wx.BitmapButton(self, id=wxId, bitmap=wx.ArtProvider.GetBitmap(wxBmp), size=(32, 32)) - return button + info('Loading widgets') - def lift(self): - ''' - Bring window to front (doesn't shift focus to window) - ''' - self.attributes('-topmost', 1) - self.attributes('-topmost', 0) + self.is_resizing = False - def update(self): - pass + def set_widget_defaults(self): + self.current_fid = 0 + self.frame = 0 + self.is_clicked = False + self.is_dragging = False + self.current_path = '' - def geometry(self): - pass + def build_widget_skeleton(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(self.Trace, 1, wx.EXPAND) + main_sizer.Add(self.Dicom, 1, wx.EXPAND) + main_sizer.Add(self.Audio, 0, wx.EXPAND) + main_sizer.Add(self.TextGrid, 0, wx.EXPAND) + self.SetSizer(main_sizer) + self.SetAutoLayout(1) + self.Show() -if __name__ == '__main__': - # app.mainloop() - app = wx.App() - mainFrame = Frame(None, title='UltraTrace') - mainFrame.Show() - while True: - try: - app.MainLoop() - break - except: - print("crashed") - # except UnicodeDecodeError as e: - # error(e) +app = wx.App(False) +frame = Frame(None, 'UltraTrace') +app.MainLoop()