def get_tiltplanes(self, sequence):
        '''
        Extract tilting planes basing on distance map
        '''
        tilting_planes = []
        distance_map = []

        for i in range(1, len(sequence)):
            distance_map.append([ sequence[i], self.virtual_atoms.get_distance( sequence[0], sequence[i] ) ])

        distance_map = sorted(distance_map, key=lambda x: x[1])

        if len(distance_map) == 4:
            # surface edge case
            # semi-octahedron at surface edge has only one tilting plane to consider
            sorted_dist = [i[0] for i in distance_map]
            if distance_map[-1][1] - distance_map[-2][1] < 0.5:
                # 1st case: max diff < 0.5 Angstrom,
                # meaning all distances to reference atom are similar,
                # therefore the reference atom is above the searched plane
                # and the searched plane consists of other atoms
                tilting_planes.append( [ i[0] for i in distance_map ] )
            else:
                # 2nd case: reference atom belongs to the searched plane,
                # procedure needs to be repeated with the next atom as reference atom
                candidates = [sequence[0], sorted_dist[-1]]
                next_distance_map = []
                next_distance_map.append([ sorted_dist[1], self.virtual_atoms.get_distance( sorted_dist[0], sorted_dist[1] ) ])
                next_distance_map.append([ sorted_dist[2], self.virtual_atoms.get_distance( sorted_dist[0], sorted_dist[2] ) ])
                next_distance_map = sorted(next_distance_map, key=lambda x: x[1])
                next_sorted_dist = [i[0] for i in next_distance_map]

                # the next reference atom is taken above the plane (distances are similar)
                if next_distance_map[1][1] - next_distance_map[0][1] < 0.5: candidates.extend([ next_sorted_dist[0], next_sorted_dist[1] ])

                # the next reference atom is taken in the plane (distances are different)
                else: candidates.extend([ sorted_dist[0], next_sorted_dist[1] ])
                tilting_planes.append(candidates)

        elif len(distance_map) == 5:
            # full octahedron case
            # full octahedron has 3 different tilting planes (perpendicular in ideal case)
            sorted_dist = [i[0] for i in distance_map]

            # 1st plane is found as:
            first_plane = sorted_dist[0:4]
            tilting_planes.append(first_plane)
            distance_map_first_plane = []
            for i in range(1, 4):
                distance_map_first_plane.append([ first_plane[i], self.virtual_atoms.get_distance( first_plane[0], first_plane[i] ) ])
            distance_map_first_plane = sorted(distance_map_first_plane, key=lambda x: x[1])
            sorted_first_plane = [i[0] for i in distance_map_first_plane]

            # 2nd and 3rd planes are found as:
            tilting_planes.append([ sequence[0], sorted_dist[4], first_plane[0], sorted_first_plane[2] ])
            tilting_planes.append([ sequence[0], sorted_dist[4], sorted_first_plane[0], sorted_first_plane[1] ])

        # filter planes by Z according to octahedral spatial compound
        filtered = list(filter(lambda x:
            abs(self.virtual_atoms[ x[0] ].z - self.virtual_atoms[ x[1] ].z) < self.OCTAHEDRON_ATOMS_Z_DIFFERENCE and \
            abs(self.virtual_atoms[ x[1] ].z - self.virtual_atoms[ x[2] ].z) < self.OCTAHEDRON_ATOMS_Z_DIFFERENCE and \
            abs(self.virtual_atoms[ x[2] ].z - self.virtual_atoms[ x[3] ].z) < self.OCTAHEDRON_ATOMS_Z_DIFFERENCE,
            tilting_planes
        )) # Py3
        if len(filtered): tilting_planes = filtered

        return tilting_planes
Extract tilting planes basing on distance map