1
\$\begingroup\$

I'm fairly new to coding and am developing a set of functions to allow mammologists to progrmatically collect data from photos of specimens. The special issue with mammals is that their coats are often textured in a way that obscures their pattern to a computer, but is usually ignored by an organismal observer.

I have an image array, pix, where all pixels of similar color have been averaged and set to that average value. This removes a lot of the extraneous detail from a natural photo, but leaves artifacts that are a result of this texture. To remove these artifacts this function uses a 2D array, labeledPix of the same height and width as the image, which labels each unique and non-contiguous feature of the image.

The labeledPix parameter is returned by assigning a unique integer value to each contiguous region of the image with a common pixel value. This is accomplished with the following function:

def labelPix(pix):
    height, width, _ = pix.shape
    pixRows = numpy.reshape(pix, (height * width, 3))
    unique, counts = numpy.unique(pixRows, return_counts = True, axis = 0)

    unique = [list(elem) for elem in unique]
    del(unique[unique.index(list(pix[0,0]))])

    labeledPix = numpy.zeros((height, width), dtype = int)
    sizes = numpy.zeros((height,width), dtype = int)
    offset = 0
    for index, zoneArray in enumerate(unique):
        index += offset
        zone = list(zoneArray)
        zoneArea = (pix == zone).all(-1)
        elementsArray, numElements = scipy.ndimage.label(zoneArea)

        elementsArray[elementsArray!=0] += offset

        labeledPix[elementsArray!=0] = elementsArray[elementsArray!=0]

        offset += numElements

    return labeledPix 

Using these I need to assess the size of each feature and its level of contrast to each of its neighbors. These values are used in an equation and if the output of that equation is below a certain threshold, that feature is merged with its most similar neighbor.

I've uploaded the image and the feature label array (printed with numpy.savetext()) to GoogleDrive and attached links

code:

def textureRemover(pix, labeledPix, ratio = 1.0):
    numElements = numpy.amax(labeledPix)
    maxSize = numpy.count_nonzero(labeledPix)
    MAXIMUMCONTRAST = 443.405

    for regionID in range(numElements):        
        start = time.clock()
        regionID += 1
        if regionID not in labeledPix:
            continue

        #print(regionID)
        #print((regionID / numElements) * 100, '%')

        neighborIDs = getNeighbors(labeledPix, regionID)
        if 0 in neighborIDs:
            neighborIDs.remove(0) #remove white value
        regionMask = labeledPix == regionID

        region = pix[regionMask]

        size = numpy.count_nonzero(regionMask)
        contrastMin = (ratio - (size / maxSize)) * MAXIMUMCONTRAST 
        regionMean = region.mean(axis = 0)

        if len(neighborIDs) > 200:
            contrast = numpy.zeros(labeledPix.shape)
            contrast[labeledPix!=0] = numpy.sqrt(numpy.sum((regionMean - pix[labeledPix!=0])**2, axis = -1))

            significantMask = (contrast < contrastMin)
            significantContrasts = list(numpy.unique(contrast[significantMask]))

            significantNeighbors = {}
            for significantContrast in significantContrasts:
                minContrast = min(significantContrasts)
                if labeledPix[contrast == minContrast][0] in neighborIDs:
                    significantNeighbors[minContrast] = labeledPix[contrast == minContrast][0]
                else:
                    significantContrasts.pop(significantContrasts.index(minContrast))

        else:
            significantNeighbors = {}
            for neighborID in neighborIDs:
                neighborMask = labeledPix == neighborID
                neighbor = pix[neighborMask]
                neighborMean = neighbor.mean(axis = 0)
                contrast = numpy.sqrt(numpy.sum((regionMean - neighborMean)**2, axis = -1))
                if contrast < contrastMin:
                    significantNeighbors[contrast] = neighborID

        if significantNeighbors:
            contrasts = significantNeighbors.keys()            
            minContrast = min(contrasts)

            minNeighbor = significantNeighbors[minContrast]
            neighborMask = labeledPix == minNeighbor
            neighborSize = numpy.count_nonzero(neighborMask)

            if neighborSize <= size:
                labeledPix[neighborMask] = regionID
                pix[neighborMask] = regionMean

            else:
                labeledPix[regionMask] = minNeighbor
                pix[regionMask] = pix[neighborMask].mean(axis = 0)

        print(time.clock() - start)
    return pix

Mammal image used for testing (click for uncompressed image)
labeledPix parameter

This code runs, but in order for it to be feasible for my purposes, it needs to be sped up 200-300x. I've been stuck on this for a few weeks and would greatly appreciate any assistance in optimizing this code.

\$\endgroup\$
  • \$\begingroup\$ The images are behind a login; can you upload them to a free site or directly put them into the question? \$\endgroup\$ – Dannnno Jan 3 '18 at 22:13
  • \$\begingroup\$ Thanks for pointing that out! I may be able to upload the image here, but if there's any compression that'll break the program. Do you have a recommendation for a free site? (I picked OneDrive based on a Stack Overflow Meta post) \$\endgroup\$ – asheets Jan 3 '18 at 22:17
  • \$\begingroup\$ I honestly have no idea what sites would or won't compress the image. OneDrive is probably fine, if you can give it to us without a password protecting it. I know that GDrive can give access to anyone with a link, but I'm not sure if it would do any compression. \$\endgroup\$ – Dannnno Jan 3 '18 at 22:22
  • \$\begingroup\$ I changed the hosting to GDrive. It doesn't look like they do any compression by default \$\endgroup\$ – asheets Jan 3 '18 at 22:32
  • \$\begingroup\$ I've taken the liberty of embedding the image and fixing the links to use the normal Markdown formatting. I've made sure that clicking the link gives you the original GDrive link in case Imgur compresses them. \$\endgroup\$ – Dannnno Jan 3 '18 at 22:36

Your Answer

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

Browse other questions tagged or ask your own question.