Renaming of 'processor' to 'operator' to avoid name clash with EdgeProcessor, added edge operators.

This commit is contained in:
Matthias Koefferlein 2024-01-26 15:19:44 +01:00
parent bbb535a0bf
commit b7277631c3
4 changed files with 284 additions and 32 deletions

View File

@ -123,6 +123,102 @@ Class<gsi::EdgeFilterImpl> decl_EdgeFilterImpl ("db", "EdgeFilter",
"This class has been introduced in version 0.29.\n" "This class has been introduced in version 0.29.\n"
); );
// ---------------------------------------------------------------------------------
// EdgeProcessor binding
Class<shape_processor_impl<db::EdgeProcessorBase> > decl_EdgeProcessorBase ("db", "EdgeOperator",
shape_processor_impl<db::EdgeProcessorBase>::method_decls (true),
"@brief A generic edge-to-polygon operator\n"
"\n"
"Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own "
"operator class and pass an instance to \\Edges#processed method.\n"
"\n"
"Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edges derived from the input edge.\n"
"The output edge collection is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n"
"\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"Here is some example that shrinks every edge to half of the size, but does not change the position.\n"
"In this example the 'position' is defined by the center of the edge:"
"\n"
"@code\n"
"class ShrinkToHalf < RBA::EdgeOperator\n"
"\n"
" # Constructor\n"
" def initialize\n"
" self.is_isotropic_and_scale_invariant # scale or orientation do not matter\n"
" end\n"
" \n"
" # Shrink to half size\n"
" def process(edge)\n"
" shift = edge.bbox.center - RBA::Point::new # shift vector\n"
" return [ (edge.moved(-shift) * 0.5).moved(shift) ]\n"
" end\n"
"\n"
"end\n"
"\n"
"edges = ... # some Edges collection\n"
"shrinked_to_half = edges.processed(ShrinkToHalf::new)\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
Class<shape_processor_impl<db::EdgeToPolygonProcessorBase> > decl_EdgeToPolygonProcessor ("db", "EdgeToPolygonOperator",
shape_processor_impl<db::EdgeToPolygonProcessorBase>::method_decls (true),
"@brief A generic edge-to-polygon operator\n"
"\n"
"Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own "
"operator class and pass an instance to \\Edges#processed method.\n"
"\n"
"Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output polygons derived from the input edge.\n"
"The output region is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n"
"\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"For a basic example see the \\EdgeOperator class, with the exception that this incarnation has to deliver edges.\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
Class<shape_processor_impl<db::EdgeToEdgePairProcessorBase> > decl_EdgeToEdgePairProcessor ("db", "EdgeToEdgePairOperator",
shape_processor_impl<db::EdgeToEdgePairProcessorBase>::method_decls (true),
"@brief A generic edge-to-edge-pair operator\n"
"\n"
"Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own "
"operator class and pass an instance to \\Edges#processed method.\n"
"\n"
"Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edge pairs derived from the input edge.\n"
"The output edge pair collection is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n"
"\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"For a basic example see the \\EdgeOperator class, with the exception that this incarnation has to deliver edge pairs.\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
// --------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------
// Edges binding // Edges binding
@ -303,6 +399,28 @@ static void filter (db::Edges *r, const EdgeFilterImpl *f)
r->filter (*f); r->filter (*f);
} }
static db::Edges processed_ee (const db::Edges *r, const shape_processor_impl<db::EdgeProcessorBase> *f)
{
return r->processed (*f);
}
static void process_ee (db::Edges *r, const shape_processor_impl<db::EdgeProcessorBase> *f)
{
r->process (*f);
}
static db::EdgePairs processed_eep (const db::Edges *r, const shape_processor_impl<db::EdgeToEdgePairProcessorBase> *f)
{
return r->processed (*f);
}
static db::Region processed_ep (const db::Edges *r, const shape_processor_impl<db::EdgeToPolygonProcessorBase> *f)
{
db::Region out;
r->processed (out, *f);
return out;
}
static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse) static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse)
{ {
db::EdgeLengthFilter f (length, length + 1, inverse); db::EdgeLengthFilter f (length, length + 1, inverse);
@ -732,6 +850,30 @@ Class<db::Edges> decl_Edges (decl_dbShapeCollection, "db", "Edges",
"\n" "\n"
"This method has been introduced in version 0.29.\n" "This method has been introduced in version 0.29.\n"
) + ) +
method_ext ("process", &process_ee, gsi::arg ("process"),
"@brief Applies a generic edge processor in place (replacing the edges from the Edges collection)\n"
"See \\EdgeProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_ee, gsi::arg ("processed"),
"@brief Applies a generic edge processor and returns a processed copy\n"
"See \\EdgeProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_eep, gsi::arg ("processed"),
"@brief Applies a generic edge-to-edge-pair processor and returns an edge pair collection with the results\n"
"See \\EdgeToEdgePairProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_ep, gsi::arg ("processed"),
"@brief Applies a generic edge-to-polygon processor and returns an edge collection with the results\n"
"See \\EdgeToPolygonProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"), method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"),
"@brief Filters the edges by length\n" "@brief Filters the edges by length\n"
"Filters the edges in the edge collection by length. If \"inverse\" is false, only " "Filters the edges in the edge collection by length. If \"inverse\" is false, only "

View File

@ -130,20 +130,20 @@ Class<gsi::PolygonFilterImpl> decl_PolygonFilterImpl ("db", "PolygonFilter",
// --------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------
// PolygonProcessor binding // PolygonProcessor binding
Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("db", "PolygonProcessor", Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonOperator ("db", "PolygonOperator",
shape_processor_impl<db::PolygonProcessorBase>::method_decls (true), shape_processor_impl<db::PolygonProcessorBase>::method_decls (true),
"@brief A generic polygon processor adaptor\n" "@brief A generic polygon operator\n"
"\n" "\n"
"Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own "
"processors class and pass an instance to \\Region#process or \\Region#processed method.\n" "operator class and pass an instance to \\Region#process or \\Region#processed method.\n"
"\n" "\n"
"Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" "Conceptually, these methods take each polygon from the region and present it to the operators' 'process' method.\n"
"The result of this call is a list of zero to many output polygons derived from the input polygon.\n" "The result of this call is a list of zero to many output polygons derived from the input polygon.\n"
"The output region is the sum over all these individual results.\n" "The output region is the sum over all these individual results.\n"
"\n" "\n"
"The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n" "before using it.\n"
"\n" "\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant " "You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
@ -153,7 +153,7 @@ Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("d
"In this example the 'position' is defined by the center of the bounding box:" "In this example the 'position' is defined by the center of the bounding box:"
"\n" "\n"
"@code\n" "@code\n"
"class ShrinkToHalfProcessor < RBA::PolygonProcessor\n" "class ShrinkToHalf < RBA::PolygonOperator\n"
"\n" "\n"
" # Constructor\n" " # Constructor\n"
" def initialize\n" " def initialize\n"
@ -175,50 +175,50 @@ Class<shape_processor_impl<db::PolygonProcessorBase> > decl_PolygonProcessor ("d
"This class has been introduced in version 0.29.\n" "This class has been introduced in version 0.29.\n"
); );
Class<shape_processor_impl<db::PolygonToEdgeProcessorBase> > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeProcessor", Class<shape_processor_impl<db::PolygonToEdgeProcessorBase> > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeOperator",
shape_processor_impl<db::PolygonToEdgeProcessorBase>::method_decls (true), shape_processor_impl<db::PolygonToEdgeProcessorBase>::method_decls (true),
"@brief A generic polygon-to-edge processor adaptor\n" "@brief A generic polygon-to-edge operator\n"
"\n" "\n"
"Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own "
"processors class and pass an instance to \\Region#processed method.\n" "operator class and pass an instance to \\Region#processed method.\n"
"\n" "\n"
"Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edges derived from the input polygon.\n" "The result of this call is a list of zero to many output edges derived from the input polygon.\n"
"The output edge collection is the sum over all these individual results.\n" "The output edge collection is the sum over all these individual results.\n"
"\n" "\n"
"The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n" "before using it.\n"
"\n" "\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant " "You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n" "formation which is not always desired and blows up the hierarchy.\n"
"\n" "\n"
"For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edges.\n" "For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edges.\n"
"\n" "\n"
"This class has been introduced in version 0.29.\n" "This class has been introduced in version 0.29.\n"
); );
Class<shape_processor_impl<db::PolygonToEdgePairProcessorBase> > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairProcessor", Class<shape_processor_impl<db::PolygonToEdgePairProcessorBase> > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairOperator",
shape_processor_impl<db::PolygonToEdgePairProcessorBase>::method_decls (true), shape_processor_impl<db::PolygonToEdgePairProcessorBase>::method_decls (true),
"@brief A generic polygon-to-edge-pair processor adaptor\n" "@brief A generic polygon-to-edge-pair operator\n"
"\n" "\n"
"Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own "
"processors class and pass an instance to \\Region#processed method.\n" "operator class and pass an instance to \\Region#processed method.\n"
"\n" "\n"
"Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n" "The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n"
"The output edge pair collection is the sum over all these individual results.\n" "The output edge pair collection is the sum over all these individual results.\n"
"\n" "\n"
"The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n" "before using it.\n"
"\n" "\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant " "You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n" "formation which is not always desired and blows up the hierarchy.\n"
"\n" "\n"
"For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edge pairs.\n" "For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edge pairs.\n"
"\n" "\n"
"This class has been introduced in version 0.29.\n" "This class has been introduced in version 0.29.\n"
); );

View File

@ -45,6 +45,49 @@ class ParallelFilter < RBA::EdgeFilter
end end
class ShrinkToHalfEdgeOperator < RBA::EdgeOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
end
# Shrink to half size
def process(edge)
shift = edge.bbox.center - RBA::Point::new # shift vector
return [ (edge.moved(-shift) * 0.5).moved(shift) ]
end
end
class SomeEdgeToEdgePairOperator < RBA::EdgeToEdgePairOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
end
def process(edge)
box = edge.bbox
return [ RBA::EdgePair::new([ box.left, box.bottom, box.left, box.top ], [ box.right, box.bottom, box.right, box.top ]) ]
end
end
class SomeEdgeToPolygonOperator < RBA::EdgeToPolygonOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # scale or orientation do not matter
end
def process(edge)
box = edge.bbox
return [ RBA::Polygon::new(box) ]
end
end
class DBEdges_TestClass < TestBase class DBEdges_TestClass < TestBase
# Basics # Basics
@ -796,6 +839,73 @@ class DBEdges_TestClass < TestBase
end end
# Generic processors
def test_generic_processors_ee
# Some basic tests for the processor class
f = ShrinkToHalfEdgeOperator::new
assert_equal(f.wants_variants?, true)
f.wants_variants = false
assert_equal(f.wants_variants?, false)
assert_equal(f.requires_raw_input, false)
f.requires_raw_input = true
assert_equal(f.requires_raw_input, true)
assert_equal(f.result_is_merged, false)
f.result_is_merged = true
assert_equal(f.result_is_merged, true)
assert_equal(f.result_must_not_be_merged, false)
f.result_must_not_be_merged = true
assert_equal(f.result_must_not_be_merged, true)
# Smoke test
f.is_isotropic
f.is_scale_invariant
# Some application
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(0, 0, 100, 100))
edges.insert(RBA::Edge::new(200, 300, 200, 500))
assert_equal(edges.processed(ShrinkToHalfEdgeOperator::new).to_s, "(25,25;75,75);(200,350;200,450)")
assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)")
edges.process(ShrinkToHalfEdgeOperator::new)
assert_equal(edges.to_s, "(25,25;75,75);(200,350;200,450)")
end
# Generic processors
def test_generic_processors_eep
p = SomeEdgeToEdgePairOperator::new
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(0, 0, 100, 100))
edges.insert(RBA::Edge::new(200, 300, 200, 500))
assert_equal(edges.processed(p).to_s, "(0,0;0,100)/(100,0;100,100);(200,300;200,500)/(200,300;200,500)")
assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)")
end
# Generic processors
def test_generic_processors_ep
p = SomeEdgeToPolygonOperator::new
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(0, 0, 100, 100))
edges.insert(RBA::Edge::new(200, 300, 200, 500))
assert_equal(edges.processed(p).to_s, "(0,0;0,100;100,100;100,0);(200,300;200,300;200,500;200,500)")
assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)")
end
end end
load("test_epilogue.rb") load("test_epilogue.rb")

View File

@ -44,7 +44,7 @@ class TriangleFilter < RBA::PolygonFilter
end end
class ShrinkToHalfProcessor < RBA::PolygonProcessor class ShrinkToHalfOperator < RBA::PolygonOperator
# Constructor # Constructor
def initialize def initialize
@ -59,7 +59,7 @@ class ShrinkToHalfProcessor < RBA::PolygonProcessor
end end
class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor class SomePolygonToEdgePairOperator < RBA::PolygonToEdgePairOperator
# Constructor # Constructor
def initialize def initialize
@ -73,7 +73,7 @@ class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor
end end
class SomePolygonToEdgeProcessor < RBA::PolygonToEdgeProcessor class SomePolygonToEdgeOperator < RBA::PolygonToEdgeOperator
# Constructor # Constructor
def initialize def initialize
@ -1281,7 +1281,7 @@ class DBRegion_TestClass < TestBase
# Some basic tests for the processor class # Some basic tests for the processor class
f = ShrinkToHalfProcessor::new f = ShrinkToHalfOperator::new
assert_equal(f.wants_variants?, true) assert_equal(f.wants_variants?, true)
f.wants_variants = false f.wants_variants = false
assert_equal(f.wants_variants?, false) assert_equal(f.wants_variants?, false)
@ -1306,9 +1306,9 @@ class DBRegion_TestClass < TestBase
region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]]))
region.insert(RBA::Box::new(200, 0, 300, 100)) region.insert(RBA::Box::new(200, 0, 300, 100))
assert_equal(region.processed(ShrinkToHalfProcessor::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") assert_equal(region.processed(ShrinkToHalfOperator::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)")
assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)")
region.process(ShrinkToHalfProcessor::new) region.process(ShrinkToHalfOperator::new)
assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)")
end end
@ -1316,7 +1316,7 @@ class DBRegion_TestClass < TestBase
# Generic processors # Generic processors
def test_generic_processors_pep def test_generic_processors_pep
p = SomePolygonToEdgePairProcessor::new p = SomePolygonToEdgePairOperator::new
region = RBA::Region::new region = RBA::Region::new
@ -1331,7 +1331,7 @@ class DBRegion_TestClass < TestBase
# Generic processors # Generic processors
def test_generic_processors_pe def test_generic_processors_pe
p = SomePolygonToEdgeProcessor::new p = SomePolygonToEdgeOperator::new
region = RBA::Region::new region = RBA::Region::new