"""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. hi = 0 lo = 255 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)) if z2 > hi: hi = int(z2) if z2 < lo: lo = int(z2) output_line.append(z2) return output_line, hi, lo ## 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.raster_flag = false self.zenith_angle = 30 self.scale_factor = 2.5 self.displaychoice = 'shade' self.filterselect = Image.NEAREST self.new_line = [] self.hi = 255 self.lo = 0 TypeList2 = ['Nearest', 'Bilinear', 'Bicubic'] TypeList = ['shade', 'contour', 'grid'] 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) exitID=wxNewId()# Make a new ID for a menu entry. menu.Append(exitID, '&Save', 'Save Projection') EVT_MENU(self, exitID, self.Picture_Save) 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. #Sizers self.sub_Picture_button_sizer = wxBoxSizer(wxVERTICAL) self.sub_Picture_button_text_sizer = wxBoxSizer(wxVERTICAL) self.Picture_button_sizer = wxBoxSizer(wxHORIZONTAL) self.Picture_sizer = wxBoxSizer(wxVERTICAL) self.AandS_sizer = wxBoxSizer(wxVERTICAL) self.AandS_lable_sizer = wxBoxSizer(wxVERTICAL) self.sub_Picture_button_text_sizer = wxBoxSizer(wxVERTICAL) self.camera_sizer = wxBoxSizer(wxVERTICAL) self.camera_text_sizer = wxBoxSizer(wxVERTICAL) self.ThreeD_button_sizer = wxBoxSizer(wxHORIZONTAL) self.ThreeD_sizer = wxBoxSizer(wxVERTICAL) #Controll ID's self.PictureWindowID = wxNewId() self.ThreeD_slider_ID = wxNewId() self.ThreeD_slider_IDa = wxNewId() self.ThreeDWindowID = wxNewId() self.Picture_Refresh_Button_ID = wxNewId() self.Picture_slider_ID = wxNewId() self.ThreeD_viewID = wxNewId() self.ThreeD_Refresh_Button_ID = wxNewId() self.RAngle_ID = wxNewId() exitID=wxNewId() self.UAngle_ID = wxNewId() self.DAngle_ID = wxNewId() #Controlls self.Splitter = wxSplitterWindow(self, -1) self.Picture_view = wxPanel(self.Splitter, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) self.PictureWindow = wxScrolledWindow(self.Picture_view, self.PictureWindowID, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER) self.ThreeD_view = wxPanel(self.Splitter, self.ThreeD_viewID, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) self.ThreeDWindow = wxScrolledWindow(self.ThreeD_view, self.ThreeDWindowID, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER) self.ThreeD_Refresh_Button = wxButton(self.ThreeD_view, self.ThreeD_Refresh_Button_ID, 'Refresh', wxDefaultPosition) self.Picture_Refresh_Button = wxButton(self.Picture_view, self.Picture_Refresh_Button_ID, 'Refresh', wxDefaultPosition) self.choice2 = wxChoice(self.Picture_view, 80, (80, 50), choices = TypeList2) self.Picture_slider = wxTextCtrl(self.Picture_view, self.Picture_slider_ID, "0", size=(125, -1)) self.choice = wxChoice(self.ThreeD_view, 40, (80, 50), choices = TypeList) #self.ThreeDWindow = wxScrolledWindow(self.ThreeD_view, self.ThreeDWindowID, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER) self.RAngle = wxSpinCtrl(self.ThreeD_view, self.RAngle_ID, "", wxPoint(30, 50), wxSize(80, -1)) self.ThreeD_slidera = wxTextCtrl(self.ThreeD_view, self.ThreeD_slider_IDa, "30", size=(125, -1)) self.ThreeD_slider = wxTextCtrl(self.ThreeD_view, self.ThreeD_slider_ID, "2.5", size=(125, -1)) self.UAngle = wxSpinCtrl(self.ThreeD_view, self.UAngle_ID, "", wxPoint(30, 50), wxSize(80, -1)) self.DAngle = wxSpinCtrl(self.ThreeD_view, self.DAngle_ID, "", wxPoint(30, 50), wxSize(80, -1)) #lables l1 = wxStaticText(self.Picture_view, -1, "Rotation Angle",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l2 = wxStaticText(self.ThreeD_view, -1, "Scale",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l4 = wxStaticText(self.Picture_view, -1, "Method",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l5 = wxStaticText(self.ThreeD_view, -1, "Azimuth",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l6 = wxStaticText(self.ThreeD_view, -1, "Display",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l7 = wxStaticText(self.ThreeD_view, -1, "R Angle",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l8 = wxStaticText(self.ThreeD_view, -1, "Up Angle",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) l9 = wxStaticText(self.ThreeD_view, -1, "Distance",wxPoint(30, 50), wxSize(80, -1),wxALIGN_CENTRE ) #Controll Settings self.choice2.SetStringSelection('Nearest') self.Picture_slider.SetInsertionPoint(0) self.ThreeD_slidera.SetInsertionPoint(0) self.ThreeD_slider.SetInsertionPoint(0) self.choice.SetStringSelection('shade') self.RAngle.SetRange(0,359) self.RAngle.SetValue(190) self.UAngle.SetRange(0,359) self.UAngle.SetValue(25) self.DAngle.SetRange(0,5000) self.DAngle.SetValue(800) self.Splitter.SetMinimumPaneSize(20) self.Splitter.SplitVertically(self.ThreeD_view, self.Picture_view) self.Splitter.SetSashPosition(300) #Class Events EVT_TEXT(self, self.Picture_slider_ID, self.EvtText) EVT_CHOICE(self, 40 , self.EvtChoice) EVT_TEXT(self, self.ThreeD_slider_ID, self.EvtThreeDChar) EVT_BUTTON(self, self.Picture_Refresh_Button_ID, self.Picture_Refresh_Button_Press) EVT_TEXT(self, self.ThreeD_slider_IDa, self.EvtThreeDText) EVT_BUTTON(self, self.ThreeD_Refresh_Button_ID, self.ThreeD_Refresh_Button_Press) EVT_CHOICE(self, 80 , self.EvtChoice2) 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_button_sizer.Add(self.Picture_Refresh_Button, 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.AandS_lable_sizer.Add(l5, 1, wxEXPAND) self.AandS_lable_sizer.Add(l2, 1, wxEXPAND) self.AandS_lable_sizer.Add(l6, 1, wxEXPAND) self.AandS_sizer.Add(self.ThreeD_slidera, 1, wxEXPAND) self.AandS_sizer.Add(self.ThreeD_slider, 1, wxEXPAND) self.AandS_sizer.Add(self.choice, 1, wxEXPAND) self.camera_text_sizer.Add(l7, 1, wxEXPAND) self.camera_text_sizer.Add(l8, 1, wxEXPAND) self.camera_text_sizer.Add(l9, 1, wxEXPAND) self.camera_sizer.Add(self.RAngle, 1, wxEXPAND) self.camera_sizer.Add(self.UAngle, 1, wxEXPAND) self.camera_sizer.Add(self.DAngle, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.ThreeD_Refresh_Button, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.AandS_lable_sizer, 2, wxEXPAND) self.ThreeD_button_sizer.Add(self.AandS_sizer, 1, wxEXPAND) self.ThreeD_button_sizer.Add(self.camera_text_sizer, 2, wxEXPAND) self.ThreeD_button_sizer.Add(self.camera_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.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 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.raster_flag = false 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): self.raster_flag = false try: self.zenith_angle = int(event.GetString()) except ValueError: self.zenith_angle = 30 def EvtThreeDChar(self, event): self.raster_flag = false try: self.scale_factor = float(event.GetString()) except ValueError: self.scale_factor = 2.5 def Picture_Exit(self, event): self.Destroy() def Picture_Refresh_Button_Press(self, event): self.raster_flag = false 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() if not(self.raster_flag): self.new_line, self.hi, self.lo = elevation_line(line_data, self.zenith_angle, self.scale_factor) self.raster_flag = true img_size = self.PIL_bitmap.size try: os.remove(self.globdir) except OSError: if self.displaychoice == 'shade': self.dis_img(self.new_line,img_size[1],img_size[0], self.lo, self.hi) elif self.displaychoice == 'grid': self.dis_img2(self.new_line,img_size[1],img_size[0], self.lo, self.hi) elif self.displaychoice == 'contour': self.dis_img3(self.new_line,img_size[1],img_size[0], self.lo, self.hi) 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 == 'shade': self.dis_img(self.new_line,img_size[1],img_size[0], self.lo, self.hi) elif self.displaychoice == 'grid': self.dis_img2(self.new_line,img_size[1],img_size[0], self.lo, self.hi) elif self.displaychoice == 'contour': self.dis_img3(self.new_line,img_size[1],img_size[0], self.lo, self.hi) 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, zlo, zhi): print zlo, zhi xray = range (n) yray = range (m) 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, n/5, 0, m, 0, m/5, zlo, zhi, zlo, (zhi-zlo)/5) dislin.crvmat (zmat, n, m, 1, 1) dislin.title () dislin.disfin () def dis_img2(self, zmat, m, n, zlo, zhi): RSpin = self.RAngle.GetValue() USpin = self.UAngle.GetValue() DSpin = self.DAngle.GetValue() print RSpin, USpin, DSpin xray = range (n) yray = range (m) dislin.metafl ('PNG') dislin.setpag ('da4l') dislin.setfil (self.globdir) dislin.disini () dislin.pagera () dislin.hwfont () dislin.axis3d(n, m, int(zhi-zlo)) dislin.view3d(RSpin, USpin, DSpin, 'ANGLE') dislin.color('BLUE') dislin.graf3d(0, n, 0, n/5, 0, m, 0, m/5, zlo, zhi, zlo, (zhi-zlo)/5) dislin.surshd(xray, n, yray, m, zmat) dislin.title () dislin.disfin () def dis_img3(self, zmat, n, m, zl, zh): if zl == zh: zl = 0 zh = 10 xray = range (n) yray = range (m) dislin.metafl('PNG') dislin.setpag('da4l') dislin.setfil(self.globdir) dislin.disini() dislin.pagera() dislin.hwfont() dislin.complx() if (n * (1400 / m))< 2000: dislin.axslen(n * (1400 / m), 1400) else: dislin.axslen(2000,m * (2000 / n)) dislin.graf(0, n, 0, n/5, 0, m, 0, m/5) dislin.height (25) for i in range (0, 9): zlev = (i * ((zh-zl)/8)) + zl dislin.labels ('FLOAT', 'CONTUR') dislin.setclr ((i+1) * 23) dislin.contur (xray, n, yray, m, zmat, zlev) dislin.disfin() # 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