Skip to content

Commit

Permalink
Improvements: debug lcu and add new metrics (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryukinix authored Nov 21, 2023
1 parent fedf645 commit 11f7b29
Show file tree
Hide file tree
Showing 12 changed files with 1,283 additions and 599 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ It's a Python implementation of a image segmentation algorithm that
combines superpixel with complex networks dynamics. In this setup, we
classify the algorithm as transductive as well.

# Showcase on grabcut dataset

![showcase](pics/egsis-showcase.png)

First segmentation mask is the result of EGSIS segmentation over lasso
annotation from GrabCut dataset, second segmentation mask is the
ground truth.


# What is transductive segmentation?

Transductive segmentation is a concept in machine learning and
Expand Down
48 changes: 16 additions & 32 deletions egsis/lcu.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,14 @@

from loguru import logger

"""lerax - sex 09 dez 2022 13:55:34
Alguma coisa não está certa... as subnetworks não são disjuntas, a
função g que gera novas particulas quase sempre está zerada... a
matriz delta só fica com elementos na diagonal... tem algo errado aqui
lerax - sáb 17 dez 2022 02:47:16
óh céus!!! A função de evolução não tá mudando em nada depois das
primeiras iterações! Será que existe mais algum bug na implementação
das equações!? Será que eu deveria fazer o que o verri falou e usar o
paper mais novo?! Deixei em research/lcu_simplified_improved.pdf
esse documento deve ajudar
lerax - qua 21 dez 2022 10:10:58
Parece que a evolução de nc está incorreta, inverti a multiplicação de
matrizes, pois geralmente é matrix x vetor, não vetor x matrix...
Os resultados começaram a ter maior variação, mas parecem estar invertidos..
"""


class LabeledComponentUnfolding:

"""
Collective Dynamic Labeled Component Unfolding
It can be used to solve Semi-Supervised problems.
It can be used to solve Transductive Semi-Supervised problems.
Parameters
Expand Down Expand Up @@ -184,11 +163,13 @@ def p(self, G: nx.graph, i: int, j: int, c: int) -> float:
edge_weight = G.edges[i, j]["weight"]
walk = edge_weight / G.degree[i]
survival = 1 - self.competition_level * self.sigma(G, i, j, c)

# FIXME: weirdly, sigma function only outputs 1, 0.5 or 0
# it doesn't makes sense
# logger.trace(f"survival factor = {survival}")
return walk * survival

def probability(self, G: nx.Graph) -> np.ndarray:
"""Matrix with probabilities of particle survival"""
"""Matrix with probabilities of particle surviving"""
P = np.zeros(shape=self.N.shape)
C, nodes, _ = P.shape
for c in range(C):
Expand All @@ -198,6 +179,8 @@ def probability(self, G: nx.Graph) -> np.ndarray:
return P

def probability_of_new_particles(self, G: nx.Graph, c: int) -> np.ndarray:
# FIXME: review this code based on the paper
# equation on page 4
node_degrees = [
G.degree[node] for node in G.nodes
]
Expand All @@ -222,7 +205,7 @@ def n0(self, G: nx.Graph) -> np.ndarray:
if label != 0:
labels[cls, idx] = label
particles = labels * population * self.scale_particles
logger.debug(f"n0: {particles}")
logger.debug(f"n0: \n{particles}")
return particles

def N0(self, G: nx.Graph):
Expand All @@ -241,17 +224,18 @@ def delta0(self, G: nx.Graph):
return np.zeros(shape=(self.n_classes, nodes, nodes))

def sigma(self, G: nx.Graph, i: int, j: int, c: int) -> float:
"""Matrix with current relative domination sigma_ij[c]
"""Current relative subordination sigma_ij[c]
Number of particles with label=c which moved from v_i to
v_j in the current time.
The fraction of particles that do not belong to class c and
have sucessfuly passed thorugh edge (i, j) in any direction at
the current time.
"""
S = np.sum((self.N[:, i, j] + self.N[:, j, i].T).flatten())
S = np.sum((self.N[:, i, j] + self.N[:, j, i]).flatten())
result: float
if S <= 0:
result = 1 - (1 / self.n_classes)
else:
if S > 0:
result = 1 - ((self.N[c][i][j] + self.N[c][j][i]) / S)
else:
result = 1 - (1 / self.n_classes)

return result

Expand Down
43 changes: 39 additions & 4 deletions egsis/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,42 @@ def f1(y_true: np.ndarray, y_pred: np.ndarray) -> float:


def err(y_true: np.ndarray, y_pred: np.ndarray) -> float:
"""Error rate (mis-segmentation rate)"""
roi_pixels = y_true
mis_segmentation = (y_true | y_pred) - y_true
return mis_segmentation.sum() / roi_pixels.sum()
"""This function calculates the error rate, also known as the
mis-segmentation rate, between the true and predicted values.
Parameters
---------
y_true : np.ndarray
The ground truth binary labels. The binary label indicates
whether each pixel is within the region of interest (ROI) or
not.
y_pred : np.ndarray:
The predicted binary labels. The binary label indicates
whether each pixel is predicted to be within the ROI or not.
Returns
-------
err : float
The mis-segmentation rate. This is calculated as the number of
mis-segmented pixels (false-positives and false-negatives) divided
by the total number of pixels in the ROI.
"""
union = (y_true | y_pred)
intersection = (y_true & y_pred)
mis_segmentation = (union - intersection).sum()
w, h = y_true.shape
roi = w * h
return mis_segmentation / roi


def recall(y_true: np.ndarray, y_pred: np.ndarray) -> float:
tp = (y_true & y_pred).sum()
fn = ((y_true | y_pred) - y_pred).sum()
return tp / (tp + fn)


def precision(y_true: np.ndarray, y_pred: np.ndarray) -> float:
tp = (y_true & y_pred).sum()
fp = ((y_true | y_pred) - y_true).sum()
return tp / (tp + fp)
8 changes: 7 additions & 1 deletion egsis/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,19 @@ def build_complex_network(
segments=segments,
labels=y
)
logger.info("Complex networks: compute node labels finished.")
complex_networks.compute_node_features(
graph=G,
img=X,
segments=segments,
feature_method=self.feature_extraction
)
logger.info("Complex networks: feature extraction finished.")
complex_networks.compute_edge_weights(
graph=G,
similarity_function=self.feature_similarity
)
logger.info("Complex networks: compute node weights finished.")
return G

def sub_networks_to_matrix(self, sub_networks: List[networkx.Graph]):
Expand All @@ -134,15 +137,17 @@ def fit_predict(self, X: numpy.ndarray, y: numpy.ndarray):
luminosity of each color channel of RGB
y : numpy.ndarray (shape=(n, m))
it's the label matrix with partial annotation, to be full
filled Every non-zero value it's a label, and zero it's
filled every non-zero value it's a label, and zero it's
an unlabeled pixel.
Returns
-------
new y matrix with full filled labels.
"""
logger.info("Run!")
self.segments = self.build_superpixels(X)
logger.info("Superpixels: finished.")
self.G = self.build_complex_network(X, y, self.segments)
logger.info("Complex networks: finished.")
n_classes = len(numpy.unique(y)) - 1
collective_dynamic = lcu.LabeledComponentUnfolding(
competition_level=self.lcu_competition_level,
Expand All @@ -152,6 +157,7 @@ def fit_predict(self, X: numpy.ndarray, y: numpy.ndarray):

self.sub_networks = collective_dynamic.fit_predict(self.G)
self.G_pred = collective_dynamic.classify_vertexes(self.sub_networks)
logger.info("Dynamic collective LCU: finished.")

# FIXME: should return a matrix y with new labels
return self.G_pred
Expand Down
80 changes: 57 additions & 23 deletions notebooks/annotator_egsis.ipynb

Large diffs are not rendered by default.

1,014 changes: 571 additions & 443 deletions notebooks/benchmark.ipynb

Large diffs are not rendered by default.

399 changes: 364 additions & 35 deletions notebooks/labeled_component_unfolding.ipynb

Large diffs are not rendered by default.

102 changes: 53 additions & 49 deletions notebooks/model_egsis.ipynb

Large diffs are not rendered by default.

155 changes: 155 additions & 0 deletions notebooks/showcase.ipynb

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions notebooks/superpixel_complex_networks.ipynb

Large diffs are not rendered by default.

Binary file added pics/egsis-showcase.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "egsis"
version = "0.1.0a0"
version = "0.1.0"
description = "Exploratory Graph-based Semi-Supervised Image Segmentation"
authors = [
{name = "Manoel Vilela", email = "[email protected]"},
Expand Down

0 comments on commit 11f7b29

Please sign in to comment.