Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0.2.5 #30

Merged
merged 6 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
ci:
autoupdate_schedule: monthly
autofix_commit_msg: "style(pre-commit.ci): auto fixes [...]"
autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate"

repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.254
hooks:
- id: ruff
args: [--fix]

- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.1.1
hooks:
- id: mypy
files: "^EmbedSeg/"
27 changes: 13 additions & 14 deletions EmbedSeg/_tests/test_center_crops.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
from EmbedSeg.utils.generate_crops import generate_center_image, generate_center_image_3d
import pytest
from EmbedSeg.utils.generate_crops import generate_center_image
import numpy as np


def test_generate_center_image_1():
ma = np.zeros((100, 100))
ma [30:-30, 30:-30] = 2
ma[30:-30, 30:-30] = 2
ids = np.unique(ma)
ids = ids[ids!=0]
center_image = generate_center_image(ma, center='centroid', ids=ids, one_hot = False )
y, x = np.where(center_image==True)
assert y==50 and x==50
ids = ids[ids != 0]
center_image = generate_center_image(ma, center="centroid", ids=ids, one_hot=False)
y, x = np.where(center_image is True)
assert y == 50 and x == 50


def test_generate_center_image_2():
ma = np.zeros((100, 100))
ma [30:-30, 30:-30] = 2
ma[30:-30, 30:-30] = 2
ids = np.unique(ma)
ids = ids[ids!=0]
center_image = generate_center_image(ma, center='medoid', ids=ids, one_hot = False )
y, x = np.where(center_image==True)
assert np.abs(y-50)<=1 and np.abs(x-50)<=1


ids = ids[ids != 0]
center_image = generate_center_image(ma, center="medoid", ids=ids, one_hot=False)
y, x = np.where(center_image is True)
assert np.abs(y - 50) <= 1 and np.abs(x - 50) <= 1
17 changes: 13 additions & 4 deletions EmbedSeg/criterions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@
from EmbedSeg.criterions.my_loss_3d import SpatialEmbLoss_3d



def get_loss(grid_z, grid_y, grid_x, pixel_z, pixel_y, pixel_x, one_hot, loss_opts):
def get_loss(
grid_z,
grid_y,
grid_x,
pixel_z,
pixel_y,
pixel_x,
one_hot,
loss_opts,
):
if grid_z is None:
return SpatialEmbLoss(grid_y, grid_x, pixel_y, pixel_x, one_hot, **loss_opts)
else:
return SpatialEmbLoss_3d(grid_z, grid_y, grid_x, pixel_z, pixel_y, pixel_x, one_hot, **loss_opts)

return SpatialEmbLoss_3d(
grid_z, grid_y, grid_x, pixel_z, pixel_y, pixel_x, one_hot, **loss_opts
)
74 changes: 43 additions & 31 deletions EmbedSeg/criterions/lovasz_losses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
from torch.autograd import Variable
import torch.nn.functional as F
import numpy as np

try:
from itertools import ifilterfalse
except ImportError: # py3k
from itertools import filterfalse
pass


def lovasz_grad(gt_sorted):
Expand All @@ -23,15 +24,15 @@ def lovasz_grad(gt_sorted):
p = len(gt_sorted)
gts = gt_sorted.sum()
intersection = gts.float() - gt_sorted.float().cumsum(0)
#union = gts.float() + (1 - gt_sorted).float().cumsum(0)
# union = gts.float() + (1 - gt_sorted).float().cumsum(0)
union = gts.float() + (1 - gt_sorted.float()).cumsum(0)
jaccard = 1. - intersection / union
jaccard = 1.0 - intersection / union
if p > 1: # cover 1-pixel case
jaccard[1:p] = jaccard[1:p] - jaccard[0:-1]
return jaccard


def iou_binary(preds, labels, EMPTY=1., ignore=None, per_image=True):
def iou_binary(preds, labels, EMPTY=1.0, ignore=None, per_image=True):
"""
IoU for foreground class
binary: 1 foreground, 0 background
Expand All @@ -47,11 +48,11 @@ def iou_binary(preds, labels, EMPTY=1., ignore=None, per_image=True):
else:
iou = float(intersection) / union
ious.append(iou)
iou = mean(ious) # mean accross images if per_image
iou = mean(ious) # mean accross images if per_image
return 100 * iou


def iou(preds, labels, C, EMPTY=1., ignore=None, per_image=False):
def iou(preds, labels, C, EMPTY=1.0, ignore=None, per_image=False):
"""
Array of IoU for each (non ignored) class
"""
Expand All @@ -64,8 +65,7 @@ def iou(preds, labels, C, EMPTY=1., ignore=None, per_image=False):
# The ignored label is sometimes among predicted classes (ENet - CityScapes)
if i != ignore:
intersection = ((label == i) & (pred == i)).sum()
union = ((label == i) | (
(pred == i) & (label != ignore))).sum()
union = ((label == i) | ((pred == i) & (label != ignore))).sum()
if not union:
iou.append(EMPTY)
else:
Expand All @@ -87,11 +87,14 @@ def lovasz_hinge(logits, labels, per_image=True, ignore=None):
ignore: void class id
"""
if per_image:
loss = mean(lovasz_hinge_flat(*flatten_binary_scores(log.unsqueeze(0), lab.unsqueeze(0), ignore))
for log, lab in zip(logits, labels))
loss = mean(
lovasz_hinge_flat(
*flatten_binary_scores(log.unsqueeze(0), lab.unsqueeze(0), ignore)
)
for log, lab in zip(logits, labels)
)
else:
loss = lovasz_hinge_flat(
*flatten_binary_scores(logits, labels, ignore))
loss = lovasz_hinge_flat(*flatten_binary_scores(logits, labels, ignore))
return loss


Expand All @@ -104,9 +107,9 @@ def lovasz_hinge_flat(logits, labels):
"""
if len(labels) == 0:
# only void pixels, the gradients should be 0
return logits.sum() * 0.
signs = 2. * labels.float() - 1.
errors = (1. - logits * Variable(signs))
return logits.sum() * 0.0
signs = 2.0 * labels.float() - 1.0
errors = 1.0 - logits * Variable(signs)
errors_sorted, perm = torch.sort(errors, dim=0, descending=True)
perm = perm.data
gt_sorted = labels[perm]
Expand All @@ -124,7 +127,7 @@ def flatten_binary_scores(scores, labels, ignore=None):
labels = labels.view(-1)
if ignore is None:
return scores, labels
valid = (labels != ignore)
valid = labels != ignore
vscores = scores[valid]
vlabels = labels[valid]
return vscores, vlabels
Expand All @@ -135,7 +138,7 @@ def __init__(self):
super(StableBCELoss, self).__init__()

def forward(self, input, target):
neg_abs = - input.abs()
neg_abs = -input.abs()
loss = input.clamp(min=0) - input * target + (1 + neg_abs.exp()).log()
return loss.mean()

Expand All @@ -158,26 +161,36 @@ def binary_xloss(logits, labels, ignore=None):
def lovasz_softmax(probas, labels, only_present=False, per_image=False, ignore=None):
"""
Multi-class Lovasz-Softmax loss
probas: [B, C, H, W] Variable, class probabilities at each prediction (between 0 and 1)
labels: [B, H, W] Tensor, ground truth labels (between 0 and C - 1)
probas: [B, C, H, W] Variable,
class probabilities at each prediction (between 0 and 1)
labels: [B, H, W] Tensor,
ground truth labels (between 0 and C - 1)
only_present: average only on classes present in ground truth
per_image: compute the loss per image instead of per batch
ignore: void class labels
"""
if per_image:
loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(0), lab.unsqueeze(0), ignore), only_present=only_present)
for prob, lab in zip(probas, labels))
loss = mean(
lovasz_softmax_flat(
*flatten_probas(prob.unsqueeze(0), lab.unsqueeze(0), ignore),
only_present=only_present
)
for prob, lab in zip(probas, labels)
)
else:
loss = lovasz_softmax_flat(
*flatten_probas(probas, labels, ignore), only_present=only_present)
*flatten_probas(probas, labels, ignore), only_present=only_present
)
return loss


def lovasz_softmax_flat(probas, labels, only_present=False):
"""
Multi-class Lovasz-Softmax loss
probas: [P, C] Variable, class probabilities at each prediction (between 0 and 1)
labels: [P] Tensor, ground truth labels (between 0 and C - 1)
probas: [P, C] Variable,
class probabilities at each prediction (between 0 and 1)
labels: [P] Tensor,
ground truth labels (between 0 and C - 1)
only_present: average only on classes present in ground truth
"""
C = probas.size(1)
Expand All @@ -190,8 +203,7 @@ def lovasz_softmax_flat(probas, labels, only_present=False):
errors_sorted, perm = torch.sort(errors, 0, descending=True)
perm = perm.data
fg_sorted = fg[perm]
losses.append(
torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))
losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))
return mean(losses)


Expand All @@ -200,12 +212,11 @@ def flatten_probas(probas, labels, ignore=None):
Flattens predictions in the batch
"""
B, C, H, W = probas.size()
probas = probas.permute(0, 2, 3, 1).contiguous(
).view(-1, C) # B * H * W, C = P, C
probas = probas.permute(0, 2, 3, 1).contiguous().view(-1, C) # B * H * W, C = P, C
labels = labels.view(-1)
if ignore is None:
return probas, labels
valid = (labels != ignore)
valid = labels != ignore
vprobas = probas[valid.nonzero().squeeze()]
vlabels = labels[valid]
return vprobas, vlabels
Expand All @@ -220,6 +231,7 @@ def xloss(logits, labels, ignore=None):

# --------------------------- HELPER FUNCTIONS ---------------------------


def mean(l, ignore_nan=False, empty=0):
"""
nanmean compatible with generators.
Expand All @@ -231,8 +243,8 @@ def mean(l, ignore_nan=False, empty=0):
n = 1
acc = next(l)
except StopIteration:
if empty == 'raise':
raise ValueError('Empty mean')
if empty == "raise":
raise ValueError("Empty mean")
return empty
for n, v in enumerate(l, 2):
acc += v
Expand Down
Loading