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

Adding a new explode_along_axis function inside NDCube #118

Merged
merged 12 commits into from
May 18, 2018

Conversation

BaptistePellorceAstro
Copy link
Contributor

Here the first version of explode_along_axis inside NDCube.

ping @DanRyanIrish

@pep8speaks
Copy link

pep8speaks commented May 4, 2018

Hello @BaptistePellorceAstro! Thanks for updating the PR.

Line 926:9: E128 continuation line under-indented for visual indent
Line 925:48: E502 the backslash is redundant between brackets

Comment last updated on May 18, 2018 at 15:08 Hours UTC

ndcube/ndcube.py Outdated
def explode_along_axis(self, axis):
"""
Separates slices of NDCubes in sequence along a given cube axis into a NDCubeSequence
of (N-1)DCubes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring should reflect that this is an NDCube method, not an NDCubeSequence. So replace with something like:
Separates slices of the NDCube along a given cube axis into a NDCubeSequence of (N-1)DCubes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, of course !

ndcube/ndcube.py Outdated
meta=self.meta, mask=self[i].mask, missing_axis=[False, False, True],
extra_coords=extra_coords_formated)
result_cubes.append(cube)
return NDCubeSequence(result_cubes, meta=self.meta)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method could be rewritten with the following:

result_cubes = []
cube_slices = [slice(None, None, None)] * len(self.data.ndim)
for i in range(self.data.shape[axis]):
    cube_slices[axis] = i
    result_cubes.append(self[cube_slices])
return NDCubeSequence(result_cubes, common_axis=axis)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make me understand these lines just now ! Of course, this is the best way.

@DanRyanIrish
Copy link
Member

DanRyanIrish commented May 4, 2018

Thanks so much for this PR @BaptistePellorceAstro. I've left a couple comments. As well as those, this method should be in NDCubeBase, rather than NDCube. It will then get inherited by NDCube.

ndcube/ndcube.py Outdated
@@ -542,34 +539,24 @@ def explode_along_axis(self, axis):
result : `ndcube_sequence.NDCubeSequence`

"""
# If axis is -ve then calculate the axis from the length of the dimensions of one cube
# is axis is -ve then calculate the axis from the length of the dimensions of one cube
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: Should be If.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified it but I copy the previous line ... a little mistake.

ndcube/ndcube.py Outdated
# Setting the slice value to the index so that the slices are done correctly.
cube_slices[axis] = i
# Appending the sliced cubes in the result_cube list
result_cubes.append(self[cube_slices])
Copy link
Member

@DanRyanIrish DanRyanIrish May 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this is arranged now, the metadata of the Cube will be copied for each slice. Really, the metadata still applies to all the data and isn't specific to the slices. So I think the metadata should be moved from the cube level, to the sequence level. This could be done by doing something like:

    sliced_cube = self[cube_slices]
    sliced_cube.meta = None
    result_cubes.append(self[cube_slices])
return NDCubeSequence(result_cubes, common_axis=axis, meta=self.meta)

@DanRyanIrish
Copy link
Member

Hi @BaptistePellorceAstro. This is looking very good now. Now, as well as the tiny changes I just requested, this PR needs tests! :)

ndcube/ndcube.py Outdated
sliced_cube.meta = None
# Appending the sliced cubes in the result_cube list
result_cubes.append(self[cube_slices])
result_cubes.append(self[item])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self[item] here should be sliced_cube.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, I forget to modify this one when I created item to solve a bug.

(cubem.explode_along_axis(1), (3*u.pix, 2*u.pix, 4*u.pix)),
(cubem.explode_along_axis(-2), (3*u.pix, 2*u.pix, 4*u.pix))
])
def test_explode_along_axis(test_input, expected):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of this test, could you also check the type of the output, and also that the meta is None for the sub-cubes and the the sequence meta is the same as the original cube meta?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ! I was thinking that this test (taken from ndcube_sequence) is not relevant at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an idea why NDCube.explode_along_axis(axis=0)[0].meta is an OrderedDict() ?
It should not be None ?

Where NDCube.explode_along_axis(axis=0) is a NDCubeSequence and NDCube.explode_along_axis(axis=0)[0] the first NDCube of the previous NDCubeSequence.

(cubem.explode_along_axis(0).dimensions, (2*u.pix, 3*u.pix, 4*u.pix)),
(cubem.explode_along_axis(1).dimensions, (3*u.pix, 2*u.pix, 4*u.pix)),
(cubem.explode_along_axis(-2).dimensions, (3*u.pix, 2*u.pix, 4*u.pix)),
(cubem.explode_along_axis(0)[0].meta, OrderedDict()),
Copy link
Contributor Author

@BaptistePellorceAstro BaptistePellorceAstro May 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is really an OrderedDict() ???

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it should be an NDCubeSequence.

Copy link
Contributor Author

@BaptistePellorceAstro BaptistePellorceAstro May 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I am speaking about the NDCube.meta

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Well I think recently in Python, dictionaries now default to OrderedDict. So I think that's fine.

@@ -910,3 +912,15 @@ def test_axis_world_coords_without_input(test_input, expected):
for i in range(len(all_coords)):
np.testing.assert_allclose(all_coords[i].value, expected[i].value)
assert all_coords[i].unit == expected[i].unit


@pytest.mark.parametrize("test_input,expected", [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put in more than one expected output by making expected a tuple and then unpacking that tuple in the test. The test should be testing the same thing(s) in the same way. You can test multiple things in one test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I know. Do you want I regroup types test ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to have something like:

def test_():
    expected_dimensions, expected_type, ... = *expected
    assert expected_dimensions == test_input.dimensions
    assert isinstance(test_input, expected_type)
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I go change that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must add and other test for the metadata.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I clearly understand why you want to regroup tests but I didn't find a better than seperate NDCubeSequence and sub-NDCube. Like that, we can have a better understanding about which test is about which types. But I think that will be interesting to modify the quantity of list for the sub-NDCube.dimensions to a tuple of quantity as for NDCubeSequence.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, you're saying NDCube.dimensions should give (<Quantity x pix>, <Quantity x pix>, <Quantity x pix>) instead of what it current gives which is <Quantity [x, x, x] pix>?

Copy link
Contributor Author

@BaptistePellorceAstro BaptistePellorceAstro May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think this should be better, as we try to have the same functions for each class.

But maybe be the list is used in some other functions ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be open to considering that. Having a Quantity of multiple numbers is obviously handier. But you can't do that for NDCubeSequence as if there's a common axis, the length of the cubes along that axis many not be the same length. On the other hand, you could turn a tuple of single value quantities into a single quantity of multiple values by doing u.Quantity((0*u.pix, 1*u.pix), unit=u.pix). Although it's probably not very fast.

Like I said, I'm open to it. I'll think about it a little more. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Cadair could we have your thoughts on this proposed change to the NDCube.dimensions API? The proposed change is that the output will become a tuple of length-one Quantities, rather than a Quantity of length>1. You can see @BaptistePellorceAstro's original arguments above. I am open to it for a couple of reasons:

  • It is more consistent with the output of ndarray.shape and other such attributes;
  • It makes NDCube.dimensions consistent with NDCubeSequence.dimensions. The latter is a tuple of Quantities so if the sub-cubes' along the common axis are of differing lengths, that can be expressed by the common axis Quantity in the tuple having length > 1 which gives the length of each cube.
  • It can be easily converted to a single Quantity by doing >>> Quantity(my_cube.dimensions, unit="pix")

This may however, have repercussion for APE 14? If so, perhaps there are arguments to escalate this change there too?

@BaptistePellorceAstro
Copy link
Contributor Author

I succeed to do only one test for explode_along_axis.

def test_explode_along_axis_meta(test_input, expected):
expected_type, expected_meta = expected
def test_explode_along_axis_v2(test_input, expected):
expected_dimensions, expected_type, expected_meta = expected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original NDCube and the axis along which is should be exploded should be in the parameterisation and the the method on them, explode_along_axis should be called in the test:

output = test_input[0].explode_along_axis(test_input[1])

])
def test_explode_along_axis_meta(test_input, expected):
expected_type, expected_meta = expected
def test_explode_along_axis_v2(test_input, expected):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a _v2 in the test name here. That's what version control is for :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was an old test in my local repo =)

assert isinstance(test_input, expected_type)
assert isinstance(test_input.meta, expected_meta)
def test_explode_along_axis(test_input, expected):
output = test_input[0].explode_along_axis(test_input[1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An optional change: Since you now have 3 inputs, you could do the same thing as you did with the expected outputs and unpack them into sensible variables like:

input_cube, input_axis, test_slice = *test_input

But this a matter of your personal preference in coding style and so optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clearer as you said so I modified.

@DanRyanIrish
Copy link
Member

Hi @BaptistePellorceAstro. I'm happy with these changes. The PR looks great. Let me know if you want to make any further optional additions, like the one I mentioned in the most recent comment. If not, I'll merge the PR :)

@BaptistePellorceAstro
Copy link
Contributor Author

If you are OK with the new version, I am ready to merge.

@DanRyanIrish
Copy link
Member

I think this PR is almost ready to go. It just has to be altered so the tests pass.

https://travis-ci.org/sunpy/ndcube/jobs/379626836

@BaptistePellorceAstro
Copy link
Contributor Author

BaptistePellorceAstro commented May 16, 2018

I don't understand what is this problem. In my local repo, the test succeeded.

@DanRyanIrish
Copy link
Member

Maybe time to call on @Cadair, the guru of understanding Travis errors. Any interpretation of what these errors actually mean, @Cadair?

@BaptistePellorceAstro
Copy link
Contributor Author

BaptistePellorceAstro commented May 18, 2018

I succeeded to solve Travis error, I am ready to merge.

@DanRyanIrish
Copy link
Member

@BaptistePellorceAstro, I just merged a bugfix PR from @Cadair . Could you pull the latest version of master in your explode_along_axis branch and push it to this PR. Once that's done and there are no test failures, I'll merge.

@BaptistePellorceAstro
Copy link
Contributor Author

I am not at the office, can I do it from the website ?

Update of explode_along_axis before a PR
@BaptistePellorceAstro
Copy link
Contributor Author

I tried something, it is what you want ?

@DanRyanIrish
Copy link
Member

Yep! That looks good! 👍

@DanRyanIrish DanRyanIrish merged commit c72df57 into sunpy:master May 18, 2018
@DanRyanIrish
Copy link
Member

Another PR merged!!!

@BaptistePellorceAstro BaptistePellorceAstro deleted the explode_along_axis branch May 25, 2018 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants