"""Shape from Shading A method for determining the shape of a surface from its image "Shape from shading" (also known as photoclinometry) is a method for determining the shape of a surface from its image. For a surface of constant albedo, the brightness at a point (x,y) in the image is related to the gradients (p,q) by the following expression: i(x,y) = a R[p(x,y),q(x,y)] where R is the reflectance map, p = dz/dx and q = dz/dy are the partial derivatives of the surface in the x- and y-directions, and a is a constant that depends on the albedo, the gain of the imaging system and other factors. The above expression also assumes that any additive offsets, for example, because of atmospheric scattering, have been removed. A variety of methods have been developed for inverting the above equation (see Horn 1990). The next section describes a simple method that provides satisfactory results in many planetary imaging scenarios. It is based on some early ideas described by Horn (1977). . If the image is rotated so that the vector that points to the sun is in the x-z plane, it can be shown that i(x,y) ~ a [sin(s) p(x,y) + cos(s)] where s is the zenith angle of the sun. The constant scale factor a is difficult to determine directly without ground truth (that is, ground targets with known albedo and slope). However, because in most images the gradients are more-or-less uniformly distributed in all directions, the expected value of the gradient in the x-direction E[p] ~ 0 and so the average image brightness E[i] ~ a cos(s). This then allows us to estimate the scale factor a = E[i] / cos(s). The elevation map z(x,y) can be obtained iteratively, row-by-row as z(x,y) = z(x-1,y) + [i(x,y) - a cos(s)] / a sin(s) z(x) = z(x-1) + [i(x) - a cos(s)] / a sin(s) where z(0,y) are the boundary values. If the boundary values z(0,y) are unknown, we can minimize the mean-squared elevation difference between rows by subtracting the average row elevation from the elevations in the row.""" import os #from pil import Image,ImageChops import Image import ImageChops from wxPython.wx import * from wxPython.lib.imagebrowser import * from math import * import dislin wxInitAllImageHandlers() def imgtyp(file_nm):#returns wx image typ fl_fld = os.path.splitext(file_nm) ext = fl_fld[1] ext = string.lower(ext[1:]) if ext == 'bmp': image = wxBITMAP_TYPE_BMP elif ext == 'gif': image = wxBITMAP_TYPE_GIF elif ext == 'png': image = wxBITMAP_TYPE_PNG elif ext == 'jpg': image = wxBITMAP_TYPE_JPEG return image def get_img(frame): #gets the image file name dir = os.getcwd() # get working directory initial_dir = os.path.join(dir, 'bitmaps') # set the initial directory for the demo bitmaps win = ImageDialog(frame, initial_dir) # open the image browser dialog win.Centre() if win.ShowModal() == wxID_OK: return win.GetFile() # show the selected file else: return 'Unnamed' def elevation_line(line_data, zenith_angle, scale_factor):#calculates elivation data output_line = [] z = 0. z2 = 0. avg = 0. for i in line_data: avg = avg + i avg = avg / len(line_data) z = avg + ( - scale_factor * cos(zenith_angle)) / (scale_factor * sin(zenith_angle)) for i in line_data: z2 = z + ( i - scale_factor * cos(zenith_angle)) / (scale_factor * sin(zenith_angle)) output_line.append(z2) return output_line ## Create a new frame class, derived from the wxPython Frame. class ViewerFrame(wxFrame): def __init__(self, parent, id, title): # First, call the base class' __init__ method to create the frame wxFrame.__init__(self, parent, id, title,wxDefaultPosition,wxDefaultSize) self.rotationangle = 0 self.zenith_angle = 30 self.scale_factor = 2.5 self.zlevel = 100 self.displaychoice = 'contour' self.filterselect = Image.NEAREST self.globdir = os.path.join(os.getcwd(),'glob.png') self.splochdir = os.path.join(os.getcwd(),'sploch.png') self.mainmenu = wxMenuBar() # Create menu bar. menu=wxMenu() # Make a menu (will be the Open menu) exitID=wxNewId() # Make a new ID for a menu entry. menu.Append(exitID, '&Open', 'Open Picture') # Name the ID by adding it to the menu. EVT_MENU(self, exitID, self.Picture_Open) # Create and assign a menu event. exitID=wxNewId() menu.Append(exitID, '&Save', 'Save Projection') EVT_MENU(self, exitID, self.Picture_Save) exitID=wxNewId() menu.Append(exitID, 'E&xit', 'Exit program') EVT_MENU(self, exitID, self.Picture_Exit) self.mainmenu.Append (menu, '&File') # Add the File menu to the menu bar. self.SetMenuBar (self.mainmenu) # Attach the menu bar to the window. self.Splitter = wxSplitterWindow(self, -1) self.Picture_button_sizer = wxBoxSizer(wxHORIZONTAL) self.Picture_sizer = wxBoxSizer(wxVERTICAL) self.AandS_sizer = wxBoxSizer(wxVERTICAL) self.AandS_lable_sizer = wxBoxSizer(wxVERTICAL) self.Picture_view = wxPanel(self.Splitter, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) self.PictureWindowID = wxNewId() self.PictureWindow = wxScrolledWindow(self.Picture_view, self.PictureWindowID, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER) self.Picture_Refresh_Button_ID = wxNewId() self.Picture_Refresh_Button = wxButton(self.Picture_view, self.Picture_Refresh_Button_ID, 'Refresh', wxDefaultPosition) EVT_BUTTON(self, self.Picture_Refresh_Button_ID, self.Picture_Refresh_Button_Press) self.Picture_slider_ID = wxNewId() l4 = wxStaticText(self.Picture_view, -1, "Method") TypeList2 = ['Nearest', 'Bilinear', 'Bicubic'] self.choice2 = wxChoice(self.Picture_view, 80, (80, 50), choices = TypeList2) self.choice2.SetStringSelection('Nearest') EVT_CHOICE(self, 80 , self.EvtChoice2) l1 = wxStaticText(self.Picture_view, -1, "Rotation Angle") self.Picture_slider = wxTextCtrl(self.Picture_view, self.Picture_slider_ID, "0", size=(125, -1)) self.Picture_slider.SetInsertionPoint(0) EVT_TEXT(self, self.Picture_slider_ID, self.EvtText) self.Picture_button_sizer.Add(self.Picture_Refresh_Button, 1, wxEXPAND) self.sub_Picture_button_sizer = wxBoxSizer(wxVERTICAL) self.sub_Picture_button_text_sizer = wxBoxSizer(wxVERTICAL) self.sub_Picture_button_text_sizer.Add(l1, 1, wxEXPAND) self.sub_Picture_button_text_sizer.Add(l4, 1, wxEXPAND) self.sub_Picture_button_sizer.Add(self.Picture_slider, 1, wxEXPAND) self.sub_Picture_button_sizer.Add(self.choice2, 1, wxEXPAND) self.Picture_button_sizer.Add(self.sub_Picture_button_text_sizer, 1, wxEXPAND) self.Picture_button_sizer.Add(self.sub_Picture_button_sizer, 1, wxEXPAND) self.Picture_sizer.Add(self.Picture_button_sizer, 1, wxEXPAND) self.Picture_sizer.Add(self.PictureWindow, 10, wxEXPAND) self.Picture_view.SetSizer(self.Picture_sizer) self.Picture_view.SetAutoLayout(1) self.Picture_sizer.Fit(self.Picture_view) self.ThreeD_button_sizer = wxBoxSizer(wxHORIZONTAL) self.ThreeD_sizer = wxBoxSizer(wxVERTICAL) self.ThreeD_view = wxPanel(self.Splitter, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) self.ThreeD_viewID = wxNewId() self.ThreeD_view = wxPanel(self.Splitter, self.ThreeD_viewID, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) self.ThreeDWindowID = wxNewId() self.ThreeDWindow = wxScrolledWindow(self.ThreeD_view, self.ThreeDWindowID, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER) self.ThreeD_Refresh_Button_ID = wxNewId() self.ThreeD_Refresh_Button = wxButton(self.ThreeD_view, self.ThreeD_Refresh_Button_ID, 'Refresh', wxDefaultPosition) EVT_BUTTON(self, self.ThreeD_Refresh_Button_ID, self.ThreeD_Refresh_Button_Press) self.ThreeD_slider_ID = wxNewId() self.ThreeD_slider_IDa = wxNewId() l1 = wxStaticText(self.ThreeD_view, -1, "Azimuth") self.ThreeD_slidera = wxTextCtrl(self.ThreeD_view, self.ThreeD_slider_IDa, "30", size=(125, -1)) self.ThreeD_slidera.SetInsertionPoint(0) EVT_TEXT(self, self.ThreeD_slider_IDa, self.EvtThreeDText) l2 = wxStaticText(self.ThreeD_view, -1, "Scale") self.ThreeD_slider = wxTextCtrl(self.ThreeD_view, self.ThreeD_slider_ID, "2.5", size=(125, -1)) self.ThreeD_slider.SetInsertionPoint(0) l3 = wxStaticText(self.ThreeD_view, -1, "Debth") self.color_textID = wxNewId() self.color_text = wxTextCtrl(self.ThreeD_view, self.color_textID, "100", size=(125, -1)) self.ThreeD_slider.SetInsertionPoint(0) EVT_TEXT(self, self.color_textID, self.EvtColorText) EVT_TEXT(self, self.ThreeD_slider_ID, self.EvtThreeDChar) self.ThreeD_button_sizer.Add(self.ThreeD_Refresh_Button, 1, wxEXPAND) self.AandS_lable_sizer.Add(l1, 1, wxEXPAND) self.AandS_sizer.Add(self.ThreeD_slidera, 1, wxEXPAND) self.AandS_lable_sizer.Add(l2, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.AandS_lable_sizer, 1, wxEXPAND) self.AandS_sizer.Add(self.ThreeD_slider, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.AandS_sizer, 1, wxEXPAND) self.checkboxtext_sizer = wxBoxSizer(wxVERTICAL) self.checkbox_sizer = wxBoxSizer(wxVERTICAL) l4 = wxStaticText(self.ThreeD_view, -1, "Display") TypeList = ['contour', 'grid'] self.choice = wxChoice(self.ThreeD_view, 40, (80, 50), choices = TypeList) self.choice.SetStringSelection('contour') EVT_CHOICE(self, 40 , self.EvtChoice) self.checkboxtext_sizer.Add(l3, 1, wxEXPAND) self.checkboxtext_sizer.Add(l4, 1, wxEXPAND) self.checkbox_sizer.Add(self.color_text, 1, wxEXPAND) self.checkbox_sizer.Add(self.choice, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.checkboxtext_sizer, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.checkbox_sizer, 1, wxEXPAND) self.ThreeD_sizer.Add(self.ThreeD_button_sizer, 1, wxEXPAND) self.ThreeD_sizer.Add(self.ThreeDWindow, 10, wxEXPAND) self.ThreeD_view.SetSizer(self.ThreeD_sizer) self.ThreeD_view.SetAutoLayout(1) self.ThreeD_sizer.Fit(self.ThreeD_view) self.Splitter.SetMinimumPaneSize(20) self.Splitter.SplitVertically(self.ThreeD_view, self.Picture_view) self.Splitter.SetSashPosition(300) self.imgfile = 'Unnamed' if len(sys.argv) > 1: self.imgfile = sys.argv[1] elif self.imgfile == 'Unnamed': self.imgfile = get_img(self) if self.imgfile == 'Unnamed': sys.exit() self.PIL_bitmap = Image.open(self.imgfile).convert("L") new_box = int(sqrt((self.PIL_bitmap.size[0]**2)+(self.PIL_bitmap.size[1]**2))) new_image = Image.new("L",(new_box,new_box)) up_left = (int((new_box - self.PIL_bitmap.size[0])/2),int((new_box - self.PIL_bitmap.size[1])/2)) new_image.paste(self.PIL_bitmap,up_left) self.PIL_bitmap = new_image self.PIL_bitmap.save(self.splochdir) self.view_bitmap = wxBitmap(self.splochdir,wxBITMAP_TYPE_PNG) self.bitmapID = wxNewId() self.bitmap2ID = wxNewId() self.glob_thing = wxStaticBitmap(self.ThreeDWindow, self.bitmap2ID,self.view_bitmap, wxPoint(0,0), wxSize(853, 603)) self.static_thing = wxStaticBitmap(self.PictureWindow, self.bitmapID, self.view_bitmap, wxDefaultPosition, wxSize(self.view_bitmap.GetWidth(), self.view_bitmap.GetHeight())) self.ThreeDWindow.SetScrollbars(1, 1, 853, 603) self.PictureWindow.SetScrollbars(1, 1, self.PIL_bitmap.size[0],self.PIL_bitmap.size[1]) def OnCloseWindow(self, event): # tell the window to kill itself self.Destroy() def EvtColorText(self, event): try: self.zlevel = int(event.GetString()) except ValueError: self.zlevel = 100 def EvtChoice(self, event): self.displaychoice = event.GetString() def EvtChoice2(self, event): if event.GetString() == 'Bilinear': self.filterselect = Image.BILINEAR elif event.GetString() == 'Bicubit': self.filterselect = Image.BICUBIC else: self.filterselect = Image.NEAREST def Picture_Open(self, event):#self.splochdir tempimgfile = get_img(self) if tempimgfile <> 'Unnamed': self.imgfile = tempimgfile self.PIL_bitmap = Image.open(self.imgfile).convert("L") new_box = int(sqrt((self.PIL_bitmap.size[0]**2)+(self.PIL_bitmap.size[1]**2))) new_image = Image.new("L",(new_box,new_box)) up_left = (int((new_box - self.PIL_bitmap.size[0])/2),int((new_box - self.PIL_bitmap.size[1])/2)) new_image.paste(self.PIL_bitmap,up_left) self.PIL_bitmap = new_image self.PIL_bitmap.save(self.splochdir) self.view_bitmap = wxBitmap(self.splochdir,wxBITMAP_TYPE_PNG) self.static_thing = wxStaticBitmap(self.PictureWindow, self.bitmapID, self.view_bitmap, wxDefaultPosition, wxSize(self.view_bitmap.GetWidth(), self.view_bitmap.GetHeight())) self.PictureWindow.SetScrollbars(1, 1, self.PIL_bitmap.size[0],self.PIL_bitmap.size[1],0,0) def Picture_Save(self, event): path = os.path.join(os.getcwd(),'glob2.png') dlg = wxFileDialog(self, "Save Shading Map", ".", "","BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif|Jpeg files (*.jpg)|*.jpg|PNG files (*.png)|*.png|All files (*.*)|*.*", wxSAVE) if dlg.ShowModal() == wxID_OK: path = dlg.GetPath() self.view_bit.SaveFile(path,imgtyp(path)) else: self.view_bit.SaveFile(path,imgtyp(path)) dlg.Destroy() def EvtText(self, event): try: self.rotationangle = int(event.GetString()) except ValueError: self.rotationangle = 0 def EvtThreeDText(self, event): try: self.zenith_angle = int(event.GetString()) except ValueError: self.zenith_angle = 30 def EvtThreeDChar(self, event): try: self.scale_factor = float(event.GetString()) except ValueError: self.zenith_angle = 30 def Picture_Exit(self, event): self.Destroy() def Picture_Refresh_Button_Press(self, event): tempimg = self.PIL_bitmap.rotate(self.rotationangle,self.filterselect) self.PIL_bitmap = tempimg tempimg.save(self.splochdir) self.view_bitmap = wxBitmap(self.splochdir,wxBITMAP_TYPE_PNG) self.static_thing.SetBitmap(self.view_bitmap) self.PictureWindow.SetScrollbars(1, 1, self.PIL_bitmap.size[0],self.PIL_bitmap.size[1]) def ThreeD_Refresh_Button_Press(self, event): New_Bitmap2 = self.PIL_bitmap.rotate(-90) New_Bitmap = ImageChops.invert(New_Bitmap2) line_data = New_Bitmap.getdata() new_line = elevation_line(line_data, self.zenith_angle, self.scale_factor) img_size = self.PIL_bitmap.size try: os.remove(self.globdir) except OSError: if self.displaychoice == 'contour': self.dis_img(new_line,img_size[1],img_size[0],self.zlevel) elif self.displaychoice == 'grid': self.dis_img2(new_line,img_size[1],img_size[0],self.zlevel) self.view_bit = wxBitmap(self.globdir,wxBITMAP_TYPE_PNG) self.glob_thing.SetBitmap(self.view_bit) #self.ThreeDWindow.SetScrollbars(1, 1, 853, 603) else: if self.displaychoice == 'contour': self.dis_img(new_line,img_size[1],img_size[0],self.zlevel) elif self.displaychoice == 'grid': self.dis_img2(new_line,img_size[1],img_size[0],self.zlevel) self.view_bit = wxBitmap(self.globdir,wxBITMAP_TYPE_PNG) self.glob_thing.SetBitmap(self.view_bit) #self.ThreeDWindow.SetScrollbars(1, 1, 853, 603) def dis_img(self,zmat,m, n,zl): xray = range (n) yray = range (m) zlev = range (zl) dislin.metafl ('PNG') dislin.setpag ('da4l') dislin.setfil (self.globdir) dislin.disini () dislin.pagera () dislin.hwfont () dislin.ax3len (1400, 1400,1400) dislin.autres(n,m) dislin.shdmod ('poly', 'contur') dislin.graf3 (0, n, 0, 100, 0, m, 0, 100, 0, zl, 0, 5) dislin.crvmat (zmat, n,m,1,1) dislin.title () dislin.disfin () def dis_img2(self,zmat,m, n,zl): xray = range (n) yray = range (m) zlev = range (zl) dislin.metafl ('PNG') dislin.setpag ('da4l') dislin.setfil (self.globdir) dislin.disini () dislin.pagera () dislin.hwfont () dislin.axis3d(n,m,255) dislin.view3d(190,35,1650,'ANGLE') dislin.color('BLUE') dislin.graf3d(0, n, 0,100, 0, m, 0, 100,0,zl,0, 100) dislin.surshd(xray,n,yray,m,zmat) dislin.title () dislin.disfin () def get_sploch(self,file): pass # Every wxWindows application must have a class derived from wxApp class MyApp(wxApp): # wxWindows calls this method to initialize the application def OnInit(self): # Create an instance of our customized Frame class frame = ViewerFrame(NULL, -1, "Shape from Shading") frame.Show(true)# Tell wxWindows that this is our main window self.SetTopWindow(frame) # Return a success flag return true app = MyApp(0) # Create an instance of the application class app.MainLoop() # Tell it to start processing events