GOALS – I have endothelial cell images (384×152) and I provide as input to the network a patch of size 1×64×64. The patch is extracted around each pixel of the image. This patch contains the central pixel and the surrounding pixels. A patch is therefore analyzed to determine the class of the central pixel. For pixels located at the edges, the original image is extended by adding reflected copies of the edges (cv2.BORDER_REFLECT_101 in cv2 or reflect mode)
Then The neural network analyzes the patch and predicts the class of the central pixel of the patch.
Finally The predictions for each patch are used to construct the final segmentation image.
I have done the data preparation to satisfy the extraction of patches (dataset called PatchDataset) and implemented the neural network described in the publication (see attached picture SWNet) or directly the link (https://pure.tudelft.nl/ws/portalfiles/portal/66777320/10.1186_s42490_019_0003_2.pdf page12/17)
class PatchDataset(Dataset):
def __init__(self, imagedir, maskdir, patch_size, transform=None, flip=None):
self.imagedir = imagedir
self.maskdir = maskdir
self.patch_size = patch_size
self.transform = transform
self.flip = flip
# Images & masks
self.image_files = sorted(list(self.imagedir.glob('*.png')))
self.mask_files = sorted(list(self.maskdir.glob('*.png')))
def __len__(self):
# Calcul du nombre total de pixels dans toutes les images
total_pixels = 0
for img_path in self.image_files:
img = cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE)
total_pixels += img.shape[0] * img.shape[1]
return total_pixels
def __getitem__(self, idx):
img_index, x, y = self._get_patch_indices(idx)
image = cv2.imread(str(self.image_files[img_index]), cv2.IMREAD_GRAYSCALE) / 255.0
mask = cv2.imread(str(self.mask_files[img_index]), cv2.IMREAD_GRAYSCALE) / 255.0 # Masque en niveaux de gris
patch = self._extract_patch(image, x, y)
label = mask[x, y]
if self.transform:
patch = self.transform(patch)
return np.expand_dims(patch, 0), label
def _get_patch_indices(self, idx):
total_pixels = 0
for i, img_path in enumerate(self.image_files):
img = cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE)/ 255.0
pixels = img.shape[0] * img.shape[1]
if idx < total_pixels + pixels:
img_idx = i
pixel_idx = idx - total_pixels
x = pixel_idx // img.shape[1]
y = pixel_idx % img.shape[1]
return img_idx, x, y
total_pixels += pixels
raise IndexError("Index out of range")
def _extract_patch(self, image, x, y):
half_size = self.patch_size // 2
# Utilisation de cv2.copyMakeBorder pour créer une bordure avec réflexion
mirrored_image = cv2.copyMakeBorder(image, half_size, half_size, half_size, half_size, cv2.BORDER_REFLECT_101)
x += half_size
y += half_size
patch = mirrored_image[x - half_size:x + half_size + 1, y - half_size:y + half_size + 1]
return patch
and the used CNN (described in the pdf)
class CustomCNN(nn.Module):
def __init__(self):
super(CustomCNN, self).__init__()
# Définition des couches du réseau
self.conv_layers = nn.Sequential(
# Premier bloc convolutif
nn.Conv2d(1, 32, kernel_size=3, padding=1), # Convolution 3x3, 32 canaux
nn.ReLU(),
nn.Conv2d(32, 32, kernel_size=3, padding=1), # Convolution 3x3, 32 canaux
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2), # MaxPooling 2x2
# Deuxième bloc convolutif
nn.Conv2d(32, 64, kernel_size=3, padding=1), # Convolution 3x3, 64 canaux
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, padding=1), # Convolution 3x3, 64 canaux
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2), # MaxPooling 2x2
# Troisième bloc convolutif
nn.Conv2d(64, 128, kernel_size=3, padding=1), # Convolution 3x3, 128 canaux
nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, padding=1),# Convolution 3x3, 128 canaux
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2), # MaxPooling 2x2
# Quatrième bloc convolutif
nn.Conv2d(128, 128, kernel_size=3, padding=1), # Convolution 3x3, 128 canaux
nn.ReLU(),
nn.Conv2d(128, 256, kernel_size=3, padding=1),# Convolution 3x3, 128 canaux
nn.ReLU()
)
# Global Average Pooling + Fully Connected Layer
self.global_avg_pool = nn.AdaptiveAvgPool2d((1, 1)) # Global Average Pooling
self.fc1 = nn.Linear(256, 2) # Fully Connected Layer avec 2 neurones (pour 2 classes)
def forward(self, x):
x = self.conv_layers(x) # Passage par les couches convolutives
x = self.global_avg_pool(x) # Application du Global Average Pooling
x = x.view(x.size(0), -1) # Flatten le tensor pour la couche fully connected
x = self.fc(x)
# Passage par la couche fully connected
return x
**QUESTION – I don’t understand why my outputs are identical at the end of each epoch.
**
Using a dummy dataset and the same network, the same behavior is observed
import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
class RandomDataset(Dataset):
def __init__(self, num_samples, image_size, num_classes):
self.num_samples = num_samples
self.image_size = image_size
self.num_classes = num_classes
def __len__(self):
return self.num_samples
def __getitem__(self, idx):
# Générer une image aléatoire
image = np.random.rand(self.image_size, self.image_size).astype(np.float32)
# Générer un label aléatoire (classification binaire)
label = np.random.randint(0, self.num_classes)
# Convertir en tenseur PyTorch
image = torch.tensor(image, dtype=torch.float).unsqueeze(0)
label = torch.tensor(label, dtype=torch.long)
return image, label
#
num_samples = 100
image_size = 64
num_classes = 2
dataset = RandomDataset(num_samples, image_size, num_classes)
dataloader = DataLoader(dataset, batch_size=8, shuffle=True)
#
model = CustomCNN()
#
criterion = nn.CrossEntropyLoss() # Utiliser CrossEntropyLoss pour classification multi-classe
optimizer = optim.Adam(model.parameters(), lr=0.00001)
# training
def train_model(model, dataloader, criterion, optimizer, epochs=10):
model.train()
for epoch in range(epochs):
running_loss = 0.0
for images, labels in dataloader:
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
print(f"LABEL : {labels}")
prob = F.softmax(outputs, dim=1)
#print(f"PROB : {prob}")
predicted = torch.argmax(prob, 1)
print(f"PRED : {predicted}")
loss.backward()
optimizer.step()
running_loss += loss.item() * images.size(0)
epoch_loss = running_loss / len(dataloader.dataset)
print(f'Epoch {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}')
train_model(model, dataloader, criterion, optimizer, epochs=10)
Thank you in advance for your help and advice. Have a nice day.