示例#1
0
class PicScan_GUI(Tkinter.Tk):

    # Make translucent background for EXIF data
    layer = []
    boxwidth = 360
    boxheight = 1000
    for i in range(boxheight):
        layer += [255-i/4]*boxwidth

    alpha = Image.new("L", (boxwidth,boxheight))
    alpha.putdata( layer, 1, 0 )
    exif_bg = Image.new("RGBA", (boxwidth,boxheight), (70,70,70,0))
    exif_bg.putalpha(alpha)


    def load_settings(self, run_location ):
        try:
            with open( run_location + '\settings.ini', 'r' ) as rfile:
                self.dat = pickle.load(rfile)
            rfile.close()
            print 'Settings loaded'

        except IOError: # If file does not exist
            self.dat = dict( # DEFAULT SETTINGS
                                dir_work=os.getcwd(),
                                jpg_dir = u'C:/',
                                nef_dir = u'C:/',
                                number_of_tiles = 1,
                                mode=0,
                                geometry='1900x1000+0+0',
                                maxWidth = 1900,
                                maxHeight = 1000,
                                tileborder = 1,
                                main_zoom = 0,
                                selectedImage = [0],
                                focalPixel = (0.5,0.5),
                                sobel = False,
                                scale = 1.0,
                                histogram = True,
                                )
            with open( run_location + '\settings.ini', 'w' ) as wfile:
                pickle.dump(self.dat, wfile)
            wfile.close()
            print 'Restored default settings'


    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent

        self.initialize()

    def initialize(self):
        self.run_location = os.getcwd()
        self.load_settings( os.getcwd() )
        self.b1press = None
        self.subwinsize = 150
        self.dat['dir_work'] = os.getcwd()

        try:
            os.chdir( self.dat['dir_work'] )
        except WindowsError:
            self.set_work_dir()


        # CREATE THE MENU BAR
        self.create_menu()
#        self.create_top_controls()

        # LEFT HAND SIDE CONTROLS
#        self.create_left_controls()

        # MAIN SCREEN CANVAS BINDINGS
        self.create_image_canvas()
        self.exif_bg = ImageTk.PhotoImage(self.exif_bg)

        # WINDOW PLACEMENT ON SCREEN
        try:
            self.geometry( self.dat['geometry'] ) # INITIAL POSITION OF TK WINDOW
        except KeyError:
            self.geometry( self.default_settings['geometry'] ) # INITIAL POSITION OF TK WINDOW

        self.update()
        self.canvas.bind('<Configure>', self.resize )

        # FINISHED GUI SETUP
        self.imdatabase = odict()
#        self.thumbs = odict()
        self.mode = 'single'
        self.load_image()

        self.print_settings( 'Settings on initialization' )



    def create_menu(self):
        # MAIN MENU BAR
        menubar = Tkinter.Menu(self)

        # FILE MENU OPTIONS: LOAD, SAVE, EXIT...
        filemenu = Tkinter.Menu(menubar, tearoff=0)
        filemenu.add_command(label="Load image", command=self.load_image)
        filemenu.add_separator()

        ALIGN_MODES = [
                "Align by pixel position",
                "Align by matching"
                ]

        self.rb = Tkinter.StringVar()
        self.rb.set(ALIGN_MODES[1]) # initialize

        for mode in ALIGN_MODES:
            filemenu.add_radiobutton(label=mode, variable=self.rb, value=mode,
                                     command=self.change_click_mode)


        filemenu.add_separator()
        filemenu.add_command(label="Exit", command=self.endsession)
        menubar.add_cascade(label="File", menu=filemenu)

        # HDR MENU OPTIONS
        hdrmenu = Tkinter.Menu(menubar, tearoff=0)
        #TODO: Add HDR options
        menubar.add_cascade(label="HDR", menu=hdrmenu)

        # SFM MENU OPTIONS
        sfmmenu = Tkinter.Menu(menubar, tearoff=0)
        #TODO: Add SFM options
        menubar.add_cascade(label="SFM", menu=sfmmenu)

        # HELP MENU OPTIONS: OPEN LADYBUG API HELP, OPEN WORKING DIRECTORY
        helpmenu = Tkinter.Menu(menubar, tearoff=0)
        helpmenu.add_command(label="About", command=hello)
        helpmenu.add_cascade(label="Open Working Directory", command=self.openWorkingDir)
        menubar.add_cascade(label="Help", menu=helpmenu)

        # SET AND SHOW MENU
        self.config(menu=menubar)


    def create_top_controls(self):
        '''Show the location of the working jpg and nef directories.

        Two text buttons that show and change the working directories.
        Program will search for NEF in both locations.
        One button for auto-move NEFs from JPG folder to NEF folder if found.
        or just put option in menu bar and set default for 'on'.
        '''

        address_bar = Tkinter.Frame(self)
        address_bar_jpg_line = Tkinter.Frame(address_bar)
        address_bar_nef_line = Tkinter.Frame(address_bar)

        self.jpg_dir = Tkinter.StringVar()
        self.jpg_dir.set(self.dat.get('jpg_dir') )
        self.nef_dir = Tkinter.StringVar()
        self.nef_dir.set(self.dat.get('nef_dir') )

        Tkinter.Label(address_bar_jpg_line, text='JPG Dir').pack(side=Tkinter.LEFT,padx=0,pady=0)
        jpg_dir = Tkinter.Button(address_bar_jpg_line,
                                 textvariable=self.jpg_dir,
                                 command=self.set_new_jpg_dir)
        jpg_dir.pack(side=Tkinter.LEFT,padx=0,pady=0, fill='x')

        Tkinter.Label(address_bar_nef_line, text='NEF Dir').pack(side=Tkinter.LEFT,padx=0,pady=0)
        nef_dir = Tkinter.Button(address_bar_nef_line,
                                 textvariable=self.nef_dir,
                                 command=self.set_new_nef_dir)
        nef_dir.pack(side=Tkinter.LEFT,padx=0,pady=0)

        address_bar_jpg_line.pack(side=Tkinter.LEFT)
        address_bar_nef_line.pack(side=Tkinter.LEFT)
        Tkinter.Label(address_bar, text='A:Save All, S:Save JPG, W:Save NEF, D:Delete Both').pack(side=Tkinter.LEFT,padx=0,pady=0)
        address_bar.pack(side=Tkinter.TOP)




    def create_image_canvas(self):
        self.canvas = Tkinter.Canvas(self, width=self.dat['maxWidth'], height=self.dat['maxHeight'], bg='black')
        self.canvas.bind("<ButtonPress-1>", self.click_pt) # DETERMINE NEW POINT OR GRAB EXISTING
        self.canvas.bind("<ButtonPress-3>", self.click_pt) # DETERMINE NEW POINT OR GRAB EXISTING
        self.canvas.bind("<ButtonRelease-3>", self.kill_zoom)
        self.bind("<space>", self.kill_zoom)
        self.canvas.bind("<B1-Motion>", self.shiftImage)
        self.canvas.bind("<B3-Motion>", self.shiftImage)
        self.bind("<Key>", self.keypress)
        self.bind("<Delete>", self.deletekey)
        self.bind("<Escape>", self.endsession)
        self.bind("<Left>", self.change_image)
        self.bind("<Right>", self.change_image)

        self.canvas.bind_all("<MouseWheel>", self.change_image)
        self.bind_all("<Up>", self.change_image)
        self.bind_all("<Down>", self.change_image)

        self.canvas.pack(side=Tkinter.RIGHT, expand=Tkinter.YES, fill=Tkinter.BOTH)






    def load_image(self, curr_dir=''):
        """Load the images from the list of filenames.

        """
        self.filenames = []
#        try:
#            filename = tkFileDialog.askopenfilename(
#                            initialdir=self.dat['jpg_dir'],
#                            )
#            self.fman = FileManager(filename)
#        except:
        filename = tkFileDialog.askopenfilename(
                        initialdir=self.dat['jpg_dir'],
                        )
        self.fman = FileManager(filename)

        self.dat['jpg_dir'] = self.fman.dir

        filename = self.fman.load()
        self.filenames.append( filename )
        self.disp_image = DisplayImage( filename )

        thread.start_new_thread( self.preload_images, () )

        self.show_image()




#    def set_new_jpg_dir(self):
#        self.fman = FileManager()
#        self.jpg_dir.set(self.fman.jpg_dir)
#        self.dat['jpg_dir'] = self.fman.jpg_dir
#
#
#
#    def set_new_nef_dir(self):
#        new_dir = self.fman.newNEFdir( self.nef_dir.get() )
#
#        self.nef_dir.set( new_dir )
#        self.dat['nef_dir'] = new_dir




    def show_image(self):
        """Then crop and display the images in the tiles."""

        self.canvas.delete('text')
        self.canvas.delete('thumbnail')
        self.canvas.delete('zoom')

        if self.disp_image == None:
            return


        maxW = self.dat['maxWidth']
        tileW = maxW / 1
        tileH = self.dat['maxHeight']

        # Crop, anchor and display image
        try:
            w,h = self.disp_image.thumbnail.size
        except:
            w = h = 0
        if tileW != w or tileH != h:
            self.disp_image.set_thumbnail( (tileW-2,tileH-2), self.dat['main_zoom'] )
        self.canvas.create_image(self.disp_image.anchor,
                                 image=self.disp_image.thumbnail,
                                 anchor=Tkinter.NW, tags='thumbnail')
#        self.disp_image.set_box( (0+600, 0+600, tileW+600, tileH+600) )
#        self.canvas.create_image( (tileW, 1), image=self.disp_image.image(), anchor=Tkinter.NW, tags='thumbnail')

        # Overlay info and icons
        self.canvas.create_image((0,0), image=self.exif_bg, anchor=Tkinter.NW, tags='text')


        # Display Histogram and EXIF data
        histo_offset = 0
        if self.dat.get('histogram'):
            self.canvas.create_image( (5,2), image=self.disp_image.get_histogram((350,30)), anchor=Tkinter.NW, tags='thumbnail')
            histo_offset = 34

#        for i, each in enumerate(self.fman.get_filename()):
#            self.canvas.create_text( (5, histo_offset+i*15), text=each, anchor=Tkinter.NW,
#                                 fill='yellow', tags='text')

        if self.fman.hasJPG():
            self.canvas.create_text( (360, 2), text='JPG', anchor=Tkinter.NW,
                                 fill='yellow', tags='text', font='arial 16 bold')
        else:
            self.canvas.create_text( (360, 2), text='JPG', anchor=Tkinter.NW,
                                 fill='grey', tags='text', font='arial 16')

        if self.fman.hasNEF():
            self.canvas.create_text( (360+55, 2), text='NEF', anchor=Tkinter.NW,
                                 fill='yellow', tags='text', font='arial 16 bold')
        else:
            self.canvas.create_text( (360+55, 2), text='NEF', anchor=Tkinter.NW,
                                 fill='grey', tags='text', font='arial 16')

        if self.fman.hasDNG():
            self.canvas.create_text( (360+55+55, 2), text='DNG', anchor=Tkinter.NW,
                                 fill='yellow', tags='text', font='arial 16 bold')
        else:
            self.canvas.create_text( (360+55+55, 2), text='DNG', anchor=Tkinter.NW,
                                 fill='grey', tags='text', font='arial 16')

        self.canvas.create_text( (5, histo_offset+45), text=str(int(self.disp_image.scale*100))+'%', anchor=Tkinter.NW,
                                 fill='yellow', tags='text')

        # Write EXIF information
        for i, each in enumerate(self.disp_image.get_exif()):
            try:
                self.canvas.create_text( (5,histo_offset+60+i*15), text=each + ' - ' + self.disp_image.exif[each], anchor=Tkinter.NW,
                                 fill='yellow', tags='text')
            except:
                print 'error>>>', i, each


        # Display 'zoom' window
        if self.b1press:
            zoom_image = self.get_crop()
            # Calculate placement of full-size window
            placement = (self.b1press[0]-self.subwinsize, self.b1press[1]-self.subwinsize)
            self.canvas.create_image( placement, image=zoom_image, anchor=Tkinter.NW, tags='zoom')




    def show_zoom(self):
            image = self.disp_image.image(sobel=self.dat['sobel'])
            self.canvas.create_image( tile[:2], image=image, anchor=Tkinter.NW, tags='image' )




    def change_click_mode(self):
        print "i'm in!", self.rb.get()
#        if self.rb.get() == "Window Selection":
#            print "Window Selection"
#        if self.rb.get() == "Heading Selection":
#            print "Heading Selection"
#        if self.rb.get() == "Object Selection":
#            print "Object Selection"
#        if self.rb.get() == "Measure Distance":
#            print "Measure Distance"
#        if self.rb.get() == "Off":
#            print "Off"



    def resize(self, event):
        self.dat['maxWidth'] = event.width
        self.dat['maxHeight'] = event.height
        self.dat['geometry'] = str(event.width) + 'x' + str(event.height) + '+0+0'
        self.canvas.config(width = event.width, height = event.height)

        self.show_image()



    def click_pt(self, event):
        if event.widget == self.canvas:
            # GET ASSOCIATED IMAGE
            self.b1press = (event.x, event.y)

            if self.disp_image != None:
                if self.disp_image.point( self.b1press ):
                    self.disp_image.set_box_center( self.b1press )
            else:
                return # IF NO ASSOCIATED IMAGE IS FOUND

            self.draw_zoom()


    def draw_zoom(self):
        self.canvas.delete('zoom')

        print 'creating zoom'
        zoom_image = self.get_crop()
        # Calculate placement of full-size window
        placement = (self.b1press[0]-self.subwinsize, self.b1press[1]-self.subwinsize)
        self.canvas.create_image( placement, image=zoom_image, anchor=Tkinter.NW, tags='zoom')




    def get_crop(self):
        ret_pt = self.disp_image.point(self.b1press)
        self.full_image = self.disp_image.image_full( ret_pt, radius=self.subwinsize )
        return self.full_image



    def shiftImage(self, event):
        self.b1press = (event.x,event.y)
        if event.widget == self.canvas and self.disp_image.point( self.b1press ):
            self.draw_zoom()







    def change_image(self, event):
#        print event.type,'|', event.char,'|', event.keysym,'|', event.keycode,'|', event.state

        # IF WHEEL TURNED, NOT HELD DOWN
        if (event.keycode == 37 and event.keysym == 'Left') or \
            (event.keycode == 120 and event.state == 8): # 10 is with caps lock
            self.next_image(event, -1)
            return
        elif (event.keycode == 39 and event.keysym == 'Right') or \
            (event.keycode == -120 and event.state == 8): # 10 is with caps lock
            self.next_image(event, 1)
            return

#
        # IF WHEEL BUTTON DOWN AND TURNED
        elif (event.keycode == 38 and event.keysym == 'Up') or \
            (event.state == 520 and event.keycode == 120):
            self.subwinsize += 25
        elif (event.keycode == 40 and event.keysym == 'Down') or \
            (event.state == 520 and event.keycode == -120):
            self.subwinsize -= 25

        if self.b1press:
            self.draw_zoom()



    def keypress(self, event):
        if event.char and event.char in 'asdwhe':
            if   event.char == 'd':
                if self.mode == 'single':
                    self.fman.trash_image()
                elif self.mode == 'paired':
                    self.fman.delALL()
            elif event.char == 'w':
                if self.mode == 'single':
                    pass
                elif self.mode == 'paired':
                    self.fman.saveNEF()
            elif event.char == 's':
                if self.mode == 'single':
                    self.fman.save_image()
                elif self.mode == 'paired':
                    if self.fman.hasJPG() == False:
                        self.disp_image.make_jpg()
                    self.fman.saveJPG()
            elif event.char == 'a':
                if self.mode == 'single':
                    self.fman.save_image()
                elif self.mode == 'paired':
                    if self.fman.hasJPG() == False:
                        self.disp_image.make_jpg()
                    self.fman.saveALL()
            elif event.char == 'h':
                self.dat['histogram'] = not self.dat.get('histogram')
                self.f.set_visible( self.dat['histogram'] )
            elif event.char == 'e': self.fman.undo_last()

            filename = self.fman.load()
            if filename not in self.filenames:
                self.filenames.append( filename )
            try:
                self.disp_image = DisplayImage( filename )
            except:
                self.next_image(1)
            self.show_image()
#        print 'event.char', event.char


    def kill_zoom(self, event):
        self.b1press = None
        self.canvas.delete('zoom')



    def next_image(self, event, val=0):
        self.currfile = self.fman.load(val)
        if self.currfile in self.imdatabase:
            print 'Preloaded', self.currfile
            self.disp_image = self.imdatabase[self.currfile]
        else:
            try:
                self.disp_image = DisplayImage( self.currfile )
                maxW = self.dat['maxWidth']
                tileW = maxW / 1
                tileH = self.dat['maxHeight']
                self.disp_image.set_thumbnail( (tileW-2,tileH-2), self.dat['main_zoom'] )
                self.imdatabase[self.currfile] = self.disp_image
            except:
                self.disp_image = None



        if len(self.imdatabase) > 12:
            tmp,tmp = self.imdatabase.popitem(False)
            del tmp
        print 'Images in memory', len(self.imdatabase)

        self.show_image()



    def preload_images(self):

        tmp_path = os.path.join( os.getcwd(), 'tmp' )
        if not os.path.exists( tmp_path ):
            os.mkdir( tmp_path )

        for filename in self.fman.imfiles:
            filename = os.path.join(self.fman.dir, filename)
            print 'Finding:', filename

            name = os.path.split( filename )[1]
            ppm = os.path.join( tmp_path, name[:-3] + 'ppm' )
            exiffile = os.path.join( tmp_path, name + '.exif' )
            if os.path.exists( ppm ):
                continue
            elif filename.endswith('nef'):
                im = get_exiftool_jpeg( filename )
                im.thumbnail((2464,2464), Image.BICUBIC)
                im.save( ppm, quality=99 )
                exif = get_exiftool_exif( filename )
                with open(exiffile, 'w') as wfile:
                    pickle.dump(exif, wfile )
            elif filename.endswith('jpg'):
                im = Image.open( filename )
                im.thumbnail((2464,2464), Image.BICUBIC)
                im.save( ppm, quality=99 )
                exif = get_exiftool_exif( filename )
                with open(exiffile, 'w') as wfile:
                    pickle.dump(exif, wfile )


    def deletekey(self, event):
        if self.disp_image:
            # Delete paired JPG/NEF/DNG if in paired mode
            if self.mode == 'paired':
                self.fman.delALL()
            # Delete individual file if in single mode
            elif self.mode == 'single':
                self.fman.trash_image()

            # Load new display image
            filename = self.fman.load()
            if filename not in self.filenames:
                self.filenames.append( filename )
            self.disp_image = DisplayImage( filename )
            self.show_image()





    def endsession(self):
        with open( self.run_location + '\settings.ini', 'w' ) as wfile:
            pickle.dump(self.dat, wfile)
        wfile.close()
        del self.imdatabase
        self.quit()




    def sup(self, x):
        if isinstance(x,list):
            for i, each in enumerate(x):
                x[i] = each*1616./self.dat['imagesize'][1]
            return x
        return x*1616./self.dat['imagesize'][1]
    def sdown(self, x):
        if isinstance(x,list):
            for i, each in enumerate(x):
                x[i] = each*self.dat['imagesize'][1]/1616.
            return x
        return x*self.dat['imagesize'][1]/1616.



    def openWorkingDir(self):
        print 'Opening current working directory'
        os.startfile(os.getcwd())



    def set_work_dir(self):
        self.dat['dir_work'] = askdirectory(title='Choose a directory to store all session related files')





    def print_settings(self, string):
        print string + ':'
        for k in self.dat:
            print '\t', k, ':', self.dat[k]