analysis.segmentation
module
Segmentation algorithm used to identify the different structures that are formed in the encounter. This file can be called from the command line to make an illustrative plot of the algorithm.
Source code
"""Segmentation algorithm used to identify the different structures
that are formed in the encounter. This file can be called from the
command line to make an illustrative plot of the algorithm.
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import utils
def segmentEncounter(data, plot=False, mode='all'):
"""Segment the encounter into tail, bridge, orbitting and
stolen particles as described in the report.
Parameters:
data: A data instance as saved by the simulation to a pickle file
plot: If true the segmentation will be plotted and shown. Highly
useful for debugging.
mode (string): If mode is 'all' all parts of the encounter will be
identified. If mode is 'bridge' only the bridge will be
identified. This is useful when there may be no tail.
Returns:
masks (tupple): tupple of array corresponding to the masks of the
(bridge, stolen, orbitting, tail) particles. One can then use
e.g. data['r_vec'][bridgeMask].
shape (tupple): tupple of (distances, angles) as measured from the
center of mass and with respect to the x axis. They define the
shape of the tail
length (float): total length of the tail.
"""
nRings = 100 # number of rings to use when segmenting the data
# Localize the central masses
r_vec = data['r_vec']
centers = r_vec[data['type'][:,0]=='center']
rCenters_vec = centers[1] - centers[0]
rCenters = np.linalg.norm(rCenters_vec)
rCenters_unit = rCenters_vec/np.linalg.norm(rCenters_vec)
# Take particles to be on the tail a priori and
# remove them as they are found in other structures
particlesLeft = np.arange(0, len(r_vec))
if plot:
colour = '#c40f4c'
f, axs = plt.subplots(1, 3, figsize=(9, 4), sharey=False)
f.subplots_adjust(hspace=0, wspace=0)
axs[0].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1)
axs[0].axis('equal')
utils.plotCenterMasses(axs[0], data)
axs[0].axis('off')
# Step 1: project points to see if they are part of the bridge
parallelProjection = np.dot(r_vec - centers[0], rCenters_unit)
perpendicularProjection = np.linalg.norm(r_vec - centers[0][np.newaxis]
- parallelProjection[:,np.newaxis] * rCenters_unit[np.newaxis], axis=-1)
bridgeMask = np.logical_and(np.logical_and(0.3*rCenters < parallelProjection,
parallelProjection < .7*rCenters), perpendicularProjection < 2)
# Remove the bridge
notInBridge = np.logical_not(bridgeMask)
r_vec = r_vec[notInBridge]
particlesLeft = particlesLeft[notInBridge]
if mode == 'bridge':
return (bridgeMask, None, None, None), None, None
# Step 2: select stolen particles by checking distance to centers
stolenMask = (np.linalg.norm(r_vec - centers[0][np.newaxis], axis=-1)
> np.linalg.norm(r_vec - centers[1][np.newaxis], axis=-1))
# Remove the stolen part
notStolen = np.logical_not(stolenMask)
r_vec = r_vec[notStolen]
particlesLeft, stolenMask = particlesLeft[notStolen], particlesLeft[stolenMask]
# Step 3: segment data into concentric rings (spherical shells really)
r_vec = r_vec - centers[0]
r = np.linalg.norm(r_vec, axis=-1)
edges = np.linspace(0, 30, nRings) # nRings concentric spheres
indices = np.digitize(r, edges) # Classify particles into shells
if plot:
axs[1].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=.1, s=.1)
axs[1].axis('equal')
axs[1].scatter(0, 0, s=100, marker="*", c='black', alpha=.7)
axs[1].axis('off')
# Step 4: find start of tail
start = None
for i in range(1, nRings+1):
rMean = np.mean(r[indices==i])
rMean_vec = np.mean(r_vec[indices==i], axis=0)
parameter = np.linalg.norm(rMean_vec)/rMean
if plot:
circ = patches.Circle((0,0), edges[i-1], linewidth=0.5,edgecolor='black',facecolor='none', alpha=.7)
axs[1].add_patch(circ)
txtxy = edges[i-1] * np.array([np.sin(i/13), np.cos(i/13)])
axs[1].annotate("{:.2f}".format(parameter), xy=txtxy, backgroundcolor='#ffffff55')
if start is None and parameter>.8 :
start = i #Here starts the tail
startDirection = rMean_vec/np.linalg.norm(rMean_vec)
if not plot: break;
if start is None: #abort if nothing found
raise Exception('Could not identify tail')
# Step 5: remove all circles before start
inInnerRings = indices < start
# Remove particles on the opposite direction to startDirection.
# in the now innermost 5 rings. Likely traces of the bridge.
oppositeDirection = np.dot(r_vec, startDirection) < 0
in5InnermostRings = indices <= start+5
orbitting = np.logical_or(inInnerRings,
np.logical_and(oppositeDirection, in5InnermostRings))
orbittingMask = particlesLeft[orbitting]
r_vec = r_vec[np.logical_not(orbitting)]
tailMask = particlesLeft[np.logical_not(orbitting)]
if plot:
axs[2].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1)
axs[2].axis('equal')
axs[2].scatter(0, 0, s=100, marker="*", c='black', alpha=.7)
axs[2].axis('off')
# Step 6: measure tail length and shape
r = np.linalg.norm(r_vec, axis=-1)
indices = np.digitize(r, edges)
# Make list of barycenters
points = [list(np.mean(r_vec[indices==i], axis=0))
for i in range(1, nRings) if len(r_vec[indices==i])!=0]
points = np.array(points)
# Calculate total length
lengths = np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1))
length = np.sum(lengths)
# Shape (for 2D only)
angles = np.arctan2(points[:,1], points[:,0])
distances = np.linalg.norm(points, axis=-1)
shape = (distances, angles)
if plot:
axs[2].plot(points[:,0], points[:,1], c='black', linewidth=0.5, marker='+')
if plot:
plt.show()
return (bridgeMask, stolenMask, orbittingMask, tailMask), shape, length
if __name__ == "__main__":
data = utils.loadData('200mass', 10400)
segmentEncounter(data, plot=True)
Functions
def segmentEncounter(data, plot=False, mode='all')
-
Segment the encounter into tail, bridge, orbitting and stolen particles as described in the report.
Parameters
data
- A data instance as saved by the simulation to a pickle file
plot
- If true the segmentation will be plotted and shown. Highly useful for debugging.
mode
:string
- If mode is 'all' all parts of the encounter will be identified. If mode is 'bridge' only the bridge will be identified. This is useful when there may be no tail.
Returns
masks
:tupple
- tupple of array corresponding to the masks of the (bridge, stolen, orbitting, tail) particles. One can then use e.g. data['r_vec'][bridgeMask].
shape
:tupple
- tupple of (distances, angles) as measured from the center of mass and with respect to the x axis. They define the shape of the tail
length
:float
- total length of the tail.
Source code
def segmentEncounter(data, plot=False, mode='all'): """Segment the encounter into tail, bridge, orbitting and stolen particles as described in the report. Parameters: data: A data instance as saved by the simulation to a pickle file plot: If true the segmentation will be plotted and shown. Highly useful for debugging. mode (string): If mode is 'all' all parts of the encounter will be identified. If mode is 'bridge' only the bridge will be identified. This is useful when there may be no tail. Returns: masks (tupple): tupple of array corresponding to the masks of the (bridge, stolen, orbitting, tail) particles. One can then use e.g. data['r_vec'][bridgeMask]. shape (tupple): tupple of (distances, angles) as measured from the center of mass and with respect to the x axis. They define the shape of the tail length (float): total length of the tail. """ nRings = 100 # number of rings to use when segmenting the data # Localize the central masses r_vec = data['r_vec'] centers = r_vec[data['type'][:,0]=='center'] rCenters_vec = centers[1] - centers[0] rCenters = np.linalg.norm(rCenters_vec) rCenters_unit = rCenters_vec/np.linalg.norm(rCenters_vec) # Take particles to be on the tail a priori and # remove them as they are found in other structures particlesLeft = np.arange(0, len(r_vec)) if plot: colour = '#c40f4c' f, axs = plt.subplots(1, 3, figsize=(9, 4), sharey=False) f.subplots_adjust(hspace=0, wspace=0) axs[0].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1) axs[0].axis('equal') utils.plotCenterMasses(axs[0], data) axs[0].axis('off') # Step 1: project points to see if they are part of the bridge parallelProjection = np.dot(r_vec - centers[0], rCenters_unit) perpendicularProjection = np.linalg.norm(r_vec - centers[0][np.newaxis] - parallelProjection[:,np.newaxis] * rCenters_unit[np.newaxis], axis=-1) bridgeMask = np.logical_and(np.logical_and(0.3*rCenters < parallelProjection, parallelProjection < .7*rCenters), perpendicularProjection < 2) # Remove the bridge notInBridge = np.logical_not(bridgeMask) r_vec = r_vec[notInBridge] particlesLeft = particlesLeft[notInBridge] if mode == 'bridge': return (bridgeMask, None, None, None), None, None # Step 2: select stolen particles by checking distance to centers stolenMask = (np.linalg.norm(r_vec - centers[0][np.newaxis], axis=-1) > np.linalg.norm(r_vec - centers[1][np.newaxis], axis=-1)) # Remove the stolen part notStolen = np.logical_not(stolenMask) r_vec = r_vec[notStolen] particlesLeft, stolenMask = particlesLeft[notStolen], particlesLeft[stolenMask] # Step 3: segment data into concentric rings (spherical shells really) r_vec = r_vec - centers[0] r = np.linalg.norm(r_vec, axis=-1) edges = np.linspace(0, 30, nRings) # nRings concentric spheres indices = np.digitize(r, edges) # Classify particles into shells if plot: axs[1].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=.1, s=.1) axs[1].axis('equal') axs[1].scatter(0, 0, s=100, marker="*", c='black', alpha=.7) axs[1].axis('off') # Step 4: find start of tail start = None for i in range(1, nRings+1): rMean = np.mean(r[indices==i]) rMean_vec = np.mean(r_vec[indices==i], axis=0) parameter = np.linalg.norm(rMean_vec)/rMean if plot: circ = patches.Circle((0,0), edges[i-1], linewidth=0.5,edgecolor='black',facecolor='none', alpha=.7) axs[1].add_patch(circ) txtxy = edges[i-1] * np.array([np.sin(i/13), np.cos(i/13)]) axs[1].annotate("{:.2f}".format(parameter), xy=txtxy, backgroundcolor='#ffffff55') if start is None and parameter>.8 : start = i #Here starts the tail startDirection = rMean_vec/np.linalg.norm(rMean_vec) if not plot: break; if start is None: #abort if nothing found raise Exception('Could not identify tail') # Step 5: remove all circles before start inInnerRings = indices < start # Remove particles on the opposite direction to startDirection. # in the now innermost 5 rings. Likely traces of the bridge. oppositeDirection = np.dot(r_vec, startDirection) < 0 in5InnermostRings = indices <= start+5 orbitting = np.logical_or(inInnerRings, np.logical_and(oppositeDirection, in5InnermostRings)) orbittingMask = particlesLeft[orbitting] r_vec = r_vec[np.logical_not(orbitting)] tailMask = particlesLeft[np.logical_not(orbitting)] if plot: axs[2].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1) axs[2].axis('equal') axs[2].scatter(0, 0, s=100, marker="*", c='black', alpha=.7) axs[2].axis('off') # Step 6: measure tail length and shape r = np.linalg.norm(r_vec, axis=-1) indices = np.digitize(r, edges) # Make list of barycenters points = [list(np.mean(r_vec[indices==i], axis=0)) for i in range(1, nRings) if len(r_vec[indices==i])!=0] points = np.array(points) # Calculate total length lengths = np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1)) length = np.sum(lengths) # Shape (for 2D only) angles = np.arctan2(points[:,1], points[:,0]) distances = np.linalg.norm(points, axis=-1) shape = (distances, angles) if plot: axs[2].plot(points[:,0], points[:,1], c='black', linewidth=0.5, marker='+') if plot: plt.show() return (bridgeMask, stolenMask, orbittingMask, tailMask), shape, length