Trained models with initial parameters for classification, object detection and segmentation of breast arterial calcification on FFDM mammograms. Provided as MATLAB live scripts. The accompanying MATLAB code used to train the models is also included.
Images were pre-processed for the three models which entailed converting the DICOM images into 16-bit pngs and, for classification and object detection, cropping the images to the breast. For both steps we used code made available by another study.
For demo purposes, a sample DICOM image with breast arterial calcification, 1-1.dcm
, from the Cancer Imaging Archive is included. Its original file location is: \CBIS-DDSM\Calc-Test_P_00646_LEFT_CC\08-29-2017-DDSM-NA-04488\1.000000-full mammogram images-68505.
Convert DICOM image to 16-bit png using Pydicom. The bit-depth is set to 16 as that is the bit-depth of the demo image.
import os
import png
import pydicom
def save_dicom_image_as_png(dicom_filename, png_filename, bitdepth=16):
"""
Save 12-bit mammogram from dicom as rescaled 16-bit png file.
:param dicom_filename: path to input dicom file.
:param png_filename: path to output png file.
:param bitdepth: bit depth of the input image. Set it to 12 for 12-bit mammograms.
"""
image = pydicom.read_file(dicom_filename).pixel_array
with open(png_filename, 'wb') as myFile:
writer = png.Writer(
height=image.shape[0],
width=image.shape[1],
bitdepth=bitdepth,
greyscale=True
)
writer.write(myFile, image.tolist())
Crop converted image using code from Wu et al (2019):
single_crop.crop_single_mammogram(os.path.join(r, file), # input mammogram_path
"NO", # horizontal_flip
view, # view, for the demo image this is 'L-CC'
file, # cropped_mammogram_path
'cropped_metadata.pkl', # metadata_path
100, # num_iterations
50) # buffer_size
Converted Test Image | Cropped Test Image |
---|---|
![]() |
![]() |
To try the segmentation network, run the MATLAB live script DeepLabv3PlusResNet18TrainedNetworkWithInitialParameters.mlx
to create the layer graph, lgraph
. Assemble the network from the pre-trained layers:
net = assembleNetwork(lgraph);
Convert the cropped image to RGB:
imagePath = fullfile('croppedImage1-1.png');
grayImage = imread(imagePath);
rgbImage = cat(3, grayImage, grayImage, grayImage);
imwrite(rgbImage, strrep(imagePath,'.png','.tif'),"tif");
Read in the cropped tif and apply the segmentation network. 512x512 patches are used:
im = imread('croppedImage1-1.tif');
figure
imshow(im)
segmentedImage = segmentImagePatchwise(im, net, [512 512]);
seg = labeloverlay(im,segmentedImage);
figure
imshow(seg);
% segmentImagePatchwise performs patchwise semantic segmentation on the input image
% using the provided network.
%
% OUT = segmentImagePatchwise(IM, NET, PATCHSIZE) returns a semantically
% segmented image, segmented using the network NET. The segmentation is
% performed patches-wise on patches of size PATCHSIZE.
% PATCHSIZE is 1x2 vector holding [WIDTH HEIGHT] of the patch.
function out = segmentImagePatchwise(im, net, patchSize)
[height, width, nChannel] = size(im);
patch = zeros([patchSize, nChannel], 'like', im);
% pad image to have dimensions as multiples of patchSize
padSize(1) = patchSize(1) - mod(height, patchSize(1));
padSize(2) = patchSize(2) - mod(width, patchSize(2));
im_pad = padarray (im, padSize, 0, 'post');
[height_pad, width_pad, ~] = size(im_pad);
out = zeros([size(im_pad,1), size(im_pad,2)], 'uint8');
for i = 1:patchSize(1):height_pad
for j =1:patchSize(2):width_pad
patch = im_pad(i:i+patchSize(1)-1, j:j+patchSize(2)-1, :);
patch_seg = semanticseg(patch, net, 'outputtype', 'uint8');
out(i:i+patchSize(1)-1, j:j+patchSize(2)-1) = patch_seg;
end
end
% Remove the padding
out = out(1:height, 1:width);
end
The network predicts for three classes: BAC, background and breast tissue: | Test Image | Prediction |
---|---|---|
![]() |
![]() |
The dataset used in our study came in four different sizes so all images were cropped as above and padded with zeros using Pillow to a size of 4140x3372. The code below is modified to pad one image, croppedImage1-1.png
:
from PIL import Image
import os
maxHeight = 4140
maxWidth = 3372
colour = 0
def add_margin(pil_img, top, right, bottom, left, colour):
width, height = pil_img.size
new_width = width + right + left
new_height = height + top + bottom
result = Image.new(pil_img.mode, (new_width, new_height), colour)
result.paste(pil_img, (left, top))
return result
new_path = 'C:\\Users\\user\\Desktop'
files = []
# r=root, d=directories, f = files.
for r, d, f in os.walk(new_path):
for file in f:
if file.endswith('croppedImage1-1.png'):
im = Image.open(os.path.join(r, file))
width, height = im.size;
if width % 2 != 0:
width_padding = maxWidth - width
left = int((width_padding - 1)/2)
right = left + 1
else:
width_padding = maxWidth - width
left = int(width_padding/2)
right = left
if height % 2 != 0:
height_padding = maxHeight - height
top = int((height_padding - 1)/2)
bottom = top + 1
else:
height_padding = maxHeight - height
top = int(height_padding/2)
bottom = top
print('Adding margin...')
im_new = add_margin(im, top, right, bottom, left, colour)
print('Added margin...')
print('Saving image...')
im_new.save('C:\\Users\\user\\Desktop\\' + str(file).replace('cropped', 'paddedCropped'),
quality=100)
print('Image saved...')
Images were reduced to 70% size due to "out of memory" errors on the GPU. A trained yolov4ObjectDetector is provided. The detector performed poorly with detections only occurring at low thresholds, i.e. 0.001:
% load trainedDetector
load("yolov470epochs.mat");
% read and resize image
I = imread("paddedCroppedImage1-1.png");
I = imresize(I, [2898 2360]);
figure
imshow(I)
% run object detector
[bboxes,scores,labels] = detect(trainedDetector, I, 'Threshold', 0.001);
% view results
I = insertObjectAnnotation(I,"rectangle",bboxes,scores);
figure
imshow(I)
Resized Image | Prediction |
---|---|
![]() |
![]() |
To try the classification network, run the MATLAB live script ResNet22TrainedNetworkWithInitialParametersUpdated.mlx
to create the layer graph, lgraph
. Assemble the network from the pre-trained layers:
net = assembleNetwork(lgraph);
For the classifier, images were also reduced to 70% size due to "out of memory" errors on the GPU.
% read and resize image
I = imread("paddedCroppedImage1-1.png");
I = imresize(I, [2898 2360]);
figure
imshow(I)
The classification model classifies whole images as 'BAC' or 'NON_BAC':
>> net.Layers
ans =
85×1 Layer array with layers:
1 'Image_input_1' Image Input 2898×2360×1 images with 'zerocenter' normalization
...
85 'classoutput' Classification Output crossentropyex with classes 'BAC' and 'NON_BAC'
Run the model on the demo image and view the results. The model correctly classifies the demo image as BAC+ve:
[Y, scores] = classify(net, I);
>> Y
Y =
categorical
BAC
>> scores
scores =
1×2 single row vector
1.0000 0.0000