classdef sydneyUrbanObjectsClassificationDatastore < matlab.io.Datastore
    % sydneyUrbanObjectsClassificationDatastore Datastore with point clouds
    % and associated categorical labels for Sydney Urban Objects dataset.
    %
    % ds = sydneyUrbanObjectsDatastore(datapath) constructs a datastore
    % that represents point clouds and associated categories for the Sydney
    % Urban Objects dataset. The input, datapath, is a string or char array
    % which represents the path to the root directory of the Sydney Urban
    % Objects Dataset.
    %
    % ds = sydneyUrbanObjectsDatastore(___,folds) optionally allows
    % specification of desired folds that you wish to be included in the
    % output ds. For example, [1 2 4] specifies that you want the first,
    % second, and fourth folds of the Dataset. Default: [1 2 3 4].
     
    % Copyright 2019 The MathWorks, Inc.
    properties
        MiniBatchSize = 1;
    end
    
    properties(Dependent)
        Files
    end
    
    properties(Access = private)
        FileDatastore
    end
    
    methods
        function this = sydneyUrbanObjectsClassificationDatastore(datapath,folds)
           
            if nargin == 0
                return;
            end
            
            if nargin < 2
                folds = 1:4;
            end
            
            datapath = string(datapath);
            path = fullfile(datapath,'objects',filesep);
            
            % Add folds to datastore.
            foldNames{1} = importdata(fullfile(datapath,'folds','fold0.txt'));
            foldNames{2} = importdata(fullfile(datapath,'folds','fold1.txt'));
            foldNames{3} = importdata(fullfile(datapath,'folds','fold2.txt'));
            foldNames{4} = importdata(fullfile(datapath,'folds','fold3.txt'));
            names = foldNames(folds);
            names = vertcat(names{:});
            
            fullFilenames = append(path,names);
            this.FileDatastore = fileDatastore(fullFilenames,'ReadFcn',@extractTrainingData,'FileExtensions','.bin');
        end
        
        function tf = hasdata(this)
            tf = hasdata(this.FileDatastore);
        end
        
        function [data,info] = read(this)
            
            if ~hasdata(this)
                error('Reached end of data. Reset datastore.');
            end

            % Preallocate output.
            batchSize = this.MiniBatchSize;
            data = cell(batchSize,2);
            info = struct(...
                'Filename',cell(batchSize,1),...
                'FileSize',cell(batchSize,1));
            
            % Read mini-batch size worth of data. The size of data can be
            % less than the specified batch size.
            idx = 0;
            while hasdata(this.FileDatastore)
                idx = idx + 1;
                [data(idx,:),info(idx)] = read(this.FileDatastore);
                if idx == batchSize
                    break;
                end
            end  
            
            data = data(1:idx,:);
            info = info(1:idx);
            
        end
        
        function reset(this)
            reset(this.FileDatastore);
        end
        
        function files = get.Files(this)
            files = this.FileDatastore.Files;
        end
        
        function set.Files(this,files)
            this.FileDatastore.Files = files;
        end
    end
    
    methods(Access=protected)
        function newds = copyElement(this)
            newds = sydneyUrbanObjectsClassificationDatastore();
            newds.FileDatastore = copy(this.FileDatastore);
            newds.MiniBatchSize = this.MiniBatchSize;
        end
    end
   
end

function dataOut = extractTrainingData(fname)

[pointData,intensity] = readbin(fname);

[~,name] = fileparts(fname);
name = string(name);
name = extractBefore(name,'.');
name = replace(name,'_',' ');

labelNames = ["4wd","building","bus","car","pedestrian","pillar",...
    "pole","traffic lights","traffic sign","tree","truck","trunk","ute","van"];

label = categorical(name,labelNames);

dataOut = {pointCloud(pointData,'Intensity',intensity),label};

end

function [pointData,intensity] = readbin(fname)
% readbin Read point and intensity data from Sydney Urban Object binary
% files.

% names = ['t','intensity','id',...
%          'x','y','z',...
%          'azimuth','range','pid']
% 
% formats = ['int64', 'uint8', 'uint8',...
%            'float32', 'float32', 'float32',...
%            'float32', 'float32', 'int32']

fid = fopen(fname, 'r');
c = onCleanup(@() fclose(fid));

fseek(fid,10,-1); % Move to the first X point location 10 bytes from beginning
X = fread(fid,inf,'single',30);
fseek(fid,14,-1);
Y = fread(fid,inf,'single',30);
fseek(fid,18,-1);
Z = fread(fid,inf,'single',30);

fseek(fid,8,-1);
intensity = fread(fid,inf,'uint8',33);

pointData = [X,Y,Z];
end