Develop Custom Datastore for DICOM Data - MATLAB & Simulink (original) (raw)
Main Content
This example shows how to develop a custom datastore that supports writing operations. The datastore is named DICOMDatastore
because it supports DICOM ® (Digital Imaging and Communications in Medicine) data, which is an international standard for medical imaging information.
Developing Custom Datastores
The topic Develop Custom Datastore describes the general process for creating a custom datastore, as well as the specific requirements to add different pieces of functionality. There are a variety of superclasses you can subclass from depending on what pieces of functionality you need (parallel evaluation, writing operations, shuffling, and so on). In particular, you can add support for writing operations by subclassing from matlab.io.datastore.FileWritable. However, for the broadest set of writing functionality, you must also subclass from matlab.io.datastore.FoldersPropertyProvider, which adds aFolders
property to the datastore. The complete requirements to add writing support to a custom datastore are covered in Add Support for Writing Data.
Class Definition
This table contains code and explanations for the DICOMDatastore
class.
classdef DICOMDatastore < matlab.io.Datastore & ... matlab.io.datastore.FileWritable & ... matlab.io.datastore.FoldersPropertyProvider | Class HeaderDICOMDatastore inherits from Datastore for basic functionality, as well as from FileWritable and FoldersPropertyProvider to enable file writing capabilities. |
---|---|
properties Files matlab.io.datastore.FileSet end | Public PropertiesDICOMDatastore defines a publicFiles property that is a FileSet object.DICOMDatastore inherits a Folders property from FoldersPropertyProvider, so that property does not need to be initialized. |
properties (Constant) SupportedOutputFormats = ... [matlab.io.datastore.ImageDatastore.SupportedOutputFormats, "dcm"]; DefaultOutputFormat = "dcm"; end | DICOMDatastore definesSupportedOutputFormats andDefaultOutputFormat as constant properties with default values. "dcm" is a custom format for DICOM data. |
methods(Access = public) function myds = DICOMDatastore(location) % The class constructor to set properties of the datastore. myds.Files = matlab.io.datastore.FileSet(location, ... "IncludeSubfolders", true); populateFoldersFromLocation(myds,location); reset(myds); end | Public MethodsThe public methods section defines common datastore methods that the class uses to manipulate data. Public methods are externally accessible, so class users ofDICOMDatastore can call these methods (in addition to other public methods inherited from the superclasses).The constructorDICOMDatastore creates a newDICOMDatastore object by setting values for theFiles and Folders properties. Use FileSet to set the value of theFiles property.Use the populateFoldersFromLocation method ofFoldersPropertyProvider to set the value of theFolders property. |
function tf = hasdata(myds) %HASDATA Returns true if more data is available. % Return logical scalar indicating availability of data. % This method should be called before calling read. This % is an abstract method and must be implemented by the % subclasses. hasdata is used in conjunction with read to % read all the data within the datastore. tf = hasNextFile(myds.Files); end | The hasdata, read, reset, and progress methods define the infrastructure for the datastore to work with small chunks of data at a time. These are abstract methods that must be implemented by the subclass. |
function [data, info] = read(myds) %READ Read data and information about the extracted data. % Return the data extracted from the datastore in the % appropriate form for this datastore. Also return % information about where the data was extracted from in % the datastore. Both the outputs are required to be % returned from the read method and can be of any type. % info is recommended to be a struct with information % about the chunk of data read. data represents the % underlying class of tall, if tall is created on top of % this datastore. This is an abstract method and must be % implemented by the subclasses. % In this example, the read method reads data from the % datastore using a custom reader function, MyFileReader, % which takes the resolved filenames as input. if ~hasdata(myds) error("No more data to read.\nUse reset method to " ... + "reset the datastore to the start of the data. Before " ... + "calling the read method, check if data is available " ... + "to read by using the hasdata method."); end file = nextfile(myds.Files); try data = dicomread(file.Filename); catch ME error("%s has failed", file.FileName); end info.FileSize = size(data); info.Filename = file.Filename; end | |
function reset(myds) %RESET Reset to the start of the data. % Reset the datastore to the state where no data has been % read from it. This is an abstract method and must be % implemented by the subclasses. % In this example, the datastore is reset to point to the % first file (and first partition) in the datastore. reset(myds.Files); end | |
function frac = progress(myds) %PROGRESS Percentage of consumed data between 0.0 and 1.0. % Return fraction between 0.0 and 1.0 indicating progress as a % double. The provided example implementation returns the % ratio of the index of the current file from FileSet % to the number of files in FileSet. A simpler % implementation can be used here that returns a 1.0 when all % the data has been read from the datastore, and 0.0 % otherwise. % % See also matlab.io.Datastore, read, hasdata, reset, readall, % preview. frac = progress(myds.Files); end end | |
methods(Access = protected) function dsCopy = copyElement(myds) %COPYELEMENT Create a deep copy of the datastore % Create a deep copy of the datastore. We need to call % copy on the datastore's property FileSet because it is % a handle object. Creating a deep copy allows methods % such as readall and preview, which call the copy method, % to remain stateless. dsCopy = copyElement@matlab.mixin.Copyable(myds); dsCopy.Files = copy(myds.Files); end | Protected MethodsProtected methods redefine methods that were inherited by the class, and they are only accessible to DICOMDatastore. For more information, see Modify Inherited Methods.The protectedcopyElement method is required whenever FileSet is used to define properties. The copyElement method allows methods such as readall and preview to remain stateless. |
function tf = write(myds, data, writeInfo, outFmt, varargin) if outFmt == "dcm" dicomwrite(data, writeInfo.SuggestedOutputName, varargin{:}); else write@matlab.io.datastore.FileWritable(myds, data, ... writeInfo, outFmt, varargin{:}); end tf = true; end | The protected write method writes out chunks of data. Since DICOMDatastore supportsImageDatastore formats as well as the custom format"dcm", the write method uses different functions to write the data depending on the output format. |
function files = getFiles(myds) files = myds.Files.FileInfo.Filename; end end | The protected getFiles method is necessary since DICOMDatastore usesFileSet objects for the Files property. The Files property is generally required to return acellstr, so the getFiles method uses theFileSet object to generate a cellstr of the file paths. |
end | End the classdef section. |
classdef DICOMDatastore < matlab.io.Datastore & ... matlab.io.datastore.FileWritable & ... matlab.io.datastore.FoldersPropertyProvider properties Files matlab.io.datastore.FileSet end
properties (Constant)
SupportedOutputFormats = ...
[matlab.io.datastore.ImageDatastore.SupportedOutputFormats, "dcm"];
DefaultOutputFormat = "dcm";
end
methods(Access = public)
function myds = DICOMDatastore(location)
% The class constructor to set properties of the datastore.
myds.Files = matlab.io.datastore.FileSet(location, ...
"IncludeSubfolders", true);
populateFoldersFromLocation(myds,location);
reset(myds);
end
function tf = hasdata(myds)
%HASDATA Returns true if more data is available.
% Return logical scalar indicating availability of data.
% This method should be called before calling read. This
% is an abstract method and must be implemented by the
% subclasses. hasdata is used in conjunction with read to
% read all the data within the datastore.
tf = hasNextFile(myds.Files);
end
function [data, info] = read(myds)
%READ Read data and information about the extracted data.
% Return the data extracted from the datastore in the
% appropriate form for this datastore. Also return
% information about where the data was extracted from in
% the datastore. Both the outputs are required to be
% returned from the read method and can be of any type.
% info is recommended to be a struct with information
% about the chunk of data read. data represents the
% underlying class of tall, if tall is created on top of
% this datastore. This is an abstract method and must be
% implemented by the subclasses.
% In this example, the read method reads data from the
% datastore using a custom reader function, MyFileReader,
% which takes the resolved filenames as input.
if ~hasdata(myds)
error("No more data to read.\nUse reset method to " ...
+ "reset the datastore to the start of the data. Before " ...
+ "calling the read method, check if data is available " ...
+ "to read by using the hasdata method.");
end
file = nextfile(myds.Files);
try
data = dicomread(file.Filename);
catch ME
error("%s has failed", file.FileName);
end
info.FileSize = size(data);
info.Filename = file.Filename;
end
function reset(myds)
%RESET Reset to the start of the data.
% Reset the datastore to the state where no data has been
% read from it. This is an abstract method and must be
% implemented by the subclasses.
% In this example, the datastore is reset to point to the
% first file (and first partition) in the datastore.
reset(myds.Files);
end
function frac = progress(myds)
%PROGRESS Percentage of consumed data between 0.0 and 1.0.
% Return fraction between 0.0 and 1.0 indicating progress as a
% double. The provided example implementation returns the
% ratio of the index of the current file from FileSet
% to the number of files in FileSet. A simpler
% implementation can be used here that returns a 1.0 when all
% the data has been read from the datastore, and 0.0
% otherwise.
%
% See also matlab.io.Datastore, read, hasdata, reset, readall,
% preview.
frac = progress(myds.Files);
end
end
methods(Access = protected)
function dsCopy = copyElement(myds)
%COPYELEMENT Create a deep copy of the datastore
% Create a deep copy of the datastore. We need to call
% copy on the datastore's property FileSet because it is
% a handle object. Creating a deep copy allows methods
% such as readall and preview, which call the copy method,
% to remain stateless.
dsCopy = copyElement@matlab.mixin.Copyable(myds);
dsCopy.Files = copy(myds.Files);
end
function tf = write(myds, data, writeInfo, outFmt, varargin)
if outFmt == "dcm"
dicomwrite(data, writeInfo.SuggestedOutputName, varargin{:});
else
write@matlab.io.datastore.FileWritable(myds, data, ...
writeInfo, outFmt, varargin{:});
end
tf = true;
end
function files = getFiles(myds)
files = myds.Files.FileInfo.Filename;
end
end
end
Using the DICOMDatastore
Class
After you implement the DICOMDatastore
class, you can use the constructor to create a new DICOMDatastore
object that references the location of a set of DICOM files. For example, if you have DICOM files in the folderC:\Data\DICOM\series-000001\
:
ds = DICOMDatastore("C:\Data\DICOM\series-000001")
ds =
DICOMDatastore with properties:
Files: [1×1 matlab.io.datastore.FileSet]
SupportedOutputFormats: ["png" "jpg" "jpeg" "tif" "tiff" "dcm"]
DefaultOutputFormat: "dcm"
Folders: {'C:\Data\DICOM\series-000001'}
Class users of DICOMDatastore
have access to these public methods:
Methods for class DICOMDatastore:
DICOMDatastore copy isPartitionable preview read reset writeall
combine hasdata isShuffleable progress readall transform
Methods of DICOMDatastore inherited from handle.
In particular, with support for writeall
, you can write the files to a new location:
writeall(ds,"C:\Data2\DICOM")
This command creates copies of the datastore files in the folderC:\Data2\DICOM\series-000001
.
For general information on authoring classes in MATLAB®, see Classes.
See Also
matlab.io.Datastore | matlab.io.datastore.FileWritable | matlab.io.datastore.FoldersPropertyProvider