Optimizing Textures for Esri 3D Software

1118
3
11-19-2018 05:46 AM
by Anonymous User
Not applicable
1 3 1,118

Most people working with 3D modeling software are not aware of how heavy textures can impact performance in the modeling process and the end-user experience.

When generating textures I always recommend creating them at a higher resolution than the final product. The reasoning is that you can always down-sample the image (Reduce number of pixels) later on but you can never scale the image back up.

For procedural modeling in Esri CityEngine and generating your libraries optimizing texture and model asset size as much as possible is key for ensuring fast performance and the ability to model massive environments.

The following script takes the difficulty out of manually scaling the images in your pipeline enabling complete automation allowing you to continue focusing on the design aspect of your project you know and love...

# -------------------------------------------------------------------------------
# Name:        ImageAutoResize.py
# Purpose:     Script for batch optimizing image for optimal performance in 3D software
#
# Author:      Geoff Taylor
#
# Created:     11/19/2018
# Copyright:   (c) Esri 2018
# updated:

# Required:
# License: Apache

# -------------------------------------------------------------------------------

import os, sys
from math import ceil
from PIL import Image, ImageStat


inFolder = r'inputFolderPathGoesHere!'
outFolder = r'outputFolderPathGoesHere!'

''' Max Width and height are only relative for a single side of the image. 
if one side of the image is larger then the smaller side will be scaled to the minimum value 
and larger scaled fractionally to the smaller side'''

minWidth = 300
minHeight = 300

fileFormats = ['.jpg'# Currently only Supports .jpg

###############
# Definitions #
###############


def update_progress(progress):
    barLength = 10  # Modify this to change the length of the progress bar
    status = ""
    if isinstance(progress, int):
        progress = float(progress)
    if not isinstance(progress, float):
        progress = 0
        status = "error: progress var must be float\r\n"
    if progress < 0:
        progress = 0
        status = "Halt...\r\n"
    if progress >= 1:
        progress = 1
        status = "Done...\r\n"
    block = int(round(barLength*progress))
    text = "\rPercent Complete: [{0}] {1}% {2}".format("#"*block + "-"*(barLength-block), progress*100, status)
    sys.stdout.write(text)
    sys.stdout.flush()


def fileExt(inFile):
    ''' [0] returns fileName, [1] returns Extension '''
    filename, file_extension = os.path.splitext(inFile)
    return filename, file_extension.lower()


def genDirectory(inPath):
    if not os.path.exists(inPath):
        os.makedirs(inPath)


def delIfFileExists(inFile,outFolder):
    fullFilePath = os.path.join(outFolder, inFile)
    if os.path.exists(fullFilePath):
        try:
            os.remove(fullFilePath)
        except:
            print("Could not remove {0}".format(fullFilePath))
    else:
        pass


def imageColorType(pilImg):
    thumb_size = 40
    MSE_cutoff = 22
    adjust_color_bias = True
    bands = pilImg.getbands()
    if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
        thumb = pilImg.resize((thumb_size, thumb_size))
        SSE, bias = 0, [0, 0, 0]
        if adjust_color_bias:
            bias = ImageStat.Stat(thumb).mean[:3]
            bias = [b - sum(bias)/3 for b in bias]
        for pixel in thumb.getdata():
            mu = sum(pixel)/3
            SSE += sum((pixel - mu - bias)*(pixel - mu - bias) for i in [0, 1, 2])
        MSE = float(SSE)/(thumb_size*thumb_size)
        if MSE <= MSE_cutoff:
            return "grayscale"
        else:
            return "color"
    elif len(bands) == 1:
        return "bw"
    else:
        return "unknown"


def imageFitsDimensions(inPilImg, minWidth, minHeight):
    width, height = inPilImg.size
    if width <= minWidth or height < minHeight:
        return True
    else:
        return False


def imageScaling(inPilImg, minWidth, minHeight):
    width, height = inPilImg.size
    if height > width:
        scaleFactor = minWidth / width
        return minWidth, ceil(scaleFactor*height)
    elif width > height:
        scaleFactor = minHeight / height
        return ceil(scaleFactor*width), minHeight
    else:
        return minWidth, minHeight


################
# Begin Script #
################

genDirectory(outFolder)

imageFiles = [f for f in os.listdir(inFolder) if
              os.path.isfile(os.path.join(inFolder, f)) and fileExt(f)[1] in fileFormats]

count = 0
for imageFile in imageFiles:
    if count > 0:
        update_progress(count/len(imageFiles))
    delIfFileExists(imageFile, outFolder)
    inFile = os.path.join(inFolder, imageFile)
    outFile = os.path.join(outFolder, imageFile)
    count += 1
    try:
        im = Image.open(inFile)
        if imageColorType(im) != "color":
            im.convert('RGB')
        if imageFitsDimensions(im, minWidth, minHeight):
            im.save(outFile, "JPEG")
        else:
            imageScaling(im, minWidth, minHeight)
            im.save(outFile, "JPEG")
            outImg = im.resize((imageScaling(im, minWidth, minHeight)[0], imageScaling(im, minWidth, minHeight)[1]),
                               Image.ANTIALIAS)
            # outImg.save(outFile, quality=95)
            outImg.save(outFile, optimize=True, quality=95)
    except IOError:
        print("cannot resample image for" % imageFile)
update_progress(100)

Just install the Python Imaging Library fork "Pillow" https://python-pillow.org/ and run the script on the folder containing your textures.

3 Comments
DanielAndia
New Contributor II

Hello taylor, I comment your publication because I have a dae file with textures that weighs 2.64 gb and when trying to import it with the tool of arcgis pro version 2.2.2 the import fails, reviewed some publications comment that it can be the dae model or the memory of the graphics card. you would have some suggestion or recommendation to save this situation it is worth mentioning that the dae file is a format supported by the arcgis pro tool.

by Anonymous User
Not applicable

Daniel,

I would recommend using CityEngine to load the data. It should be much more forgiving and will allow for down-sampling of the models when exporting to a geodatabase.

Perhaps all of the buildings are connected as one single entity and may be causing the crash?

DanielAndia
New Contributor II

hello again taylor the model that I have is from a mine, the only software that allowed me to see the model with the textures is 3ds max 2019, I showed it but some textures were missing, I tried to import the model in sketchup but I have not managed to see it with the textures for more than following the recommendations made to put the textures together with the model, I will try to import it into the CityEngine as you recommend it; I attached some images that hopefully help you to orient me, thanks for your answer.

Imported model with missing textures in the 3ds max 2019Warning message when importing the model in 3ds mx 2019