Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] add basic filename, path and derivatives JSON creation #203

Merged
merged 17 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 1 addition & 34 deletions +bids/+internal/append_to_layout.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
% Then reparse the file using the entity-label pairs defined in the schema.
p = bids.internal.parse_filename(file);

idx = find_suffix_group(modality, p.suffix, schema);
idx = bids.schema.find_suffix_group(modality, p.suffix, schema);

if ~isempty(schema)

Expand Down Expand Up @@ -62,36 +62,3 @@
end

end

function idx = find_suffix_group(modality, suffix, schema)

idx = [];

if isempty(schema)
return
end

% the following loop could probably be improved with some cellfun magic
% cellfun(@(x, y) any(strcmp(x,y)), {p.type}, suffix_groups)
for i = 1:size(schema.datatypes.(modality), 1)

this_suffix_group = schema.datatypes.(modality)(i);

% for CI
if iscell(this_suffix_group)
this_suffix_group = this_suffix_group{1};
end

if any(strcmp(suffix, this_suffix_group.suffixes))
idx = i;
break
end

end

if isempty(idx)
warning('findSuffix:noMatchingSuffix', ...
'No corresponding suffix in schema for %s for datatype %s', suffix, modality);
end

end
28 changes: 28 additions & 0 deletions +bids/+internal/camel_case.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
function str = camel_case(str)
%
% Removes non alphanumeric characters and uppercase first letter of all
% words but the first
%
% USAGE::
%
% str = camel_case(str)
%
% :param str:
% :type str: string
%
% :returns:
% :str: (string) returns the input with an upper case for first letter
% for all words but the first one (``camelCase``) and
% removes invalid characters (like spaces).
%
%

% camel case: upper case for first letter for all words but the first one
spaceIdx = regexp(str, '[a-zA-Z0-9]*', 'start');
str(spaceIdx(2:end)) = upper(str(spaceIdx(2:end)));

% remove invalid characters
[unvalidCharacters] = regexp(str, '[^a-zA-Z0-9]');
str(unvalidCharacters) = [];

end
17 changes: 12 additions & 5 deletions +bids/+internal/parse_filename.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,27 @@
% identidy an eventual prefix to the file
% and amends the sub entity accordingly
p.prefix = '';

if strfind(parts{1}, 'sub-')

tmp = regexp(parts{1}, '(sub-)', 'split');
p.prefix = tmp{1};

if ~isempty(p.prefix)
entities = fieldnames(p.entities);

p.entities.sub = p.entities.([p.prefix 'sub']);
p.entities = rmfield(p.entities, [p.prefix 'sub']);
% reorder entities to make sure that sub is the first one
entities{1} = 'sub';
p.entities = orderfields(p.entities, entities);

% reorder entities so that the 'sub' entity stays on top
entity_order = fieldnames(p.entities);
entity_order = entity_order([end, 1:end - 1]);
p.entities = orderfields(p.entities, entity_order);

end

end

% -Extra fields can be added to the structure and ordered specifically.
% Extra fields can be added to the structure and ordered specifically.
if nargin == 2
for i = 1:numel(fields)
p.entities = bids.internal.add_missing_field(p.entities, fields{i});
Expand Down
33 changes: 33 additions & 0 deletions +bids/+schema/find_suffix_datatypes.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function datatypes = find_suffix_datatypes(suffix, schema)
%
% For a given suffix, returns all the possible datatypes that have this
% suffix.
%
%
% (C) Copyright 2021 BIDS-MATLAB developers

datatypes = {};

if isempty(schema)
return
end

datatypes_list = fieldnames(schema.datatypes);

for i = 1:size(datatypes_list, 1)

this_datatype = schema.datatypes.(datatypes_list{i});
% for CI
if iscell(this_datatype)
this_datatype = this_datatype{1};
end

suffix_list = cat(1, this_datatype.suffixes);

if any(ismember(suffix_list, suffix))
datatypes{end + 1} = datatypes_list{i};
end

end

end
42 changes: 42 additions & 0 deletions +bids/+schema/find_suffix_group.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
function idx = find_suffix_group(modality, suffix, schema, quiet)
%
% For a given sufffix and modality, this returns the "suffix group" this
% suffix belongs to
%
%
% (C) Copyright 2021 BIDS-MATLAB developers

idx = [];

if nargin < 4 || isempty(quiet)
quiet = true;
end

if isempty(schema)
return
end

% the following loop could probably be improved with some cellfun magic
% cellfun(@(x, y) any(strcmp(x,y)), {p.type}, suffix_groups)
for i = 1:size(schema.datatypes.(modality), 1)

this_suffix_group = schema.datatypes.(modality)(i);

% for CI
if iscell(this_suffix_group)
this_suffix_group = this_suffix_group{1};
end

if any(strcmp(suffix, this_suffix_group.suffixes))
idx = i;
break
end

end

if isempty(idx) && ~quiet
warning('findSuffix:noMatchingSuffix', ...
'No corresponding suffix in schema for %s for datatype %s', suffix, modality);
end

end
52 changes: 52 additions & 0 deletions +bids/+schema/return_entities_for_suffix.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
function [entities, is_required] = return_entities_for_suffix(suffix, schema, quiet)
%
% returns the list of entities for a given suffix
%
% (C) Copyright 2021 BIDS-MATLAB developers

modalities = bids.schema.return_modality_groups(schema);

for iModality = 1:numel(modalities)

datatypes = schema.modalities.(modalities{iModality}).datatypes;

for iDatatype = 1:numel(datatypes)

idx = bids.schema.find_suffix_group(datatypes{iDatatype}, suffix, schema, quiet);
if ~isempty(idx)
this_datatype = datatypes{iDatatype};
this_suffix_group = schema.datatypes.(this_datatype)(idx);
break
end

end

if ~isempty(idx)
is_required = check_if_required(this_suffix_group);
entities = bids.schema.return_modality_entities(this_suffix_group, schema);
break
end

end

end

function is_required = check_if_required(this_suffix_group)

% for CI
if iscell(this_suffix_group)
this_suffix_group = this_suffix_group{1};
end

entities = fieldnames(this_suffix_group.entities);
nb_entities = numel(entities);

is_required = false(1, nb_entities);

for i = 1:nb_entities
if strcmpi(this_suffix_group.entities.(entities{i}), 'required')
is_required(i) = true;
end
end

end
153 changes: 153 additions & 0 deletions +bids/create_filename.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
function [filename, pth, json] = create_filename(p, file)
%
% Creates a BIDS compatible filename and can be used to create new names to rename files
%
% USAGE::
%
% [filename, pth, json] = bids.create_filename(p)
%
% :param p: specification of the filename to create, very similar to the output of
% ``bids.internal.parse_filename``
% :type p: structure
%
% Content of ``p``:
%
% - ``p.suffix`` - required
% - ``p.ext`` - extension (default: ``p.ext = ''``)
% - ``p.entities`` - structure listing the entity-label pairs to compose the filename
% - ``p.prefix`` - prefex to prepend (default: ``p.prefix = ''``)
% - ``p.use_schema`` - bollean to check required entities for a given suffix,
% and reorder entities according to the BIDS schema.
% - ``p.entity_order`` - user specified order in which to arranges the entities
% in the filename. Overrides ``p.use_schema``.
%
% If no entity order is specified and the filename creation is not based on the BIDS
% schema, then the filename will be created by concatenating the entity-label pairs
% found in the content of ``p.entities``.
%
% USAGE::
%
% [filename, pth, json] = bids.create_filename(p, file)
%
% :param file: file whose name has to be modified by the content of ``p``.
% :type file: string
%
%
% (C) Copyright 2021 BIDS-MATLAB developers

default.use_schema = true;
default.entity_order = {};
default.ext = '';
p = bids.internal.match_structure_fields(p, default);

if nargin > 1
p = rename_file(p, file);
end

if ~isfield(p, 'suffix')
error('We need at least a suffix to create a filename.');
end

default.prefix = '';
p = bids.internal.match_structure_fields(p, default);

entities = fieldnames(p.entities);

[p, entities, is_required] = reorder_entities(p, entities);

filename = '';
for iEntity = 1:numel(entities)

thisEntity = entities{iEntity};

if is_required(iEntity) && ...
(~isfield(p.entities, thisEntity) || isempty(p.entities.(thisEntity)))
errorStruct.identifier = 'bidsMatlab:requiredEntity';
errorStruct.message = sprintf('The entity %s cannot not be empty for the suffix %s', ...
thisEntity, ...
p.suffix);
error(errorStruct);
end

if isfield(p.entities, thisEntity) && ~isempty(p.entities.(thisEntity))
thisLabel = bids.internal.camel_case(p.entities.(thisEntity));
filename = [filename '_' thisEntity '-' thisLabel]; %#ok<AGROW>
end

end

% remove lead '_'
filename(1) = [];

filename = [p.prefix, filename '_', p.suffix, p.ext];

pth = bids.create_path(filename);

json = bids.derivatives_json(filename);

end

function parsed_file = rename_file(p, file)

parsed_file = bids.internal.parse_filename(file);

parsed_file.entity_order = p.entity_order;
parsed_file.use_schema = p.use_schema;
if isfield(p, 'prefix')
parsed_file.prefix = p.prefix;
end

entities_to_change = fieldnames(p.entities);

for iEntity = 1:numel(entities_to_change)
parsed_file.entities.(entities_to_change{iEntity}) = p.entities.(entities_to_change{iEntity});
end

end

function [p, entities, is_required] = reorder_entities(p, entities)
%
% reorder entities by one of the following ways
% - user defined: p.entity_order
% - schema based: p.use_schema
% - order defined by entities order in p.entities
%

if ~isempty(p.entity_order)

if size(p.entity_order, 2) > 1
p.entity_order = p.entity_order';
end

idx = ismember(entities, p.entity_order);
entities = cat(1, p.entity_order, entities(~idx));
is_required = false(size(entities));

elseif p.use_schema

quiet = true;

[p, is_required] = get_entity_order_from_schema(p, quiet);

idx = ismember(entities, p.entity_order);
entities = cat(1, p.entity_order, entities(~idx));

else

idx = ismember(entities, p.entity_order);
entities = cat(1, p.entity_order, entities(~idx));
is_required = false(size(entities));

end

end

function [p, is_required] = get_entity_order_from_schema(p, quiet)

schema = bids.schema.load_schema(p.use_schema);
[schema_entities, is_required] = bids.schema.return_entities_for_suffix(p.suffix, schema, quiet);
for i = 1:numel(schema_entities)
p.entity_order{i, 1} = schema_entities{i};
end

end
Loading