-
Notifications
You must be signed in to change notification settings - Fork 1
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
Quo vadis #1
Comments
Some things to consider:
|
I think treating mirrors as any other transform is the way to go. A library/application downstream of a transformation library could certainly tighten the restrictions on what could be used as a space reference, so saying that navis only works with strings would be totally in keeping with what I was thinking about, at least. Generating a mirror transform from a template brain would also be fine - it would just return an AffineTransform or a TransformSequence([AffineTransform, WarpingTransform]). As you say, I think navis would be the place for a global template registry and any free functions which depend on it. You're right that more complex data, like meshes, would be another downstream use which it might be nice to provide some convenience functions for, as reflections would invert the triangles, and nonlinear transforms could invalidate the mesh. |
Cool! So as brief road map before I start migrating
Now or later: I'll quickly do 1 later today and 2-4 is your baby? Also some nice-to-have things off the top of my head:
|
I was actually just about to go the other way for transformnd - iteration and vectorisation is much easier in (N, dims) and it seems to be a more common way of handling coordinates. There's going to be a bunch of axis-moving and transposing either way and I am getting the sense that (N, dims) is more pythonic. Not sure if it'll be any use but I also have a reflection transform class in the works where you can reflect your coordinates around a point, line, ... hyperplane (it infers orthogonal hyperplanes to get where you need to go, which I think is mathematically sound...). |
I think to get these even further down to basics, we could strip out the dataframe handling (which has opinions about dimension number and order) and let the caller do that - navis could just have one utility function like import numpy as np
import pandas as pd
DIMS = ["x", "y", "z"]
def as_coords(x):
if isinstance(x, pd.DataFrame):
return x[DIMS].to_numpy()
arr = np.asarray(x)
if arr.ndim != 2 or arr.shape[1] != 3:
raise ValueError("an informative message")
return arr Additionally, I think directionality is the responsibility of the caller or can be inferred by the space references, rather than having a "forward"/"reverse" variable (if that is necessary, could it be a boolean These minor wording changes etc. don't have to change in navis, which I appreciate is a bigger job - navis could have its own classes which inherit the current navis API and just contain an xform instance to do the work; then there could be a |
The forward/reverse argument was sort-of carried from
I did notice that you had effectively subsets of the AffineTransform in |
Cool!
I'd been trying to avoid this where possible - I suspect some transforms will bring in some hefty dependencies, so keeping transforms other than the real basics out of the top level keeps import times minimal until the user explicitly wants one of the other transforms. Additionally, with several transforms needing optional dependencies, if you keep them out of the top level import then you don't need to handle the importerror - if the caller explicitly imports that submodule, they get the importerror themselves which should be informative enough, so we get to ignore the case where it's not installed.
I agree with not having a global registry; that makes more sense downstream packages with specific domains/ use cases.
Yes, separating the linear map from the translation - adding the extra coordinates for an augmented matrix is another copy which I'm sure would be trivial once benchmarked but given it was just a subclass away I thought I'd give the option of avoiding it.
Yes, I think that's right! I might be inclined towards overriding I'd be inclined towards keeping the non-affine implementations of translation, mirror, and scale too, because they're easier to think about and put together in higher dimensionalities, although this does lean away from "one and only one way to do things". But they could always have methods to convert them into their affine form for supported dimensionalities, and having different mechanisms for composing affine ( |
I see your point. Avoiding imports means that we would need to leave transforms in individual submodules though. That in turn makes for opaque paths like |
Are they opaque, or are they namespaces, "one honking great idea"? 😅 The transforms themselves could be promoted out of the |
Played around with this in transformnd and have all of the affine operations available as alternate constructors, plus https://github.com/clbarnes/transformnd/blob/main/transformnd/affine.py#L143 |
I like the class methods! |
I had another hack at transformnd. There was some housekeeping (docs and refactoring into submodules), some nitpicking (switching from Something which might be of more interest is the adapters submodule: https://github.com/clbarnes/transformnd/tree/main/transformnd/adapters This provides an easy way to define how to transform non-ndarray objects. For example, if you had a bunch of dataframes where some columns had coordinates in them, you'd do from transformnd.adapters.pd import DataFrameAdapter
adapter = DataFrameAdapter(list("xyz"))
transformed_df = adapter(some_transform, some_df) If you had an object which had several attributes which needed transforming, potentially each with different adapters, you'd do from transformnd.adapters import AttrAdapter, NullAdapter
from dataclasses import dataclass
import pandas as pd
@dataclass
class MyObject:
pointcloud: np.ndarray
treenodes: pd.DataFrame
adapter = AttrAdapter(pointcloud=NullAdapter(), treenodes=DataFrameAdapter(list("xyz")))
transformed_obj = adapter(some_transform, some_obj) Adapters also have a For the navis case, the combination of those two should make it reasonably accessible for each type to know how to transform itself given a generic transformation function. |
Hi @clbarnes
I finished a first working version including some simple tests. Compared to the navis version:
xform_brain
->xform_space
andmirror_brain
->mirror_space
TemplateRegistry
to bare minimumsource_space
andtarget_space
to all transforms because it made sense while working on the registry anywayRe 2: for now, I dropped the distinction between bridging and mirror transforms and
mirror
/mirror_space
are currently not working. Innavis
, mirroring requires a registeredTemplateBrain
(which is used to generate a Affine transform to flip the data) and an optional warping transform. Here, I was considering simply treating mirrors as a space of their own - e.g. you would havespace1
andspace1_mirr
, and the transform from one to the other would minimally be just an Affine transform but could also be a TransformSequence. Formirror_space
this would then be something along the lines of:I'm not entirely sold on this because (a) this is a rather opaque to the user and (b) won't work with just any hashable object. May need to go back to the registry tracking mirror vs bridging transforms.
Do you have thoughts on how to proceed from here? I'm primarily interested in preserving functionality required for navis but not at all married to how it's implemented so, from my end, I'd say have at it.
The text was updated successfully, but these errors were encountered: