Skip to content

Commit

Permalink
style(tracingfuncs): Numpydoc validation, PEP8 & tidying
Browse files Browse the repository at this point in the history
This commit is squash of seven individual commits that address Numpydoc validation

- style(tracingfuncs): GL01/GL02 Numpydoc validation
- style(tracingfuncs): SS06 Numpydoc validation
- style(tracingfuncs): GL08 Numpydoc validation
- style(tracingfuncs): PR01/RT01 Numpydoc validation

Removes unused functions/methods...

- fix(tracingfuncs): Removes circularTrace_old
- fix(tracingfuncs): Remove unused getLocalPixelsBinary

Renames methods to adhere to PEP8

- style(tracingfuncs): PEP8 renaming camel-case method to snake-case
  • Loading branch information
ns-rse committed Oct 14, 2024
1 parent 0ad6ed3 commit 742cc92
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 94 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ exclude = [ # don't report on objects that match any of these regex
"\\.__repr__$",
"^test_",
"^conftest",
"^tracingfuncs",
"^conf$",
"^theme",
]
Expand Down
240 changes: 147 additions & 93 deletions topostats/tracing/tracingfuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,31 @@ class reorderTrace:
"""

@staticmethod
def linearTrace(trace_coordinates):
"""My own function to order the points from a linear trace.
def linearTrace(trace_coordinates: list | npt.NDArray) -> npt.NDArray:
"""
Function to order the points from a linear trace.
This works by checking the local neighbours for a given pixel (starting
at one of the ends). If this pixel has only one neighbour in the array
of unordered points, this must be the next pixel in the trace -- and it
is added to the ordered points trace and removed from the
remaining_unordered_coords array.
This works by checking the local neighbours for a given pixel (starting at one of the ends). If this pixel has
only one neighbour in the array of unordered points, this must be the next pixel in the trace -- and it
is added to the ordered points trace and removed from the remaining_unordered_coords array.
If there is more than one neighbouring pixel, a fairly simple function
(checkVectorsCandidatePoints) finds which pixel incurs the smallest
change in angle compared with the rest of the trace and chooses that as
the next point.
If there is more than one neighbouring pixel, a fairly simple function (check_vectors_candidate_points) finds which
pixel incurs the smallest change in angle compared with the rest of the trace and chooses that as the next
point.
This process is repeated until all the points are placed in the ordered
trace array or the other end point is reached."""
This process is repeated until all the points are placed in the ordered trace array or the other end point is
reached.
Parameters
----------
trace_coordinates : list | npt.NDArray
Unordered trace coordinates.
Returns
-------
npt.NDArray
An array of ordered coordinates from one end of a linear trace to the other.
"""

try:
trace_coordinates = trace_coordinates.tolist()
Expand Down Expand Up @@ -63,14 +72,16 @@ def linearTrace(trace_coordinates):
remaining_unordered_coords.pop(remaining_unordered_coords.index(neighbour_array[0]))
continue
elif no_of_neighbours > 1:
best_next_pixel = genTracingFuncs.checkVectorsCandidatePoints(x_n, y_n, ordered_points, neighbour_array)
best_next_pixel = genTracingFuncs.check_vectors_candidate_points(
x_n, y_n, ordered_points, neighbour_array
)
ordered_points.append(best_next_pixel)
remaining_unordered_coords.pop(remaining_unordered_coords.index(best_next_pixel))
continue
elif no_of_neighbours == 0:
# nn, neighbour_array_all_coords = genTracingFuncs.count_and_get_neighbours(x_n, y_n, trace_coordinates)
# best_next_pixel = genTracingFuncs.checkVectorsCandidatePoints(x_n, y_n, ordered_points, neighbour_array_all_coords)
best_next_pixel = genTracingFuncs.findBestNextPoint(
# best_next_pixel = genTracingFuncs.check_vectors_candidate_points(x_n, y_n, ordered_points, neighbour_array_all_coords)
best_next_pixel = genTracingFuncs.find_best_next_point(
x_n, y_n, ordered_points, remaining_unordered_coords
)

Expand All @@ -87,8 +98,19 @@ def linearTrace(trace_coordinates):

@staticmethod
def circularTrace(trace_coordinates):
"""An alternative implementation of the linear tracing algorithm but
with some adaptations to work with circular dna molecules"""
"""
Alternative implementation of the linear tracing algorithm but adapted to work with circular DNA molecules.
Parameters
----------
trace_coordinates : list | npt.NDArray
Unordered trace coordinates.
Returns
-------
npt.NDArray
An array of ordered coordinates from one end of a linear trace to the other.
"""

try:
trace_coordinates = trace_coordinates.tolist()
Expand Down Expand Up @@ -130,7 +152,9 @@ def circularTrace(trace_coordinates):
continue

elif no_of_neighbours > 1:
best_next_pixel = genTracingFuncs.checkVectorsCandidatePoints(x_n, y_n, ordered_points, neighbour_array)
best_next_pixel = genTracingFuncs.check_vectors_candidate_points(
x_n, y_n, ordered_points, neighbour_array
)
ordered_points.append(best_next_pixel)
remaining_unordered_coords.pop(remaining_unordered_coords.index(best_next_pixel))
continue
Expand Down Expand Up @@ -160,8 +184,8 @@ def circularTrace(trace_coordinates):

# Maybe at a crossing with all neighbours deleted - this is crucially a point where errors often occur
else:
# best_next_pixel = genTracingFuncs.checkVectorsCandidatePoints(x_n, y_n, ordered_points, remaining_unordered_coords)
best_next_pixel = genTracingFuncs.findBestNextPoint(
# best_next_pixel = genTracingFuncs.check_vectors_candidate_points(x_n, y_n, ordered_points, remaining_unordered_coords)
best_next_pixel = genTracingFuncs.find_best_next_point(
x_n, y_n, ordered_points, remaining_unordered_coords
)

Expand All @@ -182,71 +206,29 @@ def circularTrace(trace_coordinates):
ordered_points.append(ordered_points[0])
return np.array(ordered_points), True

@staticmethod
def circularTrace_old(trace_coordinates):
"""Reorders the coordinates of a trace from a circular DNA molecule
(with no loops) using a polar coordinate system with reference to the
center of mass
I think every step of this can be vectorised for speed up
This is vulnerable to bugs if the dna molecule folds in on itself slightly"""

# calculate the centre of mass for the trace
com_x = np.average(trace_coordinates[:, 0])
com_y = np.average(trace_coordinates[:, 1])

# convert to polar coordinates with respect to the centre of mass
polar_coordinates = []
for x1, y1 in trace_coordinates:
x = x1 - com_x
y = y1 - com_y

r = math.hypot(x, y)
theta = math.atan2(x, y)

polar_coordinates.append([theta, r])

sorted_polar_coordinates = sorted(polar_coordinates, key=lambda i: i[0])

# Reconvert to x, y coordinates
sorted_coordinates = []
for theta, r in sorted_polar_coordinates:
x = r * math.sin(theta)
y = r * math.cos(theta)

x2 = x + com_x
y2 = y + com_y

sorted_coordinates.append([x2, y2])

return np.array(sorted_coordinates)

def loopedCircularTrace():
pass

def loopedLinearTrace():
pass


class genTracingFuncs:
@staticmethod
def getLocalPixelsBinary(binary_map, x, y):
p2 = binary_map[x, y + 1]
p3 = binary_map[x + 1, y + 1]
p4 = binary_map[x + 1, y]
p5 = binary_map[x + 1, y - 1]
p6 = binary_map[x, y - 1]
p7 = binary_map[x - 1, y - 1]
p8 = binary_map[x - 1, y]
p9 = binary_map[x - 1, y + 1]

return p2, p3, p4, p5, p6, p7, p8, p9
"""Class of tracing functions."""

@staticmethod
def count_and_get_neighbours(x, y, trace_coordinates) -> tuple[int, list]:
"""Returns the number of neighbouring points for a coordinate and an
array containing the those points"""
def count_and_get_neighbours(x: int, y: int, trace_coordinates: list) -> tuple[int, list]:
"""
Count the number of neighbouring points for a coordinate and an array containing those points.
Parameters
----------
x : int
X coordinate.
y : int
Y coordinate.
trace_coordinates : list
Coordinates of the trace.
Returns
-------
tuple
The number of neighbours and the coordinates of the neighbouring points.
"""

neighbour_array = []
number_of_neighbours = 0
Expand Down Expand Up @@ -277,7 +259,22 @@ def count_and_get_neighbours(x, y, trace_coordinates) -> tuple[int, list]:
return number_of_neighbours, neighbour_array

@staticmethod
def returnPointsInArray(points_array, trace_coordinates):
def return_points_in_array(points_array: list | npt.NDArray, trace_coordinates: list | npt.NDArray) -> list:
"""
Return a subset co ordinates for the given set of points.
Parameters
----------
points_array : list | npt.NDArray
The subset of points for which coordinates are required.
trace_coordinates : list | npt.NDArray
Coordinates of all points.
Returns
-------
list
Coordinates for the subset of points.
"""
for x, y in points_array:
if [x, y] in trace_coordinates:
try:
Expand All @@ -302,7 +299,24 @@ def returnPointsInArray(points_array, trace_coordinates):
return None

@staticmethod
def makeGrid(x, y, size):
def make_grid(x: int, y: int, size: int) -> list:
"""
Make a Grid of coordinates around the points x and y.
Parameters
----------
x : int
The x coordinate.
y : int
They y coordinate.
size : int
Size of surrounding grid.
Returns
-------
list
List of coordinates that form a grid around x and y of size.
"""
for x_n in range(-size, size + 1):
x_2 = x + x_n
for y_n in range(-size, size + 1):
Expand All @@ -314,7 +328,28 @@ def makeGrid(x, y, size):
return grid

@staticmethod
def findBestNextPoint(x, y, ordered_points, candidate_points):
def find_best_next_point(
x: int, y: int, ordered_points: list | npt.NDArray, candidate_points: list | npt.NDArray
) -> list | None:
"""
Find the next best point.
Parameters
----------
x : int
The x coordinate.
y : int
They y coordinate.
ordered_points : list | npt.NDArray
Ordered points.
candidate_points : list | npt.NDArray
Points to be checked.
Returns
-------
list
Coordinates of the neighbouring pixel with the smallest angular change.
"""
ordered_points = np.array(ordered_points)
candidate_points = np.array(candidate_points)

Expand All @@ -323,9 +358,9 @@ def findBestNextPoint(x, y, ordered_points, candidate_points):

for i in range(1, 8):
# build array of coordinates from which to check
coords_to_check = genTracingFuncs.makeGrid(x, y, i)
coords_to_check = genTracingFuncs.make_grid(x, y, i)
# check for potential points in the larger search area
points_in_array = genTracingFuncs.returnPointsInArray(coords_to_check, candidate_points)
points_in_array = genTracingFuncs.return_points_in_array(coords_to_check, candidate_points)

# Make a decision depending on how many points are found
if not points_in_array:
Expand All @@ -334,16 +369,35 @@ def findBestNextPoint(x, y, ordered_points, candidate_points):
best_next_point = points_in_array[0]
return best_next_point
else:
best_next_point = genTracingFuncs.checkVectorsCandidatePoints(x, y, ordered_points, points_in_array)
best_next_point = genTracingFuncs.check_vectors_candidate_points(x, y, ordered_points, points_in_array)
return best_next_point
return None

@staticmethod
def checkVectorsCandidatePoints(x, y, ordered_points, candidate_points):
"""Finds which neighbouring pixel incurs the smallest angular change
with reference to a previous pixel in the ordered trace, and chooses that
as the next point"""

def check_vectors_candidate_points(
x: int, y: int, ordered_points: list | npt.NDArray, candidate_points: list | npt.NDArray
) -> list:
"""
Find which neighbouring pixel incurs the smallest angular change.
This is done with reference to a previous pixel in the ordered trace, and chooses that as the next point.
Parameters
----------
x : int
The x coordinate.
y : int
They y coordinate.
ordered_points : list | npt.NDArray
Ordered points.
candidate_points : list | npt.NDArray
Points to be checked.
Returns
-------
list
Coordinates of the neighbouring pixel with the smallest angular change.
"""
x_test = ordered_points[-1][0]
y_test = ordered_points[-1][1]

Expand Down

0 comments on commit 742cc92

Please sign in to comment.