Skip to content

Conversation

@MahirEmran
Copy link

@MahirEmran MahirEmran commented Oct 28, 2025

Description

Resolves #23

  • Added EdgeFilteringAlgorithm into DistancePipelineExecutor constructor
  • Edited DistancePipeline to have 4 items
  • Added edge_filters.hpp w/ ProvideEdgeFilteringAlgorithm and ModifyingPipeline
  • Added OPT_ASSIGN flags

Artifacts for PR #47 (DO NOT CHANGE)

@nguy8tri
Copy link
Collaborator

nguy8tri commented Nov 4, 2025

Hi, so you may notice that your coverage is failing for unapparent reasons. A recent bug that was introduced into Linux is the likely culprit, but unfortunately, we don't know what the bug is or how to fix it. However, we do know a way to go around it. I have noticed that you've added some new tests to pipeline-tests.cpp. You should try moving them to nominal-pipeline-test.cpp to see if it fixes the issue. If that doesn't work, try making all the pipeline tests in one file.

Note

For reference, there seems to be an issue where instantiating templates across multiple locations makes for uncombined or incomplete coverage data. Thus, the solution to this issue is to only test a particular instantiation in one file. Attempting to test them in both files (as you've done for DistancePipeline) will for some reason make gcovr believe that some things are not fully covered.

Also, I highly encourage you to work with mocks instead of instantiating new objects. This may seem odd at first, but it is a very robust way to mock behavior instead of making full on implementations, however small it looks. For instance, all your test stages within sequential-pipeline can easily be reformed into:

EXPECT_CALL(mock, function(args...)).WillOnce(testing::Return(your stuff))

As odd as it seems, it is an industry essential in nominal software development, but also just a good way to not worry about making test classes. This whole thing about mocking falls under something called Dependency Injection, used everywhere in the real world. To learn more about it, please look here: Mocking and Dependency Injection. You should also look at the purpose of the providers folder, detailed in Executor Injection, which is NEVER to be used directly in test cases (only indirectly through integration tests) :).

For reference the two functions that were not called (and why your coverage is failing in Github) are (there is almost no logic to this by the way):

  • [found::SequentialPipeline<std::pair<found::EulerAngles, found::EulerAngles>, found::Quaternion, 1ul>::Run(std::pair<found::EulerAngles, found::EulerAngles> const&) (line 222)]
  • [found::SequentialPipeline<std::vector<found::LocationRecord, std::allocatorfound::LocationRecord >, std::vector<found::LocationRecord, std::allocatorfound::LocationRecord >, 2ul>::Run(std::vector<found::LocationRecord, std::allocatorfound::LocationRecord > const&) (line 222)]

Copy link
Collaborator

@nguy8tri nguy8tri left a comment

Choose a reason for hiding this comment

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

I was going to leave this to Josh, but I realized some confusion and tough issues warrant my input on this.

I want to first commend you on doing all this, its not easy to pick up a new code space and to run with it. That being said, there are many things that must be corrected. I think you will see that a lot of this could've been avoided by looking over the thourough documentation in FOUND (but who does that am I right haha?).

A summary of what I saw:

  1. I saw many opportunities for Dependency Mocking. Trust me, its worth your time, so take some time to learn how its used via the links in the comments.
  2. I also saw a lack of understanding around Dependency Injection Framework that's used in the code. No worries, that's not even taught in the 4 years (in my case 5) that you're at UW.
  3. Confusion around the coverage. That's understandable, we'll work through it together (for reference, we faced the same issue before, and it took me 3 months to figure it out)
  4. Unnecessary code that is good in theory but useless/unoptimized in production. That will take a while to shake itself out (I'm still doing that myself)
  5. Nitpicky style things to help you conform to the rest of the code.
  6. (Were you using AI? No pressure, I'm not your professor, and I use it a lot in my work as a software dev. However, some things seemed out of place so I thought I'd ask for curiosity sake.)

Anyways, I hope the comments here will be helpful! Please ask me any questions you might have!

/// The Vectorization/Rotation Algorithm being used
std::unique_ptr<VectorGenerationAlgorithm> vectorizationAlgorithm;
/// Optional edge-filtering pipeline (may be empty)
EdgeFilteringAlgorithms filters_;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Make std::unique_ptr and reorder

FOUND_CLI_OPTION("isdda-pdf-order" , int , ISDDAPdfOrd , 2 , atoi(optarg) , kNoDefaultArgument, REQ_ASSIGN, "The Probability Density Function Order for ISSDA (even int)" ) \
FOUND_CLI_OPTION("isdda-radius-loss-order" , int , ISDDARadLossOrd , 4 , atoi(optarg) , kNoDefaultArgument, REQ_ASSIGN, "The Radius Loss Order ISSDA (even int)" ) \
FOUND_CLI_OPTION("output-file" , std::string , outputFile , "" , optarg , kNoDefaultArgument, REQ_ASSIGN, "The output file (*.found)" ) \
FOUND_CLI_OPTION("enable-noop-edge-filter" , bool , enableNoOpEdgeFilter, false , found::strtobool(optarg) , true , OPT_ASSIGN, "Enable the NoOp edge filter (test/demo only)" ) \
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a note to remove this upon implementation of the first edge filtering algorithm. Since we do not use NoOp Algorithms, we should not encourage this pattern.

for (size_t i = 0; i < this->size; i++) {
dynamic_cast<ModifyingStage<T> *>(this->stages[i])->SetResource(*this->product);
auto *stage = static_cast<ModifyingStage<T> *>(this->stages[i]);
assert(stage != nullptr);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This check, although admirable, is impractical in nominal use cases, please get rid of the assertion and go straight for the SetResource operation (also meaning you don't need to store a variable)

I'm also interested to hear why you chose a static_cast instead of dynamic_cast. I think it could be justified, just as long as we also agree on it.

Copy link
Author

Choose a reason for hiding this comment

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

I believe I did this mainly just because of testing in pipelines and just put an assert there to make sure it was fine. Forgot to change it back later (I thought it may have been an issue with polymorphism or such).

}


TEST(EdgeFiltersTest, ProvideEdgeFilteringAlgorithm_OptionsDisabledReturnsNullopt) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

No direct invocations of Provide* or Create* in your test cases. These are Dependency Injection functions, and should indirectly invoked via the integration tests.

@MahirEmran MahirEmran requested a review from nguy8tri November 5, 2025 22:54
@nguy8tri nguy8tri changed the title Implementing Algorithm Superclass Feature: Implementing Algorithm Superclass Nov 9, 2025
Copy link
Collaborator

@nguy8tri nguy8tri left a comment

Choose a reason for hiding this comment

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

Good job on the changes. However there were a few changes thay weren't done, so I've explicitly marked them. Let me know if you have questions

this->edgeDetectionAlgorithm = std::move(edgeDetectionAlgorithm);
this->distanceAlgorithm = std::move(distanceAlgorithm);
this->vectorizationAlgorithm = std::move(vectorizationAlgorithm);
// Build the pipeline: edge detection -> filters (modifying Points) -> distance -> vectorize
Copy link
Collaborator

Choose a reason for hiding this comment

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

Get rid of comment and put in proper order

* @param options The DistanceOptions to configure the pipeline (moved into the executor)
* @param edgeDetectionAlgorithm The EdgeDetectionAlgorithm used by the pipeline (moved into the executor)
* @param distanceAlgorithm The DistanceDeterminationAlgorithm used by the pipeline (moved into the executor)
* @param vectorizationAlgorithm The VectorGenerationAlgorithm used by the pipeline (moved into the executor)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where did the preconditions go?

* @param filters A pipeline of edge filtering stages; ownership is transferred to the executor
* @param distanceAlgorithm The DistanceDeterminationAlgorithm used by the pipeline (moved into the executor)
* @param vectorizationAlgorithm The VectorGenerationAlgorithm used by the pipeline (moved into the executor)
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same note, where are my preconditions

public:
EdgeFilteringAlgorithm() = default;
virtual ~EdgeFilteringAlgorithm() = default;
// Implementations override void Run(Points &)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is understood remove comment

std::unique_ptr<EdgeDetectionAlgorithm> edgeAlg = ProvideEdgeDetectionAlgorithm(options);
std::unique_ptr<DistanceDeterminationAlgorithm> distAlg = ProvideDistanceDeterminationAlgorithm(options);
std::unique_ptr<VectorGenerationAlgorithm> vecAlg = ProvideVectorGenerationAlgorithm(options);
std::unique_ptr<EdgeFilteringAlgorithms> filtersOpt = ProvideEdgeFilteringAlgorithm(options);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Reorder

* used by providers when a no-op filter is requested. This will not be needed
* once an EdgeFilteringAlgorithm is implemented.
*/
class NoOpEdgeFilter : public ModifyingStage<Points> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is in the wrong file, please put this back at the edge-filter file

* Internal provider instances and singletons used by the stage provider
* functions
*/
namespace detail {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I should've been explicit here, but unless there is a point, never make another namespace

* A single shared instance of the NoOpEdgeFilter used by providers when the
* no-op edge filter is requested. Documented to satisfy Doxygen checks.
*/
inline NoOpEdgeFilter kNoOpEdgeFilter{};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be constructed in provider function

MOCK_METHOD(PositionVector, Run, (const PositionVector& points), (override));
};

class MockEdgeFilter : public ModifyingStage<Points> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Redundant class


namespace found {

TEST(EdgeFiltersTest, RunOnEmptyThrows) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Revaluate which test need to be here. Are we testing the behavior of the pipleine/stage or are we testing the behavior of our actual algorithm. Try removing these one by one to see which ones you need.

MahirEmran and others added 2 commits November 20, 2025 13:33
…, updating pipelines to own stages (and modifying executors/pipeline tests accordingly), updating documentation
@MahirEmran
Copy link
Author

I had to make a merge in executors-test.cpp because (maybe some commit between last commit and now) there was difference in how DistanceOptions obj was being constructed. I feel like the one that was there before was odd (set all the fields in constructor rather than initializing object, then explicitly setting fields after), so I just went with current changes.

Had to rerun documentation check because first check failed the juliandate time test both attempts.

@MahirEmran MahirEmran requested a review from nguy8tri November 20, 2025 22:00
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.

Integration: Create Algorithm Superclass

3 participants