1

I'm trying to implement a filter with Python to sort out the points on a point cloud generated by Agisoft PhotoScan. PhotoScan is a photogrammetry software developed to be user friendly but also allows to use Python commands through an API.

Bellow is my code so far and I'm pretty sure there is better way to write it as I'm missing something. The code runs inside PhotoScan.

Objective:

Selecting and removing 10% of points at a time with error within defined range of 50 to 10. Also removing any points within error range less than 10% of the total, when the initial steps of selecting and removing 10% at a time are done. Immediately after every point removal an optimization procedure should be done. It should stop when no points are selectable or when selectable points counts as less than 1% of the present total points and it is not worth removing them.


Draw it for better understanding:

Drawing the logic...

Actual Code Under Construction (3 updates - see bellow for details):

import PhotoScan as PS
import math

doc = PS.app.document
chunk = doc.chunk


# using float with range and that by setting i = 1 it steps 0.1 at a time
def precrange(a, b, i):
    if a < b:
        p = 10**i
        sr = a*p
        er = (b*p) + 1
        p = float(p)
        for n in range(sr, er):
            x = n/p
            yield x
    else:
        p = 10**i
        sr = b*p
        er = (a*p) + 1
        p = float(p)
        for n in range(sr, er):
            x = n/p
            yield x


"""
Determine if x is close to y:
    x relates to nselected variable
    y to p10 variable

math.isclose() Return True if the values a and b are close to each other and
False otherwise

var is the tolerance here setted as a relative tolerance:
rel_tol is the relative tolerance – it is the maximum allowed difference
between a and b, relative to the larger absolute value of a or b. For example,
to set a tolerance of 5%, pass rel_tol=0.05. The default tolerance is 1e-09,
which assures that the two values are the same within about 9 decimal digits.
rel_tol must be greater than zero.

"""


def test_isclose(x, y, var):
    if math.isclose(x, y, rel_tol=var):  # if variables are close return True
        return True
    else:
        False


# 1. define filter limits
f_ReconstUncert = precrange(50, 10, 1)

# 2. count initial point number
tiePoints_0 = len(chunk.point_cloud.points)  # storing info for later

# 3. call Filter() and init it
f = PS.PointCloud.Filter()
f.init(chunk, criterion=PS.PointCloud.Filter.ReconstructionUncertainty)

a = 0
"""
Way to restart for loop!

should_restart = True
while should_restart:
  should_restart = False
  for i in xrange(10):
    print i
    if i == 5:
      should_restart = True
      break

"""
restartLoop = True
while restartLoop:
    restartLoop = False

    for count, i in enumerate(f_ReconstUncert):  # for each threshold value
        # count points for every i
        tiePoints = len(chunk.point_cloud.points)
        p10 = int(round((10 / 100) * tiePoints, 0))  # 10% of the total

        f.selectPoints(i)  # selects points

        nselected = len([p for p in chunk.point_cloud.points if p.selected])
        percent = round(nselected * 100 / tiePoints, 2)

        if nselected == 0:
            print("For threshold {} there´s no selectable points".format(i))
            break
        elif test_isclose(nselected, p10, 0.1):
            a += 1
            print("Threshold found in iteration: ", count)

            print("----------------------------------------------")
            print("# {}      Removing points from cloud          ".format(a))
            print("----------------------------------------------")
            print("# {}. Reconstruction Uncerntainty:"
                  " {:.2f}".format(a, i))
            print("{} - {}"
                  " ({:.1f} %)\n".format(tiePoints,
                                         nselected, percent))

            f.removePoints(i)  # removes points

            # optimization procedure needed to refine cameras positions
            print("--------------Optimizing cameras-------------\n")
            chunk.optimizeCameras(fit_f=True, fit_cx=True,
                                  fit_cy=True, fit_b1=False,
                                  fit_b2=False, fit_k1=True,
                                  fit_k2=True, fit_k3=True,
                                  fit_k4=False, fit_p1=True,
                                  fit_p2=True, fit_p3=False,
                                  fit_p4=False, adaptive_fitting=False)

            # count again number of points in point cloud
            tiePoints = len(chunk.point_cloud.points)
            print("= {} remaining points after"
                  " {} removal".format(tiePoints, a))
            # reassigning variable to get new 10% of remaining points
            p10 = int(round((10 / 100) * tiePoints, 0))
            percent = round(nselected * 100 / tiePoints, 2)
            print("----------------------------------------------\n\n")

            # restart loop to investigate from range start
            restartLoop = True
            break

        else:
            f.resetSelection()
            continue  # continue to next i
    else:
        f.resetSelection()
        print("for loop didnt work out")


print("{} iterations done!".format(count))
tiePoints = len(chunk.point_cloud.points)
print("Tiepoints 0: ", tiePoints_0)
print("Tiepoints 1: ", tiePoints)

Problems:

A. Currently I'm stuck on an endless processing because of a loop. I know it's about my bad coding. But how do I implement my objective and get away with the infinite loops? ANSWER: Got the code less confusing and updated above.

B. How do I start over (or restart) my search for valid threshold values in the range(50, 20) after finding one of them? ANSWER: Stack Exchange: how to restart a for loop

C. How do I turn the code more pythonic?


IMPORTANT UPDATE 1: altered above

Using a better range with float solution adapted from stackoverflow: how-to-use-a-decimal-range-step-value

# using float with range and that by setting i = 1 it steps 0.1 at a time
def precrange(a, b, i):
    if a < b:
        p = 10**i
        sr = a*p
        er = (b*p) + 1
        p = float(p)
        return map(lambda x: x/p, range(sr, er))
    else:
        p = 10**i
        sr = b*p
        er = (a*p) + 1
        p = float(p)
        return map(lambda x: x/p, range(sr, er))

# some code
f_ReconstUncert = precrange(50, 20, 1)

And also using math.isclose() to determine if selected points are close to the 10% selected points instead of using a manual solution through assigning new variables. This was implemented as follows:

"""
Determine if x is close to y:
    x relates to nselected variable
    y to p10 variable

math.isclose() Return True if the values a and b are close to each other and
False otherwise

var is the tolerance here setted as a relative tolerance:
rel_tol is the relative tolerance – it is the maximum allowed difference
between a and b, relative to the larger absolute value of a or b. For example,
to set a tolerance of 5%, pass rel_tol=0.05. The default tolerance is 1e-09,
which assures that the two values are the same within about 9 decimal digits.
rel_tol must be greater than zero.

"""


def test_threshold(x, y, var):
    if math.isclose(x, y, rel_tol=var):  # if variables are close return True
        return True
    else:
        False

# some code

if test_threshold(nselected, p10, 0.1):
    # if true then a valid threshold is found
    # some code

UPDATE 2: altered on code under construction

Minor fixes and got to restart de for loop from beginning by following guidance from another Stack Exchange post on the subject. Have to improve the range now or alter the isclose() to get more values.

restartLoop = True
while restartLoop:
    restartLoop = False
    for i in range(0, 10):
        if condition:
            restartLoop = True
            break

UPDATE 3: Code structure to achieve listed objectives:

threshold = range(0, 11, 1)
listx = []
for i in threshold:
    listx.append(i)

restart = 0
restartLoop = True
while restartLoop:
    restartLoop = False
    for idx, i in enumerate(listx):
        print("do something as printing i:", i)

        if i > 5:  # if this condition restart loop
            print("found value for condition: ", i)
            del listx[idx]
            restartLoop = True
            print("RESTARTING LOOP\n")
            restart += 1
            break  # break inner while and restart for loop
        else:
            # continue if the inner loop wasn't broken
            continue
    else:
        continue

print("restart - outer while", restart)

0 Answers0