def array_tinting_region_border_with_falloff(img, regions, colors, alpha):
    '''
    Given an image 'img', a sequence of two abutting regions 'regions' of
    coordinates specifying a region 'img[ regions[r] ]',
    a sequence of two tuples of three rgb values 'colors',
    and minimum and maximum blend values 'min_alpha' and 'max_alpha'
    in the range [0,1],
    returns a copy of 'img' with the regions tinted such that they each are
    tinted by their corresponding colors most strongly at their shared border
    and gradually falling off to the untinted value at the rest of their boundaries;
    the value 'alpha' = 1 means the tint is opaque at the shared border,
    while a value of 'alpha' = 0 means no tinting occurs.
    '''

    #debugger()

    assert img.dtype == uint8
    ## NOTE: We are assuming an rgb image, though we don't actually need one.
    ##       Really all we need is to be able to do img[i,j] = color.
    assert len(img.shape) == 3
    assert img.shape[2] == 3
    assert len(set([id(r) for r in regions])) == len(regions)
    assert False not in [len(color) == 3 for color in colors]

    alpha = float(alpha)
    alpha = max(0., min(1., alpha))

    ## 1 Grow each region by one pixel.
    ## 2 For each region, solve a laplace equation where values
    ##   are zero outside the region and one at points that are both
    ##   inside the region and inside one or more other grown regions.
    ## 3 Blend according to the solution to the laplace equation.

    ## Make a copy.
    result = array(img)

    ## 1
    masks = []
    for region in regions:
        mask = zeros(img.shape[:2], dtype=bool)
        mask[region] = True
        masks.append(mask)

    grown_masks = [grow(mask, 1) for mask in masks]

    ## 2
    kBufferPixels = 2
    for region, mask, grown_mask, color in zip(regions, masks, grown_masks,
                                               colors):
        ## This is the smaller region inside the image that we are looking at.
        crop = region[0].min(), region[0].max() + 1, region[1].min(
        ), region[1].max() + 1
        ## This is the mask pasted onto a canvas of zeros with a two-pixel border.
        crop_mask = zeros((crop[1] - crop[0] + kBufferPixels * 2,
                           crop[3] - crop[2] + kBufferPixels * 2),
                          dtype=bool)
        crop_mask[kBufferPixels:-kBufferPixels,
                  kBufferPixels:-kBufferPixels][mask[crop[0]:crop[1],
                                                     crop[2]:crop[3]]] = True
        assert len(where(crop_mask)) == len(where(mask))

        ## 'grown_mask - mask' gives us just the one-ring around make, but we need the two ring.
        ## For now, just constrain everything outside the region.
        #zero_constraints = set([ (i,j,0) for i,j in zip( *where( grown_mask - mask ) ) ])
        #zero_constraints = set([ (i,j) for i,j in zip( *where( logical_not( mask ) ) ) ])
        zero_constraints = set([
            (i, j) for i, j in zip(*where(logical_not(crop_mask)))
        ])

        one_constraints = set()
        for other, other_mask, other_grown_mask in zip(regions, masks,
                                                       grown_masks):
            if other is region: continue
            ## This may create duplicate entries in one_constraints, so we'll make it a set.
            ## Q: Should I use 'mask' or 'grown'mask to logical_and() with 'other_grown_mask'?
            ## A1: Use 'mask', so I get only pixels inside 'region'.
            ## A2: Use 'grown_mask', so I get a border of pixels with
            ##     thickness 2, which is important for the boundary
            ##     constraints in the laplacian system.
            ## A2 UPDATE: There will now be a conflict between some of the
            ##            zero_constraints and the one_constraints that come
            ##            from the grown part of grown_mask.
            one_constraints.update([
                (i - crop[0] + kBufferPixels, j - crop[2] + kBufferPixels)
                for i, j in zip(
                    *where(logical_and(grown_mask, other_grown_mask)))
            ])

        ## Because we're using grown_mask for one_constraints, there
        ## will be conflict between one_constraints and zero_constraints.
        #assert len( one_constraints.intersection( zero_constraints ) ) == 0
        assert len(one_constraints.intersection(zero_constraints)) > 0
        ## Remove the conflicting elements from 'zero_constraints'.
        zero_constraints = zero_constraints.difference(one_constraints)

        ## Solve the laplace equation.
        ## TODO: Shrink down to a bounding box around 'region' to avoid
        ##       solving a *much* larger system than necessary.
        ##       Something like: bounds = min( region[0] ), max( region[0] ), min( region[1] ), max( region[1] )
        ##       and then taking a slice of 'bounds' after expanding it on
        ##       either side by 2, putting that in a smaller array,
        ##       solving there, then using that to modify a subregion of 'result'.
        from recovery import solve_grid_linear
        ## Q: Laplacian or Bilaplacian?
        ## A: Laplacian.  They both produce similar results but bilaplacian is slower (*much* slower with an iterative method, which we're using).
        blend = solve_grid_linear(
            crop_mask.shape[0],
            crop_mask.shape[1],
            value_constraints=[(i, j, 0.) for i, j in zero_constraints] +
            [(i, j, 1.) for i, j in one_constraints],
            bilaplacian=False)
        ## Clip blend to the area within 'region'.
        blend[logical_not(crop_mask)] = 0.
        blend *= alpha

        ## 3
        #debugger()
        result_slice = result[crop[0]:crop[1], crop[2]:crop[3]]
        result_slice[:] = (result_slice +
                           blend[kBufferPixels:-kBufferPixels,
                                 kBufferPixels:-kBufferPixels, newaxis] *
                           (color - result_slice)).round().astype(uint8).clip(
                               0, 255)

    return result
def array_tinting_region_with_falloff_from_center(img, region, color, alpha):
    '''
    Given an image 'img', a region 'region' of coordinates
    specifying a region 'img[ region ]',
    a tuple of three rgb values 'color',
    and a blend value 'alpha' in the range [0,1],
    returns a copy of 'img' with the given region
    tinted by 'color' such that the tint is strongest in the
    center of the region and falls off at the boundary.
    A value of 'alpha' = 1 means the region is replaced with 'color' at its center
    and a value of 'alpha' = 0 means no change anywhere.
    '''

    assert img.dtype == uint8
    ## NOTE: We are assuming an rgb image, though we don't actually need one.
    ##       Really all we need is to be able to do img[i,j] = color.
    assert len(img.shape) == 3
    assert img.shape[2] == 3
    assert len(color) == 3

    alpha = float(alpha)
    alpha = max(0., min(1., alpha))

    kCropAndBuffer = True
    if not kCropAndBuffer:
        r = zeros(img.shape[:2], dtype=bool)
        r[region] = True
    else:
        ## Only solve a crop around the region.
        ## Add a buffer pixel so I can have a proper boundary conditions around
        ## the region even if it reaches all the way to the edge of the image.
        region_mask = zeros(img.shape[:2], dtype=bool)
        region_mask[region] = True

        kBufferPixels = 1
        crop = region[0].min(), region[0].max() + 1, region[1].min(
        ), region[1].max() + 1
        r = zeros((crop[1] - crop[0] + kBufferPixels * 2,
                   crop[3] - crop[2] + kBufferPixels * 2),
                  dtype=bool)
        r[kBufferPixels:-kBufferPixels,
          kBufferPixels:-kBufferPixels][region_mask[crop[0]:crop[1],
                                                    crop[2]:crop[3]]] = True

    ## Solve a bilaplacian system to make a bump inside the region.
    router = grow(r, 1)
    router[r] = False
    rinner = logical_not(grow(logical_not(r), 1))
    rim = r - rinner
    ## NOTE: I used to constrain 'rim' to '0' and 'router' to '-1', but that leads to a divide-by-zero
    ##       if the region is one or two pixels wide.
    value_constraints = [(i, j, 1.) for i, j in zip(*where(rim))
                         ] + [(i, j, 0.) for i, j in zip(*where(router))]
    print 'len( value_constraints ):', len(value_constraints)
    from recovery import solve_grid_linear
    bump = solve_grid_linear(r.shape[0],
                             r.shape[1],
                             bilaplacian=True,
                             value_constraints=value_constraints,
                             iterative=False)
    ## Mask the bump outside the region.
    bump[logical_not(r)] = 0.
    assert bump.min() >= -1e-5
    ## Normalize the bump so the maximum value is 1.
    bump *= 1. / bump.max()
    ## Guarantee it despite floating point rounding.
    bump = bump.clip(0., 1.)

    ## Make a copy of the input image.
    result = array(img)
    ## Blend the area specified by 'region' with 'color' according to 'alpha' and 'bump', linearly.
    bump *= alpha

    if not kCropAndBuffer:
        result[region] = (img[region] + bump[region][:, newaxis] *
                          (color - img[region])).round().astype(uint8).clip(
                              0, 255)
    else:
        result_slice = result[crop[0]:crop[1], crop[2]:crop[3]]
        result_slice[:] = (result_slice +
                           bump[kBufferPixels:-kBufferPixels,
                                kBufferPixels:-kBufferPixels, newaxis] *
                           (color - result_slice)).round().astype(uint8).clip(
                               0, 255)

    #import Image
    #Image.fromarray( asarray( result, dtype = uint8 ) ).show()
    #debugger()
    return result
def array_tinting_region_with_falloff_from_center( img, region, color, alpha ):
    '''
    Given an image 'img', a region 'region' of coordinates
    specifying a region 'img[ region ]',
    a tuple of three rgb values 'color',
    and a blend value 'alpha' in the range [0,1],
    returns a copy of 'img' with the given region
    tinted by 'color' such that the tint is strongest in the
    center of the region and falls off at the boundary.
    A value of 'alpha' = 1 means the region is replaced with 'color' at its center
    and a value of 'alpha' = 0 means no change anywhere.
    '''
    
    assert img.dtype == uint8
    ## NOTE: We are assuming an rgb image, though we don't actually need one.
    ##       Really all we need is to be able to do img[i,j] = color.
    assert len( img.shape ) == 3
    assert img.shape[2] == 3
    assert len( color ) == 3
    
    alpha = float( alpha )
    alpha = max( 0., min( 1., alpha ) )
    
    
    kCropAndBuffer = True
    if not kCropAndBuffer:
        r = zeros( img.shape[:2], dtype = bool )
        r[ region ] = True
    else:
        ## Only solve a crop around the region.
        ## Add a buffer pixel so I can have a proper boundary conditions around
        ## the region even if it reaches all the way to the edge of the image.
        region_mask = zeros( img.shape[:2], dtype = bool )
        region_mask[ region ] = True
        
        kBufferPixels = 1
        crop = region[0].min(), region[0].max()+1, region[1].min(), region[1].max()+1
        r = zeros( ( crop[1]-crop[0] + kBufferPixels*2, crop[3]-crop[2] + kBufferPixels*2 ), dtype = bool )
        r[ kBufferPixels:-kBufferPixels, kBufferPixels:-kBufferPixels ][ region_mask[ crop[0] : crop[1], crop[2] : crop[3] ] ] = True
    
    
    ## Solve a bilaplacian system to make a bump inside the region.
    router = grow( r, 1 )
    router[ r ] = False
    rinner = logical_not( grow( logical_not( r ), 1 ) )
    rim = r - rinner
    ## NOTE: I used to constrain 'rim' to '0' and 'router' to '-1', but that leads to a divide-by-zero
    ##       if the region is one or two pixels wide.
    value_constraints = [ (i,j,1.) for i,j in zip(*where(rim)) ] + [ (i,j,0.) for i,j in zip(*where(router)) ]
    print 'len( value_constraints ):', len( value_constraints )
    from recovery import solve_grid_linear
    bump = solve_grid_linear( r.shape[0], r.shape[1], bilaplacian = True, value_constraints = value_constraints, iterative = False )
    ## Mask the bump outside the region.
    bump[ logical_not( r ) ] = 0.
    assert bump.min() >= -1e-5
    ## Normalize the bump so the maximum value is 1.
    bump *= 1./bump.max()
    ## Guarantee it despite floating point rounding.
    bump = bump.clip(0.,1.)
    
    ## Make a copy of the input image.
    result = array( img )
    ## Blend the area specified by 'region' with 'color' according to 'alpha' and 'bump', linearly.
    bump *= alpha
    
    if not kCropAndBuffer:
        result[ region ] = ( img[ region ] + bump[ region ][ :, newaxis ] * ( color - img[ region ] ) ).round().astype( uint8 ).clip( 0, 255 )
    else:
        result_slice = result[ crop[0] : crop[1], crop[2] : crop[3] ]
        result_slice[:] = ( result_slice + bump[ kBufferPixels : -kBufferPixels, kBufferPixels : -kBufferPixels, newaxis ] * ( color - result_slice ) ).round().astype( uint8 ).clip( 0, 255 )
    
    #import Image
    #Image.fromarray( asarray( result, dtype = uint8 ) ).show()
    #debugger()
    return result
def array_tinting_region_border_with_falloff( img, regions, colors, alpha ):
    '''
    Given an image 'img', a sequence of two abutting regions 'regions' of
    coordinates specifying a region 'img[ regions[r] ]',
    a sequence of two tuples of three rgb values 'colors',
    and minimum and maximum blend values 'min_alpha' and 'max_alpha'
    in the range [0,1],
    returns a copy of 'img' with the regions tinted such that they each are
    tinted by their corresponding colors most strongly at their shared border
    and gradually falling off to the untinted value at the rest of their boundaries;
    the value 'alpha' = 1 means the tint is opaque at the shared border,
    while a value of 'alpha' = 0 means no tinting occurs.
    '''
    
    #debugger()
    
    assert img.dtype == uint8
    ## NOTE: We are assuming an rgb image, though we don't actually need one.
    ##       Really all we need is to be able to do img[i,j] = color.
    assert len( img.shape ) == 3
    assert img.shape[2] == 3
    assert len( set( [ id(r) for r in regions ] ) ) == len( regions )
    assert False not in [ len( color ) == 3 for color in colors ]
    
    alpha = float( alpha )
    alpha = max( 0., min( 1., alpha ) )
    
    
    ## 1 Grow each region by one pixel.
    ## 2 For each region, solve a laplace equation where values
    ##   are zero outside the region and one at points that are both
    ##   inside the region and inside one or more other grown regions.
    ## 3 Blend according to the solution to the laplace equation.
    
    ## Make a copy.
    result = array( img )
    
    ## 1
    masks = []
    for region in regions:
        mask = zeros( img.shape[:2], dtype = bool )
        mask[ region ] = True
        masks.append( mask )
    
    grown_masks = [ grow( mask, 1 ) for mask in masks ]
    
    ## 2
    kBufferPixels = 2
    for region, mask, grown_mask, color in zip( regions, masks, grown_masks, colors ):
        ## This is the smaller region inside the image that we are looking at.
        crop = region[0].min(), region[0].max()+1, region[1].min(), region[1].max()+1
        ## This is the mask pasted onto a canvas of zeros with a two-pixel border.
        crop_mask = zeros( ( crop[1]-crop[0] + kBufferPixels*2, crop[3]-crop[2] + kBufferPixels*2 ), dtype = bool )
        crop_mask[ kBufferPixels:-kBufferPixels, kBufferPixels:-kBufferPixels ][ mask[ crop[0] : crop[1], crop[2] : crop[3] ] ] = True
        assert len( where( crop_mask ) ) == len( where( mask ) )
        
        ## 'grown_mask - mask' gives us just the one-ring around make, but we need the two ring.
        ## For now, just constrain everything outside the region.
        #zero_constraints = set([ (i,j,0) for i,j in zip( *where( grown_mask - mask ) ) ])
        #zero_constraints = set([ (i,j) for i,j in zip( *where( logical_not( mask ) ) ) ])
        zero_constraints = set([ (i,j) for i,j in zip( *where( logical_not( crop_mask ) ) ) ])
        
        one_constraints = set()
        for other, other_mask, other_grown_mask in zip( regions, masks, grown_masks ):
            if other is region: continue
            ## This may create duplicate entries in one_constraints, so we'll make it a set.
            ## Q: Should I use 'mask' or 'grown'mask to logical_and() with 'other_grown_mask'?
            ## A1: Use 'mask', so I get only pixels inside 'region'.
            ## A2: Use 'grown_mask', so I get a border of pixels with
            ##     thickness 2, which is important for the boundary
            ##     constraints in the laplacian system.
            ## A2 UPDATE: There will now be a conflict between some of the
            ##            zero_constraints and the one_constraints that come
            ##            from the grown part of grown_mask.
            one_constraints.update( [ (i-crop[0]+kBufferPixels,j-crop[2]+kBufferPixels) for i,j in zip( *where( logical_and( grown_mask, other_grown_mask ) ) ) ] )
        
        ## Because we're using grown_mask for one_constraints, there
        ## will be conflict between one_constraints and zero_constraints.
        #assert len( one_constraints.intersection( zero_constraints ) ) == 0
        assert len( one_constraints.intersection( zero_constraints ) ) > 0
        ## Remove the conflicting elements from 'zero_constraints'.
        zero_constraints = zero_constraints.difference( one_constraints )
        
        ## Solve the laplace equation.
        ## TODO: Shrink down to a bounding box around 'region' to avoid
        ##       solving a *much* larger system than necessary.
        ##       Something like: bounds = min( region[0] ), max( region[0] ), min( region[1] ), max( region[1] )
        ##       and then taking a slice of 'bounds' after expanding it on
        ##       either side by 2, putting that in a smaller array,
        ##       solving there, then using that to modify a subregion of 'result'.
        from recovery import solve_grid_linear
        ## Q: Laplacian or Bilaplacian?
        ## A: Laplacian.  They both produce similar results but bilaplacian is slower (*much* slower with an iterative method, which we're using).
        blend = solve_grid_linear( crop_mask.shape[0], crop_mask.shape[1], value_constraints = [ (i,j,0.) for i,j in zero_constraints ] + [ (i,j,1.) for i,j in one_constraints ], bilaplacian = False )
        ## Clip blend to the area within 'region'.
        blend[ logical_not( crop_mask ) ] = 0.
        blend *= alpha
        
        ## 3
        #debugger()
        result_slice = result[ crop[0] : crop[1], crop[2] : crop[3] ]
        result_slice[:] = ( result_slice + blend[ kBufferPixels : -kBufferPixels, kBufferPixels : -kBufferPixels, newaxis ] * ( color - result_slice ) ).round().astype( uint8 ).clip( 0, 255 )
    
    return result
def main():
    import surface_with_edges_helpers as helpers
    
    ### Globals ###
    #centroids2normals_path = 'HITs3-normal_map_viz.centroids2normals'
    centroids2normals_path = 'HITs3-siga-normal_map_viz.centroids2normals'
    
    image_path = 'buddhist_column_at_sarnath-small.png'
    #mask_path = 'buddhist_column_at_sarnath-small-mask.png'
    mask_path = None
    #cut_edges_mask_path = 'buddhist_column_at_sarnath-small-patches-color2-flat-edges-thin-culled-red_disconnected-green_sharp.png'
    #cut_edges_mask_path = 'buddhist_column_at_sarnath-small-patches-color2-flat-edges-thin-culled-green_sharp.png'
    cut_edges_mask_path = 'buddhist_column_at_sarnath-small-patches-color2-flat-edges-thin-culled-red_disconnected.png'
    
    output_image_path = 'surface_with_edges'
    output_image_path_surface = helpers.unique( output_image_path + '-surface' + '.png' )
    output_npy_path_surface = helpers.unique( output_image_path + '-surface' + '.npy' )
    output_obj_path_surface = helpers.unique( output_image_path + '-surface' + '.obj' )
    
    ## ============= ## ============= ## ============= ## ============= ##
    
    ### Load files ###
    
    rows, cols = helpers.friendly_Image_open_asarray( image_path ).shape[:2]
    
    dx = []
    dy = []
    cut_edges = []
    value_constraints = [(0,0,0)]
    
    mask = None
    if mask_path is not None:
        mask = helpers.friendly_Image_open_asarray( mask_path ).astype( bool )
        assert len( mask.shape ) in (2,3)
        if len( mask.shape ) == 3:
            mask = mask.any( axis = 2 )
        
        cut_edges.extend( recovery.cut_edges_from_mask( mask ) )
        
        ## We need to make sure there is a value constraint for each connected component.
        if len( cut_edges ) > 0:
            from segm2unique import segm2unique
            from segmentation_utils import segmentation2regions
            greymask = asarray( mask, dtype = uint8 )
            connected_components = segm2unique( greymask )
            ## regions are in numpy.where() format.
            regions = segmentation2regions( connected_components )
            value_constraints = [ ( r[0][0], r[1][0], 0. ) for r in regions ]
    
    ignore_mask = zeros( ( rows, cols ), dtype = bool )
    if cut_edges_mask_path is not None:
        cut_edges_mask = friendly_Image_open_asarray( cut_edges_mask_path )
        disconnected_mask = ( cut_edges_mask == ( 255, 0, 0 ) ).all( axis = 2 )
        discontinuous_mask = ( cut_edges_mask == ( 0, 255, 0 ) ).all( axis = 2 )
        
        ## Don't add normals within 10 pixels of these constraints.
        kProtectRadius = 10
        from highlighting import grow
        ignore_mask = logical_or( ignore_mask, grow( disconnected_mask, kProtectRadius ) )
        ignore_mask = logical_or( ignore_mask, grow( discontinuous_mask, kProtectRadius ) )
        
        ## TODO: disconnected_mask could induce more disconnected components,
        ##       but I have no way of checking for or dealing with it.
        cut_edges.extend( edge_mask2edges( disconnected_mask ) )
        
        discontinuous_cut_edges = edge_mask2edges( discontinuous_mask )
        dx_disc, dy_disc = recovery.zero_dx_dy_constraints_from_cut_edges( discontinuous_cut_edges )
        
        cut_edges.extend( discontinuous_cut_edges )
        
        '''
        ## Overwrite values in dx, dy if there are duplicates.
        dx2val = dict([ ( ( row, col ), val ) for row, col, val in dx ])
        dy2val = dict([ ( ( row, col ), val ) for row, col, val in dy ])
        dx2val.update( [ ( ( row, col ), val ) for row, col, val in dx_disc ] )
        dy2val.update( [ ( ( row, col ), val ) for row, col, val in dy_disc ] )
        dx = [ ( row, col, val ) for ( row, col ), val in dx2val.iteritems() ]
        dy = [ ( row, col, val ) for ( row, col ), val in dy2val.iteritems() ]
        '''
        dx.extend( dx_disc )
        dy.extend( dy_disc )
        #'''
    
    
    from unique2centroids import lines2centroids2normals
    row_col2normal = lines2centroids2normals( open( centroids2normals_path ) )
    ## Transpose normal from right, down to row, col.
    row_col2normal = dict([ ( row_col, ( normal[1], normal[0], normal[2] ) ) for row_col, normal in row_col2normal.iteritems() ])
    
    #normal_constraints = [ ( row, col, normal ) for ( row, col ), normal in row_col2normal.iteritems() ]
    normal_constraints = [
        ( row, col, normal )
        for ( row, col ), normal in row_col2normal.iteritems()
        if not ignore_mask[ row, col ]
        ]
    
    ndx, ndy = recovery.convert_grid_normal_constraints_to_dx_and_dy_constraints( rows, cols, normal_constraints )
    
    ## Don't overwrite values in dx, dy if there are duplicates.
    #dx2val = dict([ ( ( row, col ), val ) for row, col, val in dx ])
    #dy2val = dict([ ( ( row, col ), val ) for row, col, val in dy ])
    #dx2val.update( [ ( ( row, col ), val ) for row, col, val in dx_disc ] )
    #dy2val.update( [ ( ( row, col ), val ) for row, col, val in dy_disc ] )
    #dx = [ ( row, col, val ) for ( row, col ), val in dx2val.iteritems() ]
    #dy = [ ( row, col, val ) for ( row, col ), val in dy2val.iteritems() ]
    ## UPDATE: There shouldn't be duplicates because of 'ignore_mask'
    #dx.extend( ndx )
    #dy.extend( ndy )
    ## What if we use the edges only to clear normals near edges?
    cut_edges = []
    dx = ndx
    dy = ndy
    
    
    nmap = recovery.solve_grid_linear( rows, cols, dx_constraints = dx, dy_constraints = dy, value_constraints = value_constraints, bilaplacian = True, cut_edges = cut_edges )
    ## scale by 2.
    nmap *= 2.
    
    ## Make sure we get the same thing without looking at 'cut_edges_mask_path'.
    ## We do.
    #from normal_map_viz import generate_surface_from_mask_and_normals
    #nmap2 = generate_surface_from_mask_and_normals( mask, row_col2normal )
    #debugger()
    
    ## I want to be very explicit and call numpy.save(),
    ## in case I imported something else with the name 'save'.
    import numpy
    numpy.save( output_npy_path_surface, nmap )
    print '[Saved surface depth map as a numpy array to "%s".]' % (output_npy_path_surface,)
    
    nmap_arr = recovery.normalize_to_char_img( nmap )
    nmap_img = Image.fromarray( nmap_arr )
    nmap_img.save( output_image_path_surface )
    print '[Saved surface depth map as an image to "%s".]' % (output_image_path_surface,)
    nmap_img.show()
    
    import heightmesh
    heightmesh.save_grid_as_OBJ( nmap, output_obj_path_surface, mask = mask )
    print '../GLUTViewer "%s" "%s"' % ( output_obj_path_surface, image_path )
    
    #debugger()
    pass