Select to view content in your preferred language

Older Toolbox Python Script Error (Tool Failed to Open, Tabs/Spaces Syntax)

623
2
03-01-2024 02:28 PM
SimonBV
New Contributor

Hello - I have been using the lovely Terrain Tools for a spat of recent topographic maps with great success, however I ran into some issues regarding the "Cluster Hillshade" function and can't figure out why.

SimonBV_0-1709326857929.png

No specific error code, but opening up the details shows this:

 

'C:\Users\accov\Documents\ArcGIS\Projects\WatershedsCAD\Terrain Mapping.tbx\ClusterHillshade'

isLicensed Syntax Error: File "C:\Users\accov\Documents\ArcGIS\Projects\WatershedsCAD\Terrain Mapping.tbx#ClusterHillshade.InitializeParameters.py", line 21
if inDEM:
TabError: inconsistent use of tabs and spaces in indentation

 

One thing I noticed is there was no alias for the toolbox, and the name had a space. So I created an alias called "TerrainMapping" to see if that changed anything. No such luck. Another thing is the "isLicensed Syntax Error" but I am logged into my organization with a spatial analyst license (among many others).

So I went into VSCode and looked for anything strikingly obvious, and everything looks decent. There were no errors, and the indents were consistent from my general python knowledge:

SimonBV_1-1709326987465.png

And here is the code sample in its entirety (it's not overly long).

# ---------------------------------------------------------------------------
# ClusterHillshade.py
#
# Based on a technique developed by:
# Fabio Veronesi and Lorenz Hurni (2014) and used with permission.
# Programmed by Linda Beale, Esri Inc
#
# Description:
# Further details are published in their paper:
# Veronesi, F. and Hurni, L. (2014) Changing the light azimuth in shaded relief
# representation by clustering aspect, The Cartographic Journal, 51, 4 pp291-300
# ---------------------------------------------------------------------------

import math
import arcpy
from arcpy import env
from arcpy.sa import *

def clusterHillshade(inDEM, outRaster, zFactor, majFW, meanFW, minL, maxL):
   """
    clusterHillshade: calculates a hillshade raster from a DEM

    Required arguments:
        Inputs:
            inDEM -- Input DEM.
            zFactor -- No. of ground x,y units in one surface z unit.
            majFW -- majority filter smoothing.
            meanFW -- mean filter smoothing.
            minL -- maximum light direction.
            maxL -- minimum light direction.
        Outputs:
            outRaster -- cluster shaded relief.
     """
    try:
        # Check out the Spatial Analyst license
        arcpy.CheckOutExtension("Spatial")

        # Inputs
        zenAngle = 45

        # Degrees to radians calculation
        deg2rad = math.pi / 180

        # Process: Aspect and slope
        AspectDeg = Aspect(inDEM)
        AspectClass = IsoClusterUnsupervisedClassification(AspectDeg, 4, 20, 10)
        # 9.01248403045503E-06
        SlopeDeg = Slope(inDEM, "DEGREE", zFactor)
        SlopeRad = SlopeDeg * deg2rad

        # Process: Zenith and smoothing
        Zenith = Con(SlopeDeg >= 0, (90 - int(zenAngle)) * deg2rad)
        FocalAspect = FocalStatistics(AspectClass, majFW, "MAJORITY")
        MeanCluster = FocalStatistics(FocalAspect, meanFW, "MEAN")

        # Process: Azimuth Calculator
        Azimuth = ((float(maxL) - float(minL)) / 2) * Sin(math.pi * MeanCluster + 1.570796) + (float(maxL) - ((float(maxL) - float(minL)) / 2))

        # Process: Calculate shaded relief
        Shade = ((Cos(Float(Zenith)) * Cos(Float(SlopeRad))) + (Sin(Float(Zenith)) * Sin(Float(SlopeRad)) * Cos((Float(Azimuth) * deg2rad) - Float(AspectDeg * deg2rad))))
        ShadeRelief = Con(Float(Shade) >= 0, 255 * Float(Shade), 0)

        # Process: Save outputs
        ShadeRelief.save(outRaster)
        arcpy.SetParameterAsText(1, outRaster)

    except arcpy.ExecuteError:
        arcpy.AddMessage(arcpy.GetMessages(2))

# End main function

if __name__ == '__main__':
    args = tuple(arcpy.GetParameterAsText(i) for i in range(arcpy.GetArgumentCount()))
    clusterHillshade(*args)

 

So at the bottom theres the *args that is looking for parameters from clusterHillshade, and I suspect something is going on with the inDEM parameter but that's as far as I can take it. The main hangup appears to be something where "inDEM" is not accessed whereas "inDEM" isn't greyed out for a different script.

SimonBV_2-1709331996897.pngSimonBV_3-1709332032962.png

 

if __name__ == '__main__':
    args = tuple(arcpy.GetParameterAsText(i) for i in range(arcpy.GetArgumentCount()))
    clusterHillshade(*args)
def clusterHillshade(inDEM, outRaster, zFactor, majFW, meanFW, minL, maxL):
   """
    clusterHillshade: calculates a hillshade raster from a DEM

    Required arguments:
        Inputs:
            inDEM -- Input DEM.
            zFactor -- No. of ground x,y units in one surface z unit.
            majFW -- majority filter smoothing.
            meanFW -- mean filter smoothing.
            minL -- maximum light direction.
            maxL -- minimum light direction.
        Outputs:
            outRaster -- cluster shaded relief.
     """

 

 

If anyone has any insight that would be wonderful! Hopefully it isn't just outdated and incompatibly with ArcGIS Pro 3.2.2

 

Thanks in advance!

Tags (1)
0 Kudos
2 Replies
AlfredBaldenweck
MVP Regular Contributor

The error is pretty straightforward, it looks like. (And I get bitten by it all the time in the Python Window).

Python relies on indentation to determine loops, etc.

You can use either tabs or spaces. A tab frequently looks like the same space as 4 or 8 spaces depending on the program you're using, but is considered to be an entirely different length.

AlfredBaldenweck_0-1709333458123.png

To fix it, open up the script in a text editor (I like Notepad++) and make sure that you can view whitespace chars. Find and replace all the tabs with spaces.

As an aside, many text editors will just turn tab key presses into spaces to avoid this problem. That, or if you have a tab, tabbing the line will turn it into spaces.

0 Kudos
SimonBV
New Contributor

I have an update, while poking around the validate tab on the script properties, I noticed some errors with regards to the spacing and indents:

SimonBV_0-1709333209063.png

 

import arcpy
class ToolValidator(object):
  """Class for validating a tool's parameter values and controlling
  the behavior of the tool's dialog."""

  def __init__(self):
    """Setup arcpy and the list of tool parameters."""
    self.params = arcpy.GetParameterInfo()

  def initializeParameters(self):
    """Refine the properties of a tool's parameters.  This method is
    called when the tool is opened."""
    return

  def updateParameters(self):
    """Modify the values and properties of parameters before internal
    validation is performed.  This method is called whenever a parameter
    has been changed."""
    inDEM = self.params[0].value
    if self.params[2].value:
	if inDEM: 
            if arcpy.Describe(inDEM).spatialReference.type == "Geographic":
	        self.params[2].value = 9.01248403045503E-06
	    if arcpy.Describe(inDEM).spatialReference.type == "Projected":
		self.params[2].value = 1
    return

  def updateMessages(self):
    """Modify the messages created by internal validation for each tool
    parameter.  This method is called after internal validation."""
    inFC = self.params[0].value
    self.params[0].clearMessage()
    if self.params[0].value is None:
        self.params[0].clearMessage()
    else:
        if inFC:
            desc = arcpy.Describe(inFC) 
            sr = desc.spatialReference
            if sr.type == "Geographic":
                self.params[0].setErrorMessage("Data needs to be in a Projected Coordinate System")
            else:
                self.params[0].clearMessage()
    return

 

So the error lies within the validation. It seems that there is an included validation step for "inDEM" to ensure it is in a projected coordinate system, and to convert it to a projection. All I ended up doing was replacing the the updateParameters so that it showed this:

  def updateParameters(self):
    """Modify the values and properties of parameters before internal
    validation is performed.  This method is called whenever a parameter
    has been changed."""
    return

Instead of this:

  def updateParameters(self):
    """Modify the values and properties of parameters before internal
    validation is performed.  This method is called whenever a parameter
    has been changed."""
    inDEM = self.params[0].value
    if self.params[2].value:
        if inDEM: 
            if arcpy.Describe(inDEM).spatialReference.type == "Geographic":
	        self.params[2].value = 9.01248403045503E-06
	    if arcpy.Describe(inDEM).spatialReference.type == "Projected":
		self.params[2].value = 1
    return

 Seems to work now, hope this helps people in a similar boat!

0 Kudos