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

Naming conventions/design of new library #5

Closed
coalsont opened this issue Mar 16, 2020 · 50 comments
Closed

Naming conventions/design of new library #5

coalsont opened this issue Mar 16, 2020 · 50 comments

Comments

@coalsont
Copy link
Member

coalsont commented Mar 16, 2020

Given the rewrite of the library, this issue is to gather input on naming conventions, in the hopes of improving things before we declare the new library ready for users.

I see a likely source of confusion in cifti_dense_get_surface_mapping(), in that the string "surface mapping" may make users think it refers to "volume to surface mapping", which is a very different thing (resampling, and not merely a file format detail). Anyone have some ideas? A synonym like "correspondence" could work, but that is longer and more clunky, and I am drawing a blank on other good synonyms. Another possibility is to shorten them to "map" instead of "mapping" (cifti_dense_get_vol_all_map(), etc).

It would probably be good to standardize to using only one of vol or volume in the helper names, but I'm not sure whether clarity or brevity should be favored here.

If you have any other comments on the new library other than bugs, you can post them here, too (bugs deserve new issues).

@coalsont
Copy link
Member Author

Another possible source of confusion: cifti_dense_extract_surface actually gives the data values for vertices, while its name may appear to imply coordinates and topology (which do not exist in the cifti file, a point people are often already confused about). I don't have amazing ideas here, either, maybe cifti_dense_extract_surfdata.

coalsont added a commit that referenced this issue Mar 27, 2020
@coalsont
Copy link
Member Author

I have used the following conventions for now (removing surface, volume, struct, and mapping):

surf, extract_surfdata, replace_surfdata
vol, extract_voldata, replace_voldata
structure, map

@coalsont
Copy link
Member Author

coalsont commented May 14, 2020

Another possible issue: cifti_dense_extract_* operate on a file structure, but cifti_dense_get_* operate on a dense diminfo. This suggests the need for different keywords for "dense file" and "dense map". Some possibilities (any single one of the below would resolve the ambiguity):

  • cifti_diminfo_dense_get_* - matches other diminfo helpers
  • cifti_file_dense_extract_* - all file-level (simpler to use) helpers would get longer names
  • cifti_denseinfo_get_* - coins a new term, may not be immediately obvious
  • cifti_dfile_extract_* - also coins a new term, not obvious, may cause jokes

@glasserm
Copy link

  1. What about get_surface_vertices?
  2. What about extract_surface_data?
  3. What about "dense_file" and "dense_dim"?

@coalsont
Copy link
Member Author

1. What about get_surface_vertices?

As in cifti_dense_get_surface_vertices instead of cifti_dense_get_surf_map? The info returned from the function is not just the vertices, it is also the cifti indices of them (and the surface resolution).

Side note, they could be returned as a struct instead, to reduce the number of variable names the user has to come up with (with a slight performance penalty in at least older matlab).

2. What about extract_surface_data?

That is a bit longer (and implies lengthening other function names), but it could be clearer. I'm not really sure where the preferred length vs clarity balance is.

3. What about "dense_file" and "dense_dim"?

diminfo is already used as the first name component after cifti for other things that take a diminfo struct, so if using an additional name component, I prefer cifti_diminfo_dense to fit the pattern (left to right, the organization is least specific to most specific until it reaches the "function name" part - basically I am trying to do what would otherwise be object oriented namespacing, but with global functions, as it was expressed that object oriented matlab code is not desirable).

I could make all the file-level helpers start with cifti_file in addition to that, if length is less of a priority it may make it easier to remember which to use.

@glasserm
Copy link

glasserm commented Jul 2, 2020

Probably clarity is more important than length. Returning as a struct seems fine. I do think we should move towards releasing this as it is a clear improvement.

@coalsont
Copy link
Member Author

coalsont commented Aug 5, 2020

Just to be clear, some examples of my take on your suggestion:

    %function outinfo = cifti_diminfo_dense_get_surface_info(diminfo, structure)
    %   Get information on how to map cifti indices to gifti surface vertices.
    %
    %   >> leftinfo = cifti_diminfo_dense_get_surface_info(cifti.diminfo{1}, 'CORTEX_LEFT');
    %   >> extracted = zeros(leftinfo.numverts, 1, 'single');
    %   >> extracted(leftinfo.vertlist1) = cifti.cdata(leftinfo.ciftilist, 1);

cifti_dense_extract_voldata_structure becomes cifti_file_dense_extract_volume_structure_data, and similar for surface and replace functions (the surface functions will likely be semi-frequently used)
cifti_create_dtseries_from_template becomes cifti_file_create_dtseries_from_template (may be heavily used, as it has more control than the ciftisavereset compatibility function)

Note that *_from_template can be cleaner than setting .cdata and using ciftisavereset:

write_cifti(cifti_file_create_dscalar_from_template(othercii, newdata), [outpath '/foo.dscalar.nii']);

This doesn't create a new variable, or overwrite the original data in othercii (also, it should work even if you have done othercii.cdata = []; to deallocate the input data when you don't need it any more), and may be better on memory usage in some cases.

@glasserm
Copy link

glasserm commented Aug 5, 2020

Ok

@coalsont
Copy link
Member Author

Due to the behavior of for i = colvec not looping over the vector, I am also changing all vectors to be row vectors (and voxel lists to loop over voxels, not the ijk dimension), despite the display of very long row vectors not being as nice as for column vectors. I'll rename the functions another day, and then I plan to post the function signatures of all of them here to make it easy to look them over.

@coalsont
Copy link
Member Author

The current complete list of public function signatures, annotated with ... for varargin (which are name/value pairs except for ciftiopen, etc. compatibility functions), and [] for optional:

read/write and compatibility

outstruct = read_cifti(filename, ...)
write_cifti(cifti, filename, ...)

cifti = ciftiopen(filename, ...)
ciftisave(cifti, filename, ...)
ciftisavereset(cifti, filename, ...)

dense file extract/replace helpers

[outdata, outroi] = cifti_file_dense_extract_surface_data(cifti, structure[, dimension])
cifti = cifti_file_dense_replace_surface_data(cifti, data, structure[, dimension])

[outdata, outsform1, outroi] = cifti_file_dense_extract_volume_all_data(cifti[, cropped, dimension])
cifti = cifti_file_dense_replace_volume_all_data(cifti, data[, cropped, dimension])

[outdata, outsform1, outroi] = cifti_file_dense_extract_volume_structure_data(cifti, structure[, cropped, dimension])
cifti = cifti_file_dense_replace_volume_structure_data(cifti, data, structure[, cropped, dimension])

file create helpers

cifti = cifti_file_create_sdseries(data, ...)
cifti = cifti_file_create_dscalar_from_template(ciftitemplate, data, ...)
cifti = cifti_file_create_dtseries_from_template(ciftitemplate, data, ...)
cifti = cifti_file_create_dconn_from_template(ciftitemplate, data, ...)
cifti = cifti_file_create_pconn_from_template(ciftitemplate, data, ...)
cifti = cifti_file_create_pscalar_from_template(ciftitemplate, data, ...)
cifti = cifti_file_create_ptseries_from_template(ciftitemplate, data, ...)

diminfo helpers

[surflist, vollist] = cifti_diminfo_dense_get_structures(diminfo)         %returns the names of structures that exist in this diminfo

outinfo = cifti_diminfo_dense_get_surface_info(diminfo, structure)
outinfo = cifti_diminfo_dense_get_volume_all_info(diminfo[, cropped])
outinfo = cifti_diminfo_dense_get_volume_structure_info(diminfo, structure[, cropped])

outmap = cifti_diminfo_make_scalars(nummaps[, namelist, metadatalist])
outmap = cifti_diminfo_make_series(nummaps[, start, step, unit])

outlength = cifti_diminfo_length(diminfo)         %maybe doesn't need to be public, all diminfo types have a .length field, this function recomputes length from the other contents of the diminfo

misc

outstring = cifti_metadata_get(metadata, key)         %returns empty string for nonexistent key
metadata = cifti_metadata_remove(metadata, key)         %returns unmodified struct for nonexistent key
metadata = cifti_metadata_set(metadata, key, value)         %overwrites key if it exists

indices = cifti_vox2ind(dims, voxlist1)         %helper to act like sub2ind for voxel ijk lists

@glasserm
Copy link

These look good to me

@coalsont
Copy link
Member Author

Do we expect anyone to be confused by cifti_file_create_* just making a matlab struct, and not actually a file on the filesystem? I don't currently have an idea for a better word than "file" here (not sure if simply dropping "file" would be more clear).

@glasserm
Copy link

object instead of file?

@coalsont
Copy link
Member Author

"object" is sort of a compsci jargon term, and is almost equivalent to the word "thing", so it is more of an obfuscation than an explanation. If a name has to be long, it is better if each part of the name conveys some useful information.

@adeyemob
Copy link

adeyemob commented Aug 15, 2020

Its always a balance but I'm more for clarity of the function name than file length so long as things are consistent. So if I'm understanding this discussion thread correctly, the prefix "cifti_file_" indicates a function operates on a cifti-file correct? If that's the case then as long as its made clear in the documentation then its fine. I'd also change things like this:

cifti = cifti_file_dense_replace_surface_data(cifti, data, structure[, dimension])

to something like more like:

cifti = cifti_file_dense_replace_surface_data(CIFTI-FILE, data, structure[, dimension])

to indicate the difference between a "cifti" variable in matlab as opposed to a cifti-file that's being operated on.

@glasserm
Copy link

That is fine too.

@coalsont
Copy link
Member Author

Actually, the cifti_file_* functions operate on a variable, not a file. The only things that touch the filesystem are read/write and open/save/savereset.

@coalsont
Copy link
Member Author

Basically, the difficulty in terminology is that read_cifti returns a struct that represents the entire cifti file's contents (basically, an in-memory temporary copy of what was on disk), and the idea is to categorize the helpers that work on this "entire file" type of struct, versus other helpers that instead require that you select a specific part of the struct (a diminfo element) before calling them.

@coalsont
Copy link
Member Author

coalsont commented Aug 17, 2020

A previous solution I had hinted at would be to only keep a specifier on the diminfo functions, and have the "whole file struct" functions named cifti_dense_extract_surface_data and similar, which would avoid confusion about the word "file", and shorten the names. However, it also wouldn't obviously group the "whole file struct" functions by their names.

@mharms
Copy link

mharms commented Aug 17, 2020

Since you asked for comments on the function naming ...
Coming to this completely naive as to what to expect, I'll say that I'm rather intimidated by the very lengthy function names (not at all typical for matlab). And if the intent is that the function names convey certain intuitive information about their role/purpose, I'm not perceiving the convention. Perhaps this could be handled via some sort of "Function naming introduction" in the README?

For example, what is the role of the file, diminfo, and dense portions of the function names?

Perhaps you already considered this, but could all the create functions be consolidated into a single function in which you simply specify the data type as an argument? Similarly, could the various diminfo and metadata functions be consolidated? (The idea would be to have a much smaller set of total functions in the library, but with the individual functions being more "powerful").

@coalsont
Copy link
Member Author

At present, dense means it does something with a brainordinates dimension (dscalar has always stood for "dense [by] scalar"), file means it deals directly with the "entire file" struct, and diminfo means it deals with a single element of the .diminfo field of the "entire file" struct.

The create functions mainly allow different extra inputs to set the extra info in a mapping (scalar can take a list of map names, series can take start and step values and a unit specifier). I could make a cifti_file_create function that takes a string to decide which of the other functions to call and passes on the varargs, and put the other functions in private. The help info for that function would be somewhat long, but could avoid some redundancy between pscalar vs dscalar, etc. I hadn't considered that previously. sdseries is the odd one out here, though, as all the others require an existing cifti struct to use as a template, so perhaps it should remain separate.

@mharms
Copy link

mharms commented Aug 17, 2020

FWIW, I don't find the "file" aspect of the function names all that helpful (which I see was only added by the latest commit). For that matter, I personally think you could probably drop the "diminfo" and "dense" portions of the names as well to actually simplify things. Then you basically have cifti_create_*, cifti_get_*, cifti_extract_*, and cifti_replace_* which are all reasonably intuitive as function names (except what is the difference between "get" and "extract"?).

@coalsont
Copy link
Member Author

coalsont commented Aug 17, 2020

get_*_info gives you the indexing to map between cifti indices and voxels or vertices (and must be called on a .diminfo element), extract and replace give you the extracted data or modify the data in the "entire file" struct (and must be called on the "entire file" struct). To put it concretely:

>> testscalar

testscalar = 

    metadata: [1x4 struct]
     diminfo: {[1x1 struct]  [1x1 struct]}
       cdata: [60951x2 single]

>> size(cifti_file_dense_extract_surface_data(testscalar, 'CORTEX_LEFT'))

ans =

       32492           2

>> cifti_diminfo_dense_get_surface_info(testscalar.diminfo{1}, 'CORTEX_LEFT')

ans = 

     numverts: 32492
    vertlist1: [1x30424 double]
    ciftilist: [1x30424 double]

I think at least diminfo should stay, and probably also dense, because the diminfo helpers will probably be used less frequently (and it is important to know which functions operate on the full structure versus a part of it), and without dense it may not be clear that get_*_info, replace, and extract are only usable on things that have a dense dimension (so, not sdseries or pconn, etc). I would be fine with taking file back out, as it adds length to the more commonly used helpers, and a possibility of confusion.

All the file_create helpers except sdseries can be replaced with this, and with the implementation directly in that file instead of being a wrapper for the other functions, there is less redundant code, and therefore less maintenance burden:

cifti = cifti_file_create_from_template(ciftitemplate, data, type, ...)

where type is one of 'dscalar', 'pconn', etc.

@coalsont
Copy link
Member Author

As for the metadata functions, get returns a string, while the other two return a modified metadata struct array - changing the returned type based on an argument's value isn't my first design choice - however, I am influenced by mostly working in compiled languages.

The different diminfo helpers are a bit more diverse - dense_get_structures returns an array of strings, while get_*_info return structures containing indexing info - surface and volume_structure could be combined the way I did *_from_template (volume_all has one less mandatory argument, making that less clean), and the replace/extract could also be combined somewhat (though that would result in the output arguments changing in number and type (4D array vs 2D), which I'm not that fond of). diminfo_make being only implemented for scalar and series, using entirely different optional arguments, to me suggests not combining them. Worse, if/when diminfo_make is implemented for dense, parcel, and label, it will take entirely different mandatory arguments, and that would get messy.

To some extent, the "lots of functions" issue is that they can't easily be in a separate folder while still needing only one addpath() to use them. Maybe classes could be abused to do this, but I don't know if it is a good idea to have the helpers and IO functions at a different level of visibility.

@mharms
Copy link

mharms commented Aug 18, 2020

What about making the cifti_diminfo functions accept either the full struct or struct.diminfo as inputs (a little bit of internal logic would easily distinguish the two)? Then it seems that the need for diminfo in the file names becomes moot (and you get the additional benefit that the functions become more flexible in terms of allowed inputs).

I'm still not convinced that dense in the file names is worth the complexity (although I don't have the full picture on how the library may get additional functionality over time). Probably 90+% of usage will be with "dense" structures, esp. if plabel, ptseries and pscalar fall into the dense category. (Do they? If not, it superficially at least seems that a set of tools for working with parcellated file types is missing). In which case, you could simply print an informative message if someone tries using with an unsupported type such as sdseries or pconn.

If some functions are clearly helpers that could be "hidden" in a sub-folder, one way to accomplish that would be to have the main function issue the necessary addpath to the sub-folder.

@coalsont
Copy link
Member Author

coalsont commented Aug 18, 2020

Parcels do not qualify as dense, no, they have entirely different internals and behavior. I'm not sure what additional helpers are likely to be added in the future, so my intuition is to have a specifier in the name to prevent future collision of desired function names. There are not currently helpers for extracting information about parcels, as unlike the common case of expanding dense data back out to full-surface or standard volume format, parcellated data is generally dealt with as-is (and the parcel info is available in the diminfo element as a struct array with one element per parcel that contains lists of vertices and voxels, though those are 0-indexed currently).

For the diminfo functions to operate on an "entire file" struct, they would also require a dimension to be used on (virtually all cifti files have exactly 2 diminfo elements). Some of the functions, though, simply create a diminfo element, and returning that as-is seems much simpler than also trying to support passing in a cifti object and a dimension, and the user storing that result either over the original cifti object or to a new variable.

Currently, the main reasons for users to use diminfo functions are for exceptional cases, such as asymmetric dconn (or dpconn/pdconn) or cifti with 3 dimensions. The file-level helpers use the diminfo helpers internally.

To put it another way, the diminfo helpers are more low-level - they can handle any case of oddball cifti file because they only operate on one dimension at a time, and it is up to the user to write the glue code to deal with the data matrix, and the user has to know which dimension is which, and what the mapping types need to be for dtseries, dscalar, etc. The file-level helpers instead take whatever cifti you started with, plus your new data matrix, and figure out the rest for you without you needing to know what dimension is what, or the details of dtseries or dscalar (unless the original cifti is weird or what you asked it to do could be ambiguous).

@coalsont
Copy link
Member Author

coalsont commented Aug 18, 2020

I'd rather not have the library do an addpath, especially if the purpose is to hide something that the user may need to use. Not only would this complicate compiling matlab, if the user saved a cifti struct to a matlab file, loaded it in a new instance, and tried to use one of the hidden lower-level helpers, it would fail because it hasn't run any non-hidden function in the library, and therefore can't have done the addpath to find the hidden ones.

For things the user definitely doesn't need to call, the "private" folder is a matlab special convention for this purpose that does not require any addpath or any other trickery, which is already used for the IO and parsing code.

@mharms
Copy link

mharms commented Aug 18, 2020

That's helpful perspective, although I still think that consolidating the various primarily "helper" functions into a smaller set of "super" functions would simplify the overall library considerably. e.g., for the diminfo issue, you could default to using a given dimension of the full struct, and have an optional dim=? argument that provides an option for explicit control over the dimension to use if the full struct was provided as input. Similarly, it seems like there could perhaps be a single cifti_extract and cifti_replace function in which you specify both the data type as an argument, and what you specifically want to extract/replace as an arg as well. Such an organization is infinitely extendable to add new supported data types and new elements to operate on/return, without adding more functions (with complicated naming) to the library itself.

Related to this consolidation theme, it seems you could perhaps at least consolidate *volume_all_data and *volume_structure_data, by having an optional structure argument, which if missing means you simply operate on all structures? [Or, if you don't want to deal with the more complicated arg parsing that entails, just use "ALL" as the required structure entry for the all structures case].

That said, easy for me to suggest all this, as I'm not the one that would be doing the additional dev work and testing. I do think that the current set of proposed functions and their naming is still going to be "intimidating", but this can in principle be managed by a good README.

I think I'm about at the limit of what I can meaningfully comment on at a big picture level.

@mharms
Copy link

mharms commented Aug 18, 2020

In terms of possibly some more granular input (e.g., on the organization of specific functions), is there an easy way to "comment" on lines in the code (outside of a change to those lines as part of an existing PR)?
e.g.,

  • Typical convention in matlab usage statements is to put the variable names/arguments in ALL CAPS when referring to them in the descriptions/help text, to make it obvious that you are referring to an argument.

  • In the *replace* functions, it might be clearer if the help statement used newdata rather than data.

  • Maybe add a blank line in front and after the usage/help statement text, just to better set it off the help text from the actual code.

  • I'm not a fan of an entire function definition being indented by one level (such that essentially nothing other than the initial function and end (if present) are NOT indented). Perhaps that is the default of the particular editor that you are using? (The problem is that you get the impression that the code is part of some large scale sub-block, when it really isn't).

@mharms
Copy link

mharms commented Aug 18, 2020

Going back to my earlier comment (#5 (comment)), I see that you have a myargparse helper function that appears to basically already provide complete flexibility in specifying additional arguments in arbitrary order. (i.e., seems like it would be relatively straightforward to add a dim argument to the *_get_* functions if they were generalized to also operate on "entire file" structs).

@coalsont
Copy link
Member Author

coalsont commented Aug 18, 2020

It looks like it is possible for wb_command to use "ALL" as the identifier for a single structure (which isn't in the cifti spec, so I should check on that), but even if this weren't the case, using a "special value" to mean "this value isn't specified" can be dangerous, especially for strings.

Using advanced logic on the input arguments to make one function behave with 3 or more different behaviors with different mandatory arguments, outputs, or other behavior just doesn't seem like good coding style to me, in any language. I don't expect doing that to increase code reuse much (and reading/editing the code would probably get a lot harder), unlike your good suggestion for the *_from_template functions. In fact, extending such a "super-function" further in the future seems likely to be harder, not easier, than adding another function, because you would have to dodge all the previous logic of how to select the functionality in order to make sure you don't break backwards compatibility.

So, at the moment we have 2 votes to keep file in the names of the friendlier helpers (glasserm, adeyemob), and 2 to remove it (mharms, me). Anyone feel like breaking the tie?


I opened another issue for discussing the help info formatting, see #7. In the version I am using, matlab's editor doesn't have a "convert selection to caps" option, so I'd prefer to deal with that kind of detail after nailing down the function names. You can comment on lines of code on an existing file, I believe (or at least on lines in an existing commit), but that may be tied to a specific commit (so, easy to lose track of).

I deliberately chose to indent the function bodies, as to me it is clearer and more consistent, especially if you define sub-functions in the same file (see private/cifti_parse_xml.m). Every end decreases the indentation level, every function has end terminating it (and no other ends occur at the same indentation level, making it much more obvious if there is an "extra end" problem), no reliance on matlab's sloppy function coding conventions. Most editors will automatically indent for you following whatever convention exists in the current line, including matlab's.

@mharms
Copy link

mharms commented Aug 18, 2020

Regarding the indentation, your preference is not consistent with matlab's convention, so in that regard, while perhaps more logical to you, it isn't consistent with what others expect for matlab functions (and in that regard, I would argue makes the code less clear to others).

In my mind at least, the further consolidation that I was suggesting seems in keeping with typical matlab packages/libraries. The functions in this cifti library strike me as very granular. If that's the design choice, so be it (but the README will be important to orient users).

@coalsont
Copy link
Member Author

I have added a usage section to the readme, see what you think.

I disagree that a less-ambiguous coding convention (that makes it easier to spot/avoid errors) is "less clear". It may be unfamiliar to some who have not worked in other languages. Notably, python requires indentation to delineate entire blocks (instead of having something like end), so in python it is not even possible to write functions using the convention you are suggesting. I find indentation highly readable and error-preventing (I indent bash functions, too), so unless I get input from others saying the ambiguous convention is much better, I am not willing to change this.

@mharms
Copy link

mharms commented Aug 19, 2020

One other comment on the naming: Off the top of my head, I can't really think of other matlab functions/libraries in which the function names themselves attempt to encode info on the type of input (and certainly not to this degree of specificity). Usually, matlab functions are named by what they do, not what they operate on. Here I realize that we have both. Maybe if the info related to the input type was relegated to the end of the function names, so that the action came first...? e.g., cifti_<action>_<elementType>.

The file descriptor in the function names tells me nothing, and in fact is outright confusing.

@mharms
Copy link

mharms commented Aug 19, 2020

When in python, you expect the code to be indented per python's conventions/requirements. For better or worse, matlab has established its own convention (via their own code), and that is not to indent the root level of the function. (I see your point about sub-functions in the same file, but those are generally rare). The current indentation scheme is actually "ambiguous" to me, in that if the entire function doesn't fit on one screen, then per matlab convention, the fact that an entire screen of code is indented is going to send me back in the code searching for the controlling block. It becomes of a matter of which conventions do you adopt, because others will expect them, even if you don't agree with them personally...

@glasserm
Copy link

I agree with Mike, but don't see this as a big deal either way...

@coalsont
Copy link
Member Author

coalsont commented Aug 19, 2020

Putting classification-type terms first in the function name helps with completion (narrowing down the options without needing to remember the specific verb used for some action), and matlab's editor has tab completion support, though I haven't used it all that much. Of course, at present read_cifti and write_cifti break this convention, but they may be easy enough to remember.

I would agree that removing file from the names seems like a small net positive on the basis of confusion and length.

Full disclosure: I see enough design decisions in matlab's language and standard functions as questionable that I don't find "base matlab does it this way" to be very compelling on a design perspective. I view that mainly (or even solely) as a familiarity argument.

@coalsont
Copy link
Member Author

coalsont commented Aug 19, 2020

@glasserm:

I agree with Mike, but don't see this as a big deal either way...

You only meant about indentation, I assume?

@mharms
Copy link

mharms commented Aug 19, 2020

Since you raised it, why not cifti_read and cifti_write instead?

To be clear, I'm not taking a position on whether matlab's indentation convention is correct/optimal from a design perspective (that is a flame war sort of thing). Simply that there is a pretty strong convention, and thus I think there is indeed a good familiarity reason for matching that convention.

@coalsont
Copy link
Member Author

coalsont commented Aug 19, 2020

I would be fine with cifti_read and cifti_write, I think I named them after ft_write_cifti to begin with without much thought.

I am not inclined to lower my coding standards to that of the average matlab user based on your opinion. Please drop the indentation issue until someone else says it is important to them.

@mharms
Copy link

mharms commented Aug 19, 2020

It's clearly Mathworks own indentation standard, based on how they write their own code. That's why the "average user" writes their matlab functions in the same manner. They are simply emulating Mathworks own standard. With that, I'll drop the issue.

@mharms
Copy link

mharms commented Aug 19, 2020

I think the extended README is helpful. I think it would be helpful to additionally touch base on why "dense" is part of the function names, and why for example there aren't any "parc" (parcellated) analogs at this point in time.

@coalsont
Copy link
Member Author

coalsont commented Aug 19, 2020

Such an explanation seems like it has to end up being somewhat silly, here is a draft:

The dense part of some function names refers to only being applicable to "dense" files
(in cifti xml terms, having a "brain models" mapping), such as dtseries, dscalar, dlabel,
or dconn. There are more dense helpers mainly because there is a more common need to
make use of the information in a dense diminfo than most other diminfo types.

@mharms
Copy link

mharms commented Aug 19, 2020

It might seem somewhat silly to us, but I think it is a helpful addition for those trying to wrap their head around all this.

@coalsont
Copy link
Member Author

coalsont commented Aug 20, 2020

Another option for avoiding file in the function names would be to replace it with struct, and to consistently use "cifti struct" to refer to the "entire-file" struct in the rest of the documentation. This isn't much different from the suggestion of object, but object isn't as common a matlab concept/term (and probably wasn't used at all in the documentation), while most matlab users are likely to have seen matlab say struct at some point.

I realized a cifti_write_from_template function could simplify the cifti_write(cifti_file_create_from_template(...)) pattern (which removes some redundancy from the arguments), but that leaves out sdseries (because it doesn't need a template cifti struct) - should there also be a cifti_write_sdseries to shorten cifti_write(cifti_file_create_sdseries(...)), even though it wouldn't change any of the arguments?

@coalsont
Copy link
Member Author

I have renamed them to say cifti_struct. I also looked for anywhere I had used "object" and reworded things to refer to "struct" instead. cifti_write_sdseries has also been added.

@mharms
Copy link

mharms commented Aug 29, 2020

Personally, I find the file names too long (as mentioned before), but they do make sense once you understand the various "fields" within the function names.

@coalsont
Copy link
Member Author

coalsont commented Sep 2, 2020

For reference, a new listing of the current function signatures:

read/write and compatibility

outstruct = cifti_read(filename, ...)
cifti_write(cifti, filename, ...)

cifti = ciftiopen(filename, ...)     %note: these 3 do not use option pairs, the varargs here is to make passing 'wb_command' optional
ciftisave(cifti, filename, ...)
ciftisavereset(cifti, filename, ...)

dense struct extract/replace helpers

[outdata, outroi] = cifti_struct_dense_extract_surface_data(cifti, structure[, dimension])
cifti = cifti_struct_dense_replace_surface_data(cifti, data, structure[, dimension])

[outdata, outsform1, outroi] = cifti_struct_dense_extract_volume_all_data(cifti[, cropped, dimension])
cifti = cifti_struct_dense_replace_volume_all_data(cifti, data[, cropped, dimension])

[outdata, outsform1, outroi] = cifti_struct_dense_extract_volume_structure_data(cifti, structure[, cropped, dimension])
cifti = cifti_struct_dense_replace_volume_structure_data(cifti, data, structure[, cropped, dimension])

struct create helpers and write convenience functions

cifti = cifti_struct_create_from_template(ciftitemplate, data, type, ...)
cifti_write_from_template(ciftitemplate, data, filename, ...)

cifti = cifti_struct_create_sdseries(data, ...)
cifti_write_sdseries(data, filename, ...)

diminfo helpers

[surflist, vollist] = cifti_diminfo_dense_get_structures(diminfo)         %returns the names of structures that exist in this diminfo

outinfo = cifti_diminfo_dense_get_surface_info(diminfo, structure)
outinfo = cifti_diminfo_dense_get_volume_all_info(diminfo[, cropped])
outinfo = cifti_diminfo_dense_get_volume_structure_info(diminfo, structure[, cropped])

outmap = cifti_diminfo_make_scalars(nummaps[, namelist, metadatalist])
outmap = cifti_diminfo_make_series(nummaps[, start, step, unit])

misc

outstring = cifti_metadata_get(metadata, key)         %returns empty string for nonexistent key
metadata = cifti_metadata_remove(metadata, key)         %returns unmodified struct for nonexistent key
metadata = cifti_metadata_set(metadata, key, value)         %overwrites key if it exists

indices = cifti_vox2ind(dims, voxlist1)         %helper to act like sub2ind for voxel ijk lists

@mharms
Copy link

mharms commented Sep 2, 2020

I would add that to the end of the README.

@coalsont
Copy link
Member Author

I believe the current API is ready to be a release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants