-
Notifications
You must be signed in to change notification settings - Fork 42
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
Transformation Specification #28
Comments
Note that there is a scale "discussion" ;) in #12. Some considerations:
|
Ok, good point I did not see that one and it's indeed related.
Yes, axes is the dimension order as it refers to the values for this transformation. One could also store this at a higher level in the metadata and then assume that the transform values always refer to this axis order.
So the idea here would be to describe one transformation per image. I think chaining transformations is currently out of scope.
They are all optional, where their absence indicates a scale of 1, no translation, no rotation etc. |
I decided to put "axes" in there because I work alongside people who use languages / libraries that use different array indexing schemes, and so we ran into lots of annoyances due to ambiguity about axis names (is the first axis z, or is it x?). And I think every scale level should have the transform listed. The principle for that is "multiscale is just a collection of single-scale datasets => multiscale metadata is just a collection of single scale metadata". |
This issue has been mentioned on Image.sc Forum. There might be relevant details there: https://forum.image.sc/t/next-call-on-next-gen-bioimaging-data-tools-feb-23/48386/9 |
We modeled the metadata here after the open organelle layout, but at the time didn't see a need for a transformation for each scale level. But I have thought about this more since then, and I agree that it's better to have it for each scale level, which allows to display them separately. |
I'm not 100% sure there is an absolute right answer here. One drawback of listing transforms in However, from an implementation standpoint, it's very convenient to access metadata from a single consolidated file (i.e., a single GET), rather than accessing it from multiple files (i.e., multiple GETs). This convenience was also a motivation for putting the the spatial transform stuff in |
And has basically nothing in it. More than happy to let @constantinpape take the lead. Closing #12. |
We didn't put it in the dataset metadata at all and only in I don't feel strong about having it with the individual datasets or not; I think for what we design the viewer would always access the |
I think it's critical that individual datasets always have spatial metadata. The |
I agree very much with this design constraint. To me it certainly feels more important than the problem of metadata duplication, which can be managed. |
Ok, then I think we have clear perference: transformation metadata must be specified in the individual datasets and in |
What do you guys think of speccing out a flexible extensible trafo meta data format with clear exit points when something is not supported? Our very old approach to this is the TrakEM2/ Render format (http://www.ini.uzh.ch/~acardona/api/mpicbg/trakem2/transform/CoordinateTransform.html and https://github.com/saalfeldlab/render/blob/master/docs/src/site/markdown/data-model.md). The gist
and
The entire spec here is very Java centric (type is the legit class name), and data encode all sorts of stuff into a String which is limiting in how much you can map into it. Also, a reference to a file/ dataset (for deformation fields or coefficients) is not intended. |
@axtimwalde {
"transformation": {"type": "affineBdv", "data": "..."}, # affine transformation in bigdataviewer format
"transformation": {"type": "eulerElastix", "data": "..."} # euler transformation in elastix format
...
} and we would somewhere list the available types and the corresponding format for And |
Approximately, but the data : String approach is very limiting and it should therefore be flexible what to put into the parameters. Often, that would be something like "transformation" : {"type" : "scale", "parameters" : [4, 4, 40]} |
@axtimwalde I think this is a good solution, because it also makes it easier to extend the transformations later. (E.g. to support #30). I am also not so sure whether adding the Also, in the current form, the image spec always assumes 5D data. However, the transformations are only valid for the 2 or 3 spatial dimensions. This is something we need to keep in mind too, but I think we first need to discuss the 5D requirement more, see #35. |
I disagree :) I don't think trying to come up with a spec that covers all known / possible nonlinear transformations is feasible or a good use of time. In my view, as soon as you go down the road of applying a nonlinear coordinate transformation to your data, you are working with specialized tools and the developers of those tools should be allowed to use whatever metadata they need. This only requires that the ngff transformation spec be permissive. On the other hand, I think it does make sense to be formal about linear coordinate transformations, because they are universal -- every image processing library understands affine transforms (and most visualization tools can display data on an affine-transformed grid, with varying levels of support for shear and rotation). Because linear transforms are universal, I think it does make sense to come up with (or copy) a readable, compact description of linear transforms. This could either be a list of named transformations, like
I don't think axes and units describe the data; instead, axes and units describe the grid on which the data is sampled. The spatial transform under discussion also describes this grid. In particular for shears and rotations it's very important to explicitly name the dimensions of the input space and the dimensions of the output space -- in medical applications, images might be acquired in machine coordinates and transformed into anatomical coordinates. |
Not every image/ array processing library supports affine transformations, translations or rigid transformations have fewer parameters than affine transformations which justifies treating them differently than affine transformations, but default decomposing affine transformations makes little sense in practical applications that use affine transformations and is incredibly verbose. Axes in data space are usually a mixture of several physical dimensions, i.e. a scanner scans a spatial dimension while spending time, so the 'correct' axes label would be "x + t" with a unit of "0.4um + 0.0000s" or something. It is quickly clear that particularly the qualifiers in the unit field are clearly parts of a transformation. I therefore strongly believe that named axes with references to physical spaces and units should be used for the target space of a transformation, not the data/ source space. The data has axes in null or data space, it's default unit is "px". A transformation converts them into physical space with named axes and physical dimensions (if you want). A transformation can transform between all sorts of spaces, not only physical and data. I feel bad about muddling a "default" axes permutation (another subset of affine transformations) into the spec by naming the axes of the transformation, so they can be used for data with axes that have the same name. That is a very complicated way of specifying the axes permutation into a 'default' data space in which some other transformation can be applied. I think it would be great for transformations to have names, e.g. "xycz2xyzc" for one such axes permutation. A 'standard library' of transformations may only contain affine transformations and subsets thereof at first and may be extended by other classes of transformations and reference implementations as needed. |
Thanks for the detailed comments @d-v-b @axtimwalde. I will try to go through it point by point: Transformation Specification:
I think we are on a very similar page here. The transformation spec {"type": "some_transformation_type", "parameters": {...}} allows to specify custom transformations. But to support interoperability, we would provide a list of "standard" transformation types (e.g. translation, scale, rigid, affine) with a strict definition. Developers can still use their custom transformations, but then there is no guarantee other tools can parse it. (At some point the list of standard types could be extended with common custom transformations, but let's not get ahead of ourselves here). Axes and units:
This makes a lot of sense. The only open question for me is what to do about image datasets that don't specify a transformation, but for which one still needs to specify the axes.
I would prefer 1, but this would be a breaking change with the current spec. Axes permutations:
I agree with this. My point here was that it's unclear how to indicate which axes of the input data the transformation is applied to. |
If the axes of the source space are not named, then information is needlessly lost. Imagine point-scanning an image of a region in space, then scanning the same region after rotating the sample 90 degrees (i.e., transposing the scan axes). Without semantics for the source axes (e.g., names like scan_y, scan_x), it is cumbersome to distinguish these two images. But I agree that a default unit of
I would prefer 1, but this would be a breaking change with the current spec. I also prefer 1, and I note that option 2 is just shoehorning in a specification of a transform ("physical dimensions" is just a scale transform), so these two options aren't really different. |
+1 for "all data sets must specify a transformation" (aka a mapping from data space to physical space) As @constantinpape pointed out, to practically proceed further I guess the question of the dimensionality of the data space indeed seems of quite high priority. |
Thanks for picking this up @d-v-b and @constantinpape.
This is a transformation. Rotating the sample will most likely not be a perfect 90deg rotation either, so being able to do this with a flexible transformation spec instead of renaming axes would be much desired.
This is compatible with specifying no transformation: No transformation means identity transformation. Named axes and units are specified for the target space of the transformation. No named axes means data space with unit "px". This is compatible with named axes and units for images because that means that the target space is achieved by applying the identity transformation after which "px" are "cm". The current standard case of scaled named axes to express the resolution is also compatible with this, the scaling as the transformation, the axes names and units are the target space. |
I am wondering how a transformation from, e.g., a 3D data space that should be mapped into physical space with the dimensions XYC would look like? Did you consider this or are we currently assuming that all dimensions of the transformation are always spatial? I think an argument for supporting a data space XYC was that some people have data with lots of channels and would like to configure the chunking such that all channels for one location XY are loaded as one chunk. |
Let's say I want 5 micrometer voxel spacing in the physical space and translate 10 micrometer and have XYC data space. Would something like this be the idea?
|
👍 Since anisotropic pixel spacing and an offset is common with microscopy and other types of scientific images, supporting spacing and offset as standard spatial metadata associated with the image makes sense. These two are built into an xarray.Dataset representation as discussed in #48. A few other items for consideration: Parameter precision turns out to be very important in practice with spatial transformations (lessons learned over the years in itk). Serialization and deserialization in decimal ascii in general will result in a loss of precision due to the differences in binary / decimal encoding and how IEEE float's are represented. This is most significant for transformations that involve rotations. In general, special effort has to be taken to avoid loss of precision, e.g. google double-conversion. So, it is best to store the transformation parameters in float64 arrays. It works well to store a spatial transformation, identified by the transformation type and its parameters, along with an identifier of the coordinate system it transforms from and the coordinate system it transforms to. Then, the image holds in its metadata the identifier for its coordinate system. A few examples of this include DICOM spatial transformations 1 2 and WIP in NeurodataWithoutBorders (NWB). Even for "simple" rigid transformations, there are a dozen ways that they can be interpreted unless all the information is there to interpret them. As @axtimwalde and I learned when examining the bioimage electron microscopy community prior to standardization, a dozen researchers will somehow manage to all pick the different ways 🥚 🍳 🍞 🥖 🥪 In terms of possible transformations, ITK has defined a nice phylogeny (attached). |
This issue has been mentioned on Image.sc Forum. There might be relevant details there: https://forum.image.sc/t/fiji-hackathon-2021-big-data-days-a/53926/7 |
This issue has been mentioned on Image.sc Forum. There might be relevant details there: https://forum.image.sc/t/next-call-on-next-gen-bioimaging-data-tools-early-september-2021/55333/14 |
Here is an early but concrete proposal @axtimwalde and I wrote based on the discussion above that reflects our preferences (of course). https://github.com/saalfeldlab/n5-ij/wiki/Transformation-spec-proposal-for-NGFF |
Thanks, John! Overall I really like this proposal and its extensible and already covers some of the non-linear use-cases of #30. A couple of comments:
In addition, following the discussions in #53, there seems to be strong support to specify a transformation per scale dataset. I think the easiest way to do this would be
|
Thanks a lot for fleshing this out! ❤️ I also have a couple of questions:
What's the motivation to specify in this direction? For example I think in the elastix registration software they specify from target to source. I am not saying that I would prefer to specify it in the other direction, I am just curious!
The axes right now have a field called |
Thanks for the feedback Constantin! @constantinpape On axes:
I agree. The only "big" difference that I see to what you describe in #35 is the "type" (spatial, time, etc.). We might also see the nrrd spec. and as you say there:
which I would be very happy with. [pd]-fields
I think this is a great idea. "which axis does my transform operate on"
You've pinpointed a hard thing. I'm not sure, and am open to ideas. "shove all the transformations together" {
"transform": {
"type": "affine",
"affine": [
2.0, 0.0, 0.0, 10.0,
0.0, 0.5, 0.0, -5.0,
0.0, 0.0, 1.0, 0.0
]
},
"axisTypes": ["spatial","spatial","channel"],
"axisLabels": ["X","Y","C"]
} to "split by type" "axes": [
{
"type":"spatial",
"labels":["X","Y"],
"indicies":[0,1],
"transform": {
"type":"affine",
"affine":[2.0, 0.0, 10.0,
0.0, 0.5, -5.0 ]
},
"unit":"nm"
},
{
"type":"channel",
"labels":["C"],
"indicies":[2],
"transform": {
"type":"identity"
}
}
] the former is simpler, the latter "clearer" somehow and does not let one "mix" different types of axes. I'm even tempted to allow a couple of options ranging from simple to general. |
Thanks @tischi ! transform inverses
Exactly! Our thinking was that it fits most people's (? certainly my) mental model better. In the case of elastix output, we have in mind to do something like: {
"type" : "inverse_of",
"transformation" : {
"type" : "bspline_dfield",
"data" : "the parameters..."
}
} axes
Good point. I was thinking (a), kind of. More we mean it to be a way for people / software that generates data to encode something about it. I'm thinking about what you say here. So, I wasn't thinking about (b), but maybe we should.
me too. |
Yes, I guess we need something to tell how the spatial axes are mapped onto the right-handedness, isn't it? |
...or is that maybe implicit in the transformation, i.e. the right-handedness is a feature of sample space and not voxel space. Thus, maybe just the order of the spatial axes after the transformation defines the axes order and thereby the handedness (to this end, within the transformation one could "switch" axes if needed). |
What does this mean exactly? I think wherever possible the semantics of axis names should not play any role in this spec. The spec should only "know" that axes have string names. I think mapping string names on to real life stuff is the job of tools that consume data, not the storage layer. |
I understand this to mean:
How can the consuming software know that some spatial dimension is left-right, vs right-left, vs inferior-superior if the storage layer doesn't tell it? |
If this kind of information is part of the spec, it creates interaction between axis names and axis types -- would it be a violation of the spec to have an axis called
If I understand it correctly, this kind of information pertains to the relationship between axes, not the mapping from axis names onto physical stuff, and so this seems in-scope for the spec. |
I just had time to quickly read all the recent comments, but I think there is some need for clarification of the current (v0.3)
I think there are some good arguments to switch to something closer to the proposal and decouple label and semantic meaning. Ideally we would do this in a way that is somewhat compatible with v0.3 (i.e. the field I will read the rest of the comments during/after the meeting. |
Follow up from last week's meeting: |
Could we put all transform parameters in binary floating point |
@constantinpape looking at the current proposal from @bogovicj it is not clear to me whether it would be possible to specify different transformations for different channels. |
@tischi, I went crazy, let me know what you think. You're right, the spec does not allow multiple transforms per dataset. I would personally split channels into different datasets if I was going to process / transform them differently. However, what you want is possible by using (abusing?) a displacement field (and doesn't even cost lots of extra storage), but it either requires some assumptions or modifications. Maybe it's a little unclear / overkill though. I'm open to adding something to cover that case more clearly. I assume One transform per datasetThe stuff below gets messy. We might declare Use a displacement fieldWe could do the above by using N displacement fields (stored in one dataset), but it's not a "field" so much because there's only one displacement per channel (assuming 3 channels). I'd argue "boundary" extension is the most sensible for displacement fields, so can be assumed. But we could be explicit about it either by spec-ing the extension "shape" : [ 1, 1, N, 3 ],
"axes" : {
"labels": ["x","y","c","v"],
"type": ["space","space","c","vector"],
"unit": ["ly","ly","","um"]
},
"extend" : [ "boundary", "boundary", "0", "0" ] or abusing the transform (pixel width there is 1 billion tera-meters :p). I actually hate this. "shape" : [ 1, 1, N, 3 ],
"axes" : {
"labels": ["x","y","c","v"],
"type": ["space","space","c","vector"],
"unit": ["Tm","Tm","","um"]
},
"transform" : {
"type" : "scale",
"scale" : [1e9, 1e9, 1, 1]
{ The downside is that the displacement vectors are 3D to be explicit about which vector component applies to which dimension. We could go to shape Coordinate-wiseAnother way modify the spec to make this possible is to add a new "coordinate-wise" transformation that might look like this: {
"type" : "coordinate-wise",
"axis" : "c",
"indexes" : [ 2, 5 ],
"transformations" : [
{ "type" : "translation",
"translation" : [-0.5, 0.0]
},
{ "type" : "translation",
"translation" : [0.0, 0.9]
}
]
} which says "translate the channel at index 2 by -0.5 units, and translate the channel at index 5 by 0.9 units". This would stink if there were lots and lots of channels, so let's make that less painful. Transformation_listA "transformation_list" stores many transformations of a single type, in a dataset. {
"type" : "coordinate-wise",
"axis" : "c",
"transformations" : {
"type": "transformation_list",
"member_type": "translation",
"path": "/path/to/channel_translations"
}
} the above requires a dataset in this container at "/path/to/channel_translations" of shape Even more general?If we allow even more general axis types, we go as far as describing a piece-wise affine with say: "axes" : {
"labels": ["x","y","c","matrix"],
"type": ["space","space","c","affine_parameters"],
"unit": ["um","um","c","um"]
} but I think it's not worth going the far right now. |
Thanks a lot! I also had an idea (not sure if this is covered by your proposals, it feels a bit similar to your coordinate-wise proposal).
What do you think? By the way, another use case would be drift correction where one may want to specify different translations per timepoint. |
This issue has been mentioned on Image.sc Forum. There might be relevant details there: https://forum.image.sc/t/ome-zarr-hackathon-january-2022/60771/1 |
Closing in favor of #94 |
This is a feature request towards v0.2 of the multi-scale image spec:
Support (affine) transformations for images to apply these on the fly (e.g. to map images to a shared coordinate system).
So far, I have come around two different implementations that contain a proposal for this:
Open Organelle
The transformation metadata is also duplicated in the individual datasets.
I2K Tutorial
Same as the open organelle proposal, but the transformation is defined for on the dataset level, not for each individual scale:
The transformation metadata is not duplicated.
(I have left out the nested
multiscale
etc. in both cases to make this more readable.)I would be happy to develop this into a PR to the spec, but it would be good to have some feedback first:
rotation
andshear
totransformation
, so that a full affine trafo can be specified?@joshmoore @frauzufall @kephale @axtimwalde @d-v-b @tischi @jni
(please tag anyone else who might be interested in this)
Original text from #12:
Originally discussed as part of the multiscales specification, scale metadata related to each volume in a multiscale image needs storing for the proper interpretation of the data.
see also ome/omero-iviewer#359 in that each level should be strictly smaller than the last.
The text was updated successfully, but these errors were encountered: