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

SpanSets #106

Merged
merged 3 commits into from Nov 22, 2016
Merged

SpanSets #106

merged 3 commits into from Nov 22, 2016

Conversation

kfindeisen
Copy link
Member

Pull request for simultaneous review of DM-2427, DM-7170, and others. (@rearmstr, can you add to this list?)

namespace geom = lsst::afw::geom;

// Expose properties of the underlying vector constaining spans such that the
// SpanSet can be concidered a container
Copy link
Contributor

Choose a reason for hiding this comment

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

concidered -> "considered"

Copy link
Contributor

Choose a reason for hiding this comment

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

check


class SpanSet {
public:
typedef std::vector<Span>::const_iterator const_iterator;
Copy link
Contributor

Choose a reason for hiding this comment

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

Having just reading through the C++ style guidelines, seems that typedefs should have the first letter capitalized. (Guideline 3-5)

Choose a reason for hiding this comment

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

Hmm, but not for STL standard conventions I hope?

Copy link
Member

Choose a reason for hiding this comment

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

We should use the STL lowercase form here. I believe this exception the our guidelines is broadly recognized, but it may need to be made explicit if it isn't already.

};
} // namespace

namespace geom = lsst::afw::geom;
Copy link
Contributor

Choose a reason for hiding this comment

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

According to style guideline 3-6, should be afwGeom.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, we shouldn't need this namespace alias here at all; names in afw::geom are already visible.

Copy link
Contributor

Choose a reason for hiding this comment

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

removed

return spanVector.empty();
}

// Default constructor, creates a null null spanset which may be useful for
Copy link
Contributor

Choose a reason for hiding this comment

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

duplicate "null"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

}

// Default constructor, creates a null null spanset which may be useful for
// compairisons
Copy link
Contributor

Choose a reason for hiding this comment

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

"compairisons" -> "comparisons"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

/* Because the array is sorted, the minimum and maximum values for Y will
be in the first and last elements, only need to find the min and max X
values */
int minX = spanVector[0].getMinX();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need to check here if spanVector is empty?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point, it is possible that the constructor will be abused

for (auto iter = spanVector.begin() + 1 ; iter != spanVector.end() ; iter++) {
// This means they are on the same row, and are not contiguous in x
if ((*(iter -1)).getY() == (*iter).getY() && !spansContiguous(*(iter - 1), *iter, false)) {
// If two spans are not contiguous on the same row, they must bothe be contiguous with either
Copy link
Contributor

Choose a reason for hiding this comment

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

"bothe" -> "both"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

auto yRange = std::equal_range(primaryRuns.begin(), primaryRuns.end(), y, ComparePrimaryRunY());

/* Discard runs for any balue of y for wich we find fewer groups than M,
* the total Y range of the structuring element. THis is step 3.1 of the
Copy link
Contributor

Choose a reason for hiding this comment

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

"THis" -> "This"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

}
}
}
return geom::SpanSet(std::move(tempVec), false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need to nomalize the SpanSet here in case one span from other overlaps two spans?

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed to normalize

if (spansOverlap(spn, otherSpn)) {
added = 1;
/* To handle one span containing the other, the spans will be added
* peice wise, and let the SpanSet contructor normalize spans which
Copy link
Contributor

Choose a reason for hiding this comment

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

"peice wise" -> "piecewise"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

}
}
}
/* If added is still zero, that means it did not verlap any of the spans in other
Copy link
Contributor

Choose a reason for hiding this comment

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

"verlap" -> "overlap"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

static SpanSet spanSetFromShape(int r, Stencil s = Stencil::CIRCLE);

private:
void runNormalize();
Copy link
Contributor

Choose a reason for hiding this comment

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

According to the style guideline 3-4, private member functions must be preceded by an underscore.

#include "ndarray.h"
#include "Eigen/Core"

namespace geom = lsst::afw::geom;
Copy link
Contributor

Choose a reason for hiding this comment

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

Change to afwGeom.

Copy link
Contributor

Choose a reason for hiding this comment

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

changed

void testShiftedBy() {
geom::Extent2I SSextent(geom::Point2I(2, 2));
// BBox lower corner should be at -2,-2
auto SpanSetNoShift = geom::SpanSet::spanSetFromShape(2);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be more clear to use the explicit enum values so I can tell what shape you are using.

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed

template <typename Pixel, int inC>
ndarray::Array<Pixel, 1, 1> flatten(ndarray::Array<Pixel, 2, inC> & input,
Point2I const xy0 = Point2I()) const {
// Populate a one dimensional array with the values from input at taken at the points of SpanSet
Copy link
Contributor

Choose a reason for hiding this comment

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

"at taken" -> "taken"

Copy link
Contributor

Choose a reason for hiding this comment

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

check

Copy link
Member Author

@kfindeisen kfindeisen left a comment

Choose a reason for hiding this comment

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

I didn't see any problems with the use of variadic templates as such, but I did find the code difficult to read and understand. Please consider the suggested style/name changes, and please add documentation. I'd like to re-read the code once that's done; maybe I'll spot something I missed in the current version.

Also, please consider making the unit test coverage a bit more complete. See the comments for specific suggestions.

return outputArray;
}

template <typename Pixel, int outC, int inC>
Copy link
Member Author

Choose a reason for hiding this comment

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

Please document all public and protected members of SpanSets, as well as the class itself. You may want to ensure you've covered everything in RFC-225, but even documentation according to existing guidelines would make the code much more usable and maintainable.

In this case, it's not clear what xy0 is -- the dimensions of input?

Point2I const xy0 = Point2I()) const {
// Populate array output with values from input at positions given by SpanSet
applyFunctor([](Point2I point, int n, typename details::FlatNdGetter<Pixel>::Reference out,
typename details::ImageNdGetter<Pixel, 2, inC>::Reference in) {out = in;},
Copy link
Member Author

Choose a reason for hiding this comment

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

This is hard to read. Consider factoring out the types into alias declarations, factoring the lambda into its own variable, and/or listing the lambda parameters one per line (examples in the dev guide).

template <typename Pixel, int outC, int inC>
void unflatten(ndarray::Array<Pixel, 2, outC> & output,
ndarray::Array<Pixel, 1, inC> & input,
Point2I const xy0 = Point2I()) const {
Copy link
Member Author

Choose a reason for hiding this comment

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

Same comments as for flatten. The internal comments could be a good starting point for documentation, although it's not clear to me what "locations defined by SpanSet" means. Is xy0 an offset or a pair of dimensions?

}

template <typename T>
void setMask(lsst::afw::image::Mask<T> & target, T bitmask) const {
Copy link
Member Author

Choose a reason for hiding this comment

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

Same comments as for unflatten.

}

template <typename T>
void clearMask(lsst::afw::image::Mask<T> & target, T bitmask) const {
Copy link
Member Author

Choose a reason for hiding this comment

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

Same comments as for unflatten.


void testFunctor() {
// Test the remaining types of functors. Above code has tested ndarray functors
// need to test, constants, iterators, Images
Copy link
Member Author

Choose a reason for hiding this comment

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

Your test cases should include some functors that make use of the initial Point2I and int arguments -- there's not a single example in this PR.

Test cases where you call applyFunctor from a non-contiguous SpanSet and/or pick functors that take more than four parameters would also expand coverage.

SSShape.applyFunctor([](geom::Point2I, int n, int & out, int in){out = in;}, vecObject.begin(), 6);
auto imageIter = imageObject.begin();
auto vectorIter = vecObject.begin();
for (int i = 0; i < 25; ++i, ++imageIter, ++vectorIter) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Do you really need three independent counter variables? Can't you just use a foreach loop over imageObject and another over vecObject?

Copy link
Contributor

Choose a reason for hiding this comment

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

changed algorithm all together

lsst::afw::image::Image<int> imageObject(5, 5, 0);
std::vector<int> vecObject(25, 0);

auto SSShape = geom::SpanSet::spanSetFromShape(2, geom::Stencil::BOX).shiftedBy(2, 2);
Copy link
Member Author

Choose a reason for hiding this comment

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

As I understand it, this SpanSet covers every point in imageObject and vecObject. This means you can't test whether only desired points are altered; this is particularly important for IterGetter, which has no safeguards against that sort of thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

included points outside SpanSet region

void testUnflatten() {
// Test version without output array, as this simply delegates
ndarray::Array<int, 1, 1> input = ndarray::allocate(6);
input.deep() = 9;
Copy link
Member Author

Choose a reason for hiding this comment

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

Magic numbers should be factored out as constants.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have addressed many of these where I felt it appropriate

std::cout << "SS is " << val << std::endl;
}
SSShape.applyFunctor([](geom::Point2I pt, int n, afwImage::Image<int>::Pixel & out, int in){ out = in; },
imageObject, 6);
Copy link
Member Author

Choose a reason for hiding this comment

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

Again, factor out the 6 to a named constant.

Copy link
Contributor

Choose a reason for hiding this comment

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

done

invocation. All other arguments to the functor are generated from the getters
and will be supplied in the order in which they are passed to applyFunctor
*/
applyFunctorImpl(func, details::makeGetter(x)...);

Choose a reason for hiding this comment

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

std::forward the arguments.

// offset by xy0
applyFunctor([](Point2I point, int n, typename details::ImageNdGetter<Pixel, 2, outC>::Reference out,
typename details::FlatNdGetter<Pixel>::Reference in) {out = in;},
ndarray::ndImage(output, xy0), ndarray::ndFlat(input));
Copy link
Member

Choose a reason for hiding this comment

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

Could you use more whitespace in multi-line lambdas like this one (there are a few more in this file) to make them easier to read? At the very least, the lambda implementation should not be on the same line as part of the argument list if the argument list consumes more than one line. For example, I'd reformat this as:

applyFunctor(
    [](
        Point2I point,
        int n,
        typename details::ImageNdGetter<Pixel, 2, outC>::Reference out,
        typename details::FlatNdGetter<Pixel>::Reference in
    ) {
        out = in;
    },
    ndarray::ndImage(output, xy0),
    ndarray::ndFlat(input)
);

(Also, you should pass the Point by const reference here as well).

Copy link
Contributor

Choose a reason for hiding this comment

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

changed

bool inSpan = false; // Are we in a span?
int start = -1; // Start of span

for (int x = newBBoxI.getBeginX(); x < newBBoxI.getEndY(); ++x) {
Copy link
Contributor

Choose a reason for hiding this comment

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

newBoxI.getEndY() ->newBoxI.getEndX()?

Copy link
Contributor

Choose a reason for hiding this comment

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

good catch

@natelust natelust force-pushed the tickets/DM-7170 branch 2 times, most recently from c8f8e1e to 7060e7f Compare November 11, 2016 15:19
Copy link
Member Author

@kfindeisen kfindeisen left a comment

Choose a reason for hiding this comment

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

It looks much better; while I would like some more improvements in the unit tests and have some stylistic comments elsewhere, the only serious issue is in the const-correctness of makeGetter(lsst::afw::image::Image<T> const &) and makeGetter(lsst::afw::image::Mask<T> const &).

I think if you try to call these methods from the tests, you'll find that the code refuses to compile unless you make the return type ImageNdGetter<T const, 2, 1>. Please investigate and fix if necessary; I have no objections to merging once that's taken care of.

#ifndef LSST_AFW_GEOM_SPANSET_H
#define LSST_AFW_GEOM_SPANSET_H
/**
* @brief Implements a compact representation of a collection of pixels
Copy link
Member Author

Choose a reason for hiding this comment

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

This block will be attached to either nothing or the lsst namespace; remove it.

/** @brief An enumeration class which describes the shape used in creating SpanSets,
erosion kernels, or dilation kernels. CIRCLE creates a circle shape, BOX
creates a box shape, and MANHATTAN creates a diamond shape.
*/
Copy link
Member Author

Choose a reason for hiding this comment

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

Brief descriptions should be one line. Documentation of the enum values makes more sense as part of the detailed description (or immediately before each value, of course).

*
* @tparam Pixel - The data-type for the ndarray
* @tparam inC - Number of guaranteed row-major contiguous dimensions, starting from the end
*/
Copy link
Member Author

Choose a reason for hiding this comment

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

No documentation of outC. If you plan to say the same thing, you can do @tparam outC, inC.

*
* @param output - The 1d ndarray which will be populated with output parameters, will happen in place
* @param input - The ndarray from which the values will be taken
* @param xy0 - A point object with is used as the origin point for the SpanSet coordinate system
Copy link
Member Author

Choose a reason for hiding this comment

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

with -> which

* @param xy0 - A point object with is used as the origin point for the SpanSet coordinate system
*
* @tparam Pixel - The datatype for the ndarray
* @tparam inC - Number of guaranteed row-major contiguous dimensions, starting from the end
Copy link
Member Author

Choose a reason for hiding this comment

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

Again, outC is conspicuously absent.


// Test with an array for an output
int outputSize = 6;
ndarray::Array<int, 2, 2> unflatOutput = ndarray::allocate(ndarray::makeVector(outputSize, outputSize));
Copy link
Member Author

Choose a reason for hiding this comment

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

Trying an array with different outC would give slightly broader test coverage.

auto spnSt = afwGeom::SpanSet::spanSetFromShape(3, afwGeom::Stencil::CIRCLE)->shiftedBy(5, 5);
spnSt->setMask(msk, static_cast<lsst::afw::image::MaskPixel>(2));
return std::pair<lsst::afw::image::Mask<lsst::afw::image::MaskPixel>,
std::shared_ptr<afwGeom::SpanSet>>(msk, spnSt);
Copy link
Member Author

Choose a reason for hiding this comment

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

return std::make_pair(msk, spnSt) would be a lot easier to read.

auto mask = result.first;
auto spnSt = result.second;
// Verify the mask was populated with the value 2
auto mskArray = mask.getArray();
Copy link
Member Author

Choose a reason for hiding this comment

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

This test still doesn't check two important properties of setMask:

  • does setMask actually set bits, instead of e.g. integer assignment (the degeneracy would be broken if at least some of the original values were non-zero)?
  • does setMask only alter points inside the SpanSet, leaving other values unchanged?

auto mskArray = mask.getArray();
for (auto const & spn : *spnSt) {
for (auto const & pt : spn) {
BOOST_CHECK(mskArray[pt.getY()][pt.getX()] == static_cast<lsst::afw::image::MaskPixel>(0));
Copy link
Member Author

Choose a reason for hiding this comment

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

As above, there's no constraint on clearMask actually doing a bitwise operation. While the null case below does check that values are unchanged, it would also be helpful to test this for a nontrivial SpanSet.

SSShape->applyFunctor([](afwGeom::Point2I, int & out, int in){out = in;},
vecObject.begin(), dataValue);

// Check on the point input of a functor
Copy link
Member Author

Choose a reason for hiding this comment

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

Consider moving this block to after the dataValue/initialValue checks, so that related code is together.

*/
std::shared_ptr<SpanSet> union_(SpanSet const & other) const;

// Compareison Operators
Copy link
Contributor

Choose a reason for hiding this comment

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

"Compareison" -> "Comparison"

return _bBox;
}

/* Here is a description of how the _makeLabels and _label function works. In the
Copy link
Contributor

@rearmstr rearmstr Nov 18, 2016

Choose a reason for hiding this comment

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

"function works" -> "functions work"

under consideration. This results in all Spans which overlap each other in x
being labeled with the current label.

Once _label reaches the end of Spans which meet it's criteria, control falls
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there an extra space after "Spans"?

back to the _makeLabels function and the current label is incremented. The
function then moves onto the next Span in the SpanSet which has not been
labeled. If all spans were labeled in the recursive call the loop falls to the
end, and the label vector and the last label number (number of labels) are
Copy link
Contributor

Choose a reason for hiding this comment

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

Extra space after "label number"

void _initialize();

/* Label Spans according to contiguous group. If the SpanSet is contiguous, all Spans will be labeled 1.
* If there are more than one groups each group will receive a label one higher than the previous.
Copy link
Contributor

Choose a reason for hiding this comment

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

"there are more than one groups" -> "there is more than one group"

}
++index;
}
return std::pair<std::vector<std::size_t>, std::size_t>(labelVector, currentLabel);
Copy link
Contributor

@rearmstr rearmstr Nov 18, 2016

Choose a reason for hiding this comment

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

Doesn't this return one more than the last label number? That isn't what the comment above says.

@natelust natelust force-pushed the tickets/DM-7170 branch 2 times, most recently from 02d7887 to af8b530 Compare November 21, 2016 19:51
SpanSet is a class which holds a collection of spans to represent a collection
of spatial indexes. This class has associated methods for working with SpanSets
such as intersection, logical difference, and union. It also contains
functionality which takes in a functor, and containers to operate on, and
applies the functor to the containers at each of the points defined in the span
set.
Wrap the new SpanSet class in Swig. Some functionally works, but some
will be better wrapped with the upcoming pybind11 change
Added a unit test for SpanSets class in c++ instead of python to
more throughly test all of the functionality, specifically with
functors, which would be difficult to completely test in python
@natelust natelust merged commit 6d44d56 into master Nov 22, 2016
@ktlim ktlim deleted the tickets/DM-7170 branch August 25, 2018 06:44
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.

None yet

7 participants