Main Content

Develop Custom Datastore for DICOM Data

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 a Folders 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 Header

DICOMDatastore 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 Properties

DICOMDatastore defines a public Files 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 defines SupportedOutputFormats and DefaultOutputFormat 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 Methods

The public methods section defines common datastore methods that the class uses to manipulate data. Public methods are externally accessible, so class users of DICOMDatastore can call these methods (in addition to other public methods inherited from the superclasses).

The constructor DICOMDatastore creates a new DICOMDatastore object by setting values for the Files and Folders properties.

  • Use FileSet to set the value of the Files property.

  • Use the populateFoldersFromLocation method of FoldersPropertyProvider to set the value of the Folders 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 Methods

Protected methods redefine methods that were inherited by the class, and they are only accessible to DICOMDatastore. For more information, see Modify Inherited Methods.

The protected copyElement 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 supports ImageDatastore 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 uses FileSet objects for the Files property. The Files property is generally required to return a cellstr, so the getFiles method uses the FileSet object to generate a cellstr of the file paths.

end

End the classdef section.

 Expand for Class Code

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 folder C:\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(ds)
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 folder C:\Data2\DICOM\series-000001.

For general information on authoring classes in MATLAB®, see Classes.

See Also

| |

Related Topics