I have a gdb with survey data regarding urban trees. Each tree has photo attachments that correspond to the specific tree.

Below is a link to a ESRI script that will pull the photos and put them into a folder, but will not have them in any specific order, nor will they have a meaningful name. I would like to have a workflow that allows me to pull the attachments, rename them to something meaningful such as "456_344_1.jpg". This way I can easily navigate to a specific tree to confirm that it was identified correctly by the people who completed the survey. The GDB has a unique id for each tree in its attribute table.

The goal is to populate a folder with photos of all of our trees. The format for file names should be something like: treeID_segmentID_uniquephoto.jpg

ESRI help article here: http://support.esri.com/technical-article/000011912

  • 1
    Welcome to GIS SE! Please take the community tour here: gis.stackexchange.com/tour. What is your question? If you are looking for coding help you will first need to provide your best code attempt within your question and let us know where you are stuck.
    – artwork21
    Aug 11 '17 at 19:37
  • I am looking for some way to accomplish the task of iterating through the attachments and renaming them with an easy to navigate naming convention. Aug 11 '17 at 19:48

Here is the source code for a script tool (but you can also run it as is without creating a custom script tool). I've added some comments to make it easier for you to follow. The basic idea is that you can specify the field which you would like to use to name each output attachment.

If you would like to name your output attachment using multiple fields, you can easily modify the source code to include multiple fields and then modify how the lookup dictionary feature_names = {row[0]: row[1] for row in lookup} will be constructed.

import arcpy
import os

##define input parameters for the tool
#input attachments table
in_table = arcpy.GetParameterAsText(0)
#output folder to save files in
file_location = arcpy.GetParameterAsText(1)
#field to be used for naming attachments
name_field = arcpy.GetParameterAsText(2)

#getting the feature class name from the attachments table through the relationship class
#simple way to do this - however might break should the table be named in another way in
#upcoming releases - is to use `inTable.split('__')[0]`
in_table_desc = arcpy.Describe(in_table)
relclass = os.path.join(in_table_desc.path, in_table_desc.relationshipClassNames[0])
rel_class_desc = arcpy.Describe(relclass)
fc = os.path.join(rel_class_desc.path, rel_class_desc.originClassNames[0])
foreign_key_field = [i[0] for i in rel_class_desc.originClassKeys if i[1] == 'OriginForeign'][0]

##creating a dict with values to be used for naming output attachments files
globalid_field = arcpy.Describe(in_table).globalIDFieldName
with arcpy.da.SearchCursor(fc, [globalid_field, name_field]) as lookup:
    feature_names = {row[0]: row[1] for row in lookup}

##iterating attachments and saving them on disk
with arcpy.da.SearchCursor(in_table, [foreign_key_field, 'DATA', 'Content_Type']) as cursor:
    for item in cursor:
        featureid = item[0]
        attachment = item[1]
        featurename = feature_names[featureid]
        if not isinstance(featurename, unicode):
            featurename = str(featurename)
        filename = featurename + '.' + item[2].split('/')[1]

        #constructing the output file path
        filepath = os.path.join(file_location,filename)
        with open(filepath, 'wb') as f:

A single cursor should do the trick for this, and involve creating your unique name for each row. I'm assuming segment and tree ID are field values for each row. Then your final unique identifying number can be determined with a dictionary.


tab = r"C:\Path\To\Table" #Full path to table (GetParameterAsText can be used for script tools)
segFld = "SEGMENT" #Segment field (assumed to be a field in table)
treeIdFld = "TREE_ID" #Tree ID field
attachFld = "PIC_ATTACHEMENT" #Attachement field
outLoc = r"C:\Path\To\Output\Folder" #Full path to output folder 

#import modules
import arcpy
import os

#empty dictionary to store which names have been used
nameDi = {}

#cursor to iterate rows in table, using three input feilds
with arcpy.da.SearchCursor (tab, [segFld, treeIdFld, attachFld]) as curs:
    for seg, treeId, attachment in curs:
        #create the first part of the file name
        fileNameStart = "{}_{}".format (treeId, seg)
        #update or add 1 to name dictionary
        try: nameDi [fileNameStart] += 1
        except KeyError: nameDi [fileNameStart] = 1
        #get number from dictionary
        num = nameDi [fileNameStart]
        #create full name
        fileName = "{}_{}.jpg".format (fileNameStart, num)
        #create full path
        outFile = os.path.join (outLoc, fileName)
        open (outFile, 'wb').write (attachement.tobytes ())
  • I put in the appropriate field names for this. When I run the script I get the following error: Traceback (most recent call last): File "C:\Users\gjfuelling\Desktop\Berlandtest2.py", line 30, in <module> open (outFile).write (attachement.tobytes ()) IOError: [Errno 2] No such file or directory: u'C:\\Users\\gjfuelling\\Desktop\\Test\\junk\\{FA2B3D32-58E3-486F-B181-73AA1CAA34AB}_1_1.jpg' Aug 14 '17 at 16:19
  • I forgot the wb in the code for open. See update. Aug 14 '17 at 16:23
  • New error: Traceback (most recent call last): File "C:\Users\gjfuelling\Desktop\Berlandtest2.py", line 30, in <module> open (outFile, 'wb').write (attachment.tobytes ()) AttributeError: 'unicode' object has no attribute 'tobytes' Aug 14 '17 at 16:27
  • Your input field containing an attachment might not be BLOB type. Sounds like a text field. Aug 14 '17 at 17:03
  • You are right, I had input the wrong field. Thank you! Aug 14 '17 at 17:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.