aboutsummaryrefslogtreecommitdiffstats
path: root/test/extractor
diff options
context:
space:
mode:
Diffstat (limited to 'test/extractor')
-rw-r--r--test/extractor/generic/test_constant.py21
-rw-r--r--test/extractor/generic/test_path.py13
-rw-r--r--test/extractor/generic/test_stat.py13
-rw-r--r--test/extractor/image/test_colors_spatial.py17
-rw-r--r--test/extractor/image/test_photometrics.py143
-rw-r--r--test/extractor/test_base.py13
-rw-r--r--test/extractor/test_builder.py21
-rw-r--r--test/extractor/test_preview.py29
8 files changed, 189 insertions, 81 deletions
diff --git a/test/extractor/generic/test_constant.py b/test/extractor/generic/test_constant.py
index bde3805..77ee02b 100644
--- a/test/extractor/generic/test_constant.py
+++ b/test/extractor/generic/test_constant.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import unittest
@@ -20,11 +15,11 @@ class TestConstant(unittest.TestCase):
def test_extract(self):
schema = '''
bse:author rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "true"^^xsd:boolean .
bse:comment rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "false"^^xsd:boolean .
'''
@@ -33,10 +28,10 @@ class TestConstant(unittest.TestCase):
(ns.bse.comment, 'the quick brown fox jumps over the lazy dog.'),
]
ext = Constant(schema, tuples)
- node = _node.Node(ns.bsfs.Entity, '') # Blank node
+ node = _node.Node(ns.bsn.Entity, '') # Blank node
p_author = ext.schema.predicate(ns.bse.author)
p_comment = ext.schema.predicate(ns.bse.comment)
- entity = ext.schema.node(ns.bsfs.Node).child(ns.bsfs.Entity)
+ entity = ext.schema.node(ns.bsfs.Node).child(ns.bsn.Entity)
string = ext.schema.literal(ns.bsfs.Literal).child(ns.xsd.string)
# baseline
self.assertSetEqual(set(ext.extract(node, None, (p_author, p_comment))),
@@ -55,11 +50,11 @@ class TestConstant(unittest.TestCase):
# schema compliance
schema = '''
bse:author rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "true"^^xsd:boolean .
bse:comment rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "false"^^xsd:boolean .
'''
@@ -80,13 +75,13 @@ class TestConstant(unittest.TestCase):
def test_eq(self):
schema_a = '''
bse:author rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "true"^^xsd:boolean .
'''
schema_b = '''
bse:comment rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "false"^^xsd:boolean .
'''
diff --git a/test/extractor/generic/test_path.py b/test/extractor/generic/test_path.py
index ae68686..0beb37e 100644
--- a/test/extractor/generic/test_path.py
+++ b/test/extractor/generic/test_path.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import unittest
@@ -31,17 +26,17 @@ class TestPath(unittest.TestCase):
self.assertEqual(Path().schema,
bsfs.schema.from_string(base.SCHEMA_PREAMBLE + '''
bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:File ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
+ bsfs:unique "true"^^xsd:boolean .
'''))
def test_extract(self):
ext = Path()
- node = _node.Node(ns.bsfs.File, '') # Blank node
+ node = _node.Node(ns.bsn.Entity, '') # Blank node
content = '/tmp/foo/bar'
p_filename = ext.schema.predicate(ns.bse.filename)
- entity = ext.schema.node(ns.bsfs.Node).child(ns.bsfs.Entity)
+ entity = ext.schema.node(ns.bsfs.Node).child(ns.bsn.Entity)
string = ext.schema.literal(ns.bsfs.Literal).child(ns.xsd.string)
# baseline
diff --git a/test/extractor/generic/test_stat.py b/test/extractor/generic/test_stat.py
index e5562d1..0e83e24 100644
--- a/test/extractor/generic/test_stat.py
+++ b/test/extractor/generic/test_stat.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import os
import unittest
@@ -32,17 +27,17 @@ class TestStat(unittest.TestCase):
self.assertEqual(Stat().schema,
bsfs.schema.from_string(base.SCHEMA_PREAMBLE + '''
bse:filesize rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:File ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:integer ;
- bsfs:unique "false"^^xsd:boolean .
+ bsfs:unique "true"^^xsd:boolean .
'''))
def test_extract(self):
ext = Stat()
- node = _node.Node(ns.bsfs.File, '') # Blank node
+ node = _node.Node(ns.bsn.Entity, '') # Blank node
content = os.stat(__file__)
p_filesize = ext.schema.predicate(ns.bse.filesize)
- entity = ext.schema.node(ns.bsfs.Node).child(ns.bsfs.Entity)
+ entity = ext.schema.node(ns.bsfs.Node).child(ns.bsn.Entity)
string = ext.schema.literal(ns.bsfs.Literal).child(ns.xsd.string)
# baseline
diff --git a/test/extractor/image/test_colors_spatial.py b/test/extractor/image/test_colors_spatial.py
index ba551f3..902ab6d 100644
--- a/test/extractor/image/test_colors_spatial.py
+++ b/test/extractor/image/test_colors_spatial.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import os
import unittest
@@ -24,8 +19,8 @@ from bsie.extractor.image.colors_spatial import ColorsSpatial
class TestColorsSpatial(unittest.TestCase):
def setUp(self):
# content id with default constructors (width=32, height=32, exp=4)
- self.instance_prefix = 'http://ie.bsfs.ai/schema/Feature/ColorsSpatial'
- self.predicate_prefix = 'http://bsfs.ai/schema/Entity/colors_spatial'
+ self.instance_prefix = 'https://schema.bsfs.io/ie/Literal/Array/Feature/ColorsSpatial'
+ self.predicate_prefix = 'https://schema.bsfs.io/ie/Node/Entity#colors_spatial_'
self.uuid = 'adee8d6c43687021e1c5bffe56bcfe727f1638d792744137181304ef889dac2a'
def test_essentials(self):
@@ -55,7 +50,7 @@ class TestColorsSpatial(unittest.TestCase):
def test_schema(self):
schema = bsfs.schema.from_string(base.SCHEMA_PREAMBLE + f'''
- <{self.instance_prefix}> rdfs:subClassOf bsfs:Feature ;
+ <{self.instance_prefix}> rdfs:subClassOf bsa:Feature ;
# annotations
rdfs:label "Spatially dominant colors"^^xsd:string ;
schema:description "Domiant colors of subregions in an image."^^xsd:string ;
@@ -68,8 +63,8 @@ class TestColorsSpatial(unittest.TestCase):
<{self.instance_prefix}/args#height> "32"^^xsd:integer ;
<{self.instance_prefix}/args#exp> "4"^^xsd:float .
- <{self.predicate_prefix}#{self.uuid}> rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:File ;
+ <{self.predicate_prefix}{self.uuid}> rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsn:Entity ;
rdfs:range <{self.instance_prefix}#{self.uuid}> ;
bsfs:unique "true"^^xsd:boolean .
''')
@@ -78,7 +73,7 @@ class TestColorsSpatial(unittest.TestCase):
def test_extract(self):
ext = ColorsSpatial(2,2,2)
img = PIL.Image.open(os.path.join(os.path.dirname(__file__), 'testimage.jpg'))
- node = _node.Node(ns.bsfs.Entity, bsfs.URI('http://example.com/entity#1234'))
+ node = _node.Node(ns.bsn.Entity, bsfs.URI('http://example.com/entity#1234'))
principals = set(ext.principals)
self.assertEqual(len(principals), 1)
# valid invocation yields feature
diff --git a/test/extractor/image/test_photometrics.py b/test/extractor/image/test_photometrics.py
new file mode 100644
index 0000000..fb219e2
--- /dev/null
+++ b/test/extractor/image/test_photometrics.py
@@ -0,0 +1,143 @@
+
+# standard imports
+import unittest
+
+# bsie imports
+from bsie.extractor import base
+from bsie.utils import bsfs, node as _node, ns
+
+# objects to test
+from bsie.extractor.image.photometrics import Exif, _gps_to_dec
+
+
+## code ##
+
+class TestExif(unittest.TestCase):
+
+ def test_gps_to_dec(self):
+ # deg+min+sec format
+ self.assertAlmostEqual(_gps_to_dec('29/1 58/1 45/1'.split()), 29.979167, 6)
+ self.assertAlmostEqual(_gps_to_dec('31 08 03'.split()), 31.134167, 6)
+ self.assertAlmostEqual(_gps_to_dec('20 40 586/10'.split()), 20.682944, 6)
+ self.assertAlmostEqual(_gps_to_dec('88/1 34 68/10'.split()), 88.568556, 6)
+ # deg+min format
+ self.assertAlmostEqual(_gps_to_dec('13 472167/10000 0/1 '.split()), 13.786945, 6)
+ self.assertAlmostEqual(_gps_to_dec('104/1 3215/100 0/1'.split()), 104.535833, 6)
+
+ def test_eq(self):
+ # identical instances are equal
+ self.assertEqual(Exif(), Exif())
+ self.assertEqual(hash(Exif()), hash(Exif()))
+ # comparison respects type
+ class Foo(): pass
+ self.assertNotEqual(Exif(), Foo())
+ self.assertNotEqual(hash(Exif()), hash(Foo()))
+ self.assertNotEqual(Exif(), 1234)
+ self.assertNotEqual(hash(Exif()), hash(1234))
+ self.assertNotEqual(Exif(), None)
+ self.assertNotEqual(hash(Exif()), hash(None))
+
+ def test_schema(self):
+ self.assertSetEqual({pred.uri for pred in Exif().schema.predicates()}, {
+ ns.bsfs.Predicate,
+ ns.bse.exposure,
+ ns.bse.aperture,
+ ns.bse.iso,
+ ns.bse.focal_length,
+ ns.bse.width,
+ ns.bse.height,
+ ns.bse.orientation,
+ ns.bse.orientation_label,
+ ns.bse.altitude,
+ ns.bse.latitude,
+ ns.bse.longitude,
+ })
+
+ def test_extract(self):
+ ext = Exif()
+ node = _node.Node(ns.bsfs.File, '') # Blank node
+ content = {
+ 'Exif.Photo.ExposureTime': '10/600',
+ 'Exif.Photo.FNumber': '48/10',
+ 'Exif.Photo.ISOSpeedRatings': '400',
+ 'Exif.Photo.FocalLength': '460/10',
+ 'Exif.Photo.PixelXDimension': '4288',
+ 'Exif.Photo.PixelYDimension': '2848',
+ 'Exif.Image.Orientation': '1',
+ 'Exif.GPSInfo.GPSAltitude': '431/1',
+ 'Exif.GPSInfo.GPSLatitude': '46/1 11397/625 0/1',
+ 'Exif.GPSInfo.GPSLongitude': '7/1 131250/2500 0/1',
+ }
+
+ # invalid principals are ignored
+ self.assertSetEqual(set(ext.extract(node, content, {ns.bse.filename})), set())
+ # extract finds all relevant information
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.exposure)})),
+ {(node, ext.schema.predicate(ns.bse.exposure), 60.0)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.aperture)})),
+ {(node, ext.schema.predicate(ns.bse.aperture), 4.8)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.iso)})),
+ {(node, ext.schema.predicate(ns.bse.iso), 400)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.focal_length)})),
+ {(node, ext.schema.predicate(ns.bse.focal_length), 46.0)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.width)})),
+ {(node, ext.schema.predicate(ns.bse.width), 4288)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.height)})),
+ {(node, ext.schema.predicate(ns.bse.height), 2848)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.orientation)})),
+ {(node, ext.schema.predicate(ns.bse.orientation), 1)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.orientation_label)})),
+ {(node, ext.schema.predicate(ns.bse.orientation_label), 'landscape')})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.altitude)})),
+ {(node, ext.schema.predicate(ns.bse.altitude), 431.0)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.latitude)})),
+ {(node, ext.schema.predicate(ns.bse.latitude), 46.30392)})
+ self.assertSetEqual(set(ext.extract(node, content, {ext.schema.predicate(ns.bse.longitude)})),
+ {(node, ext.schema.predicate(ns.bse.longitude), 7.875)})
+
+ # check orientation label
+ self.assertSetEqual(set(ext.extract(
+ node, {
+ 'Exif.Photo.PixelXDimension': '4288',
+ 'Exif.Photo.PixelYDimension': '2848',
+ 'Exif.Image.Orientation': '5',
+ },
+ {ext.schema.predicate(ns.bse.orientation_label)})),
+ {(node, ext.schema.predicate(ns.bse.orientation_label), 'portrait')})
+
+ # can pass multiple principals
+ self.assertSetEqual(set(ext.extract(node, content, {
+ ext.schema.predicate(ns.bse.exposure),
+ ext.schema.predicate(ns.bse.iso),
+ ext.schema.predicate(ns.bse.focal_length),
+ })), {
+ (node, ext.schema.predicate(ns.bse.exposure), 60.0),
+ (node, ext.schema.predicate(ns.bse.iso), 400),
+ (node, ext.schema.predicate(ns.bse.focal_length), 46.0),
+ })
+
+ # principals w/o content are ignored
+ self.assertSetEqual(set(ext.extract(
+ node,
+ content={'Exif.Photo.ExposureTime': '10/600'},
+ principals={
+ ext.schema.predicate(ns.bse.exposure),
+ ext.schema.predicate(ns.bse.iso),
+ ext.schema.predicate(ns.bse.focal_length),
+ })
+ ), {
+ (node, ext.schema.predicate(ns.bse.exposure), 60.0),
+ })
+
+ # empty content is acceptable
+ self.assertSetEqual(set(ext.extract(node, {}, set(ext.principals))), set())
+ # no principals is acceptable
+ self.assertSetEqual(set(ext.extract(node, content, set())), set())
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/extractor/test_base.py b/test/extractor/test_base.py
index acfaf58..81865e1 100644
--- a/test/extractor/test_base.py
+++ b/test/extractor/test_base.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import unittest
@@ -20,11 +15,11 @@ class StubExtractor(base.Extractor):
def __init__(self):
super().__init__(bsfs.schema.from_string(base.SCHEMA_PREAMBLE + '''
bse:author rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "false"^^xsd:boolean .
bse:comment rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "false"^^xsd:boolean .
'''))
@@ -53,8 +48,8 @@ class TestExtractor(unittest.TestCase):
def test_principals(self):
schema = bsfs.schema.Schema()
- entity = schema.node(ns.bsfs.Node).child(ns.bsfs.Entity)
- string = schema.literal(ns.bsfs.Literal).child(bsfs.URI('http://www.w3.org/2001/XMLSchema#string'))
+ entity = schema.node(ns.bsfs.Node).child(ns.bsn.Entity)
+ string = schema.literal(ns.bsfs.Literal).child(ns.xsd.string)
p_author = schema.predicate(ns.bsfs.Predicate).child(ns.bse.author, domain=entity, range=string)
p_comment = schema.predicate(ns.bsfs.Predicate).child(ns.bse.comment, domain=entity, range=string)
ext = StubExtractor()
diff --git a/test/extractor/test_builder.py b/test/extractor/test_builder.py
index 039ea53..fbb0895 100644
--- a/test/extractor/test_builder.py
+++ b/test/extractor/test_builder.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import unittest
@@ -51,33 +46,33 @@ class TestExtractorBuilder(unittest.TestCase):
{'bsie.extractor.generic.constant.Constant': {
'schema': '''
bse:author rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "true"^^xsd:boolean .
bse:rating rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
''',
'tuples': [
- ('http://bsfs.ai/schema/Entity#author', 'Me, myself, and I'),
- ('http://bsfs.ai/schema/Entity#rating', 123),
+ ('https://schema.bsfs.io/ie/Node/Entity#author', 'Me, myself, and I'),
+ ('https://schema.bsfs.io/ie/Node/Entity#rating', 123),
],
}}])
obj = builder.build(0)
import bsie.extractor.generic.constant
self.assertEqual(obj, bsie.extractor.generic.constant.Constant('''
bse:author rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:string ;
bsfs:unique "true"^^xsd:boolean .
bse:rating rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
+ rdfs:domain bsn:Entity ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
''', [
- ('http://bsfs.ai/schema/Entity#author', 'Me, myself, and I'),
- ('http://bsfs.ai/schema/Entity#rating', 123),
+ ('https://schema.bsfs.io/ie/Node/Entity#author', 'Me, myself, and I'),
+ ('https://schema.bsfs.io/ie/Node/Entity#rating', 123),
]))
# building with invalid args
diff --git a/test/extractor/test_preview.py b/test/extractor/test_preview.py
index 10d2a7f..6526783 100644
--- a/test/extractor/test_preview.py
+++ b/test/extractor/test_preview.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsie test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import io
import os
@@ -45,28 +40,28 @@ class TestPreview(unittest.TestCase):
def test_schema(self):
self.assertEqual(Preview([1,2,3]).schema,
bsfs.schema.from_string(base.SCHEMA_PREAMBLE + '''
- bsfs:Preview rdfs:subClassOf bsfs:Node .
- bsfs:BinaryBlob rdfs:subClassOf bsfs:Literal .
- bsfs:JPEG rdfs:subClassOf bsfs:BinaryBlob .
+ bsn:Preview rdfs:subClassOf bsfs:Node .
+ bsl:BinaryBlob rdfs:subClassOf bsfs:Literal .
+ <https://schema.bsfs.io/ie/Literal/BinaryBlob/JPEG> rdfs:subClassOf bsl:BinaryBlob .
bse:preview rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:File ;
- rdfs:range bsfs:Preview ;
+ rdfs:domain bsn:Entity ;
+ rdfs:range bsn:Preview ;
bsfs:unique "false"^^xsd:boolean .
bsp:width rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Preview ;
+ rdfs:domain bsn:Preview ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
bsp:height rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Preview ;
+ rdfs:domain bsn:Preview ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
bsp:asset rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Preview ;
- rdfs:range bsfs:JPEG ;
+ rdfs:domain bsn:Preview ;
+ rdfs:range <https://schema.bsfs.io/ie/Literal/BinaryBlob/JPEG> ;
bsfs:unique "true"^^xsd:boolean .
'''))
@@ -74,7 +69,7 @@ class TestPreview(unittest.TestCase):
def test_extract(self):
# setup dependents
rdr = Reader()
- subject = _node.Node(ns.bsfs.File)
+ subject = _node.Node(ns.bsn.Entity)
path = os.path.join(os.path.dirname(__file__), 'testimage.jpg')
# setup extractor
@@ -88,7 +83,7 @@ class TestPreview(unittest.TestCase):
gen(10) # NOTE: consume some image to avoid resource error warning
# extract a preview
triples = set(ext.extract(subject, rdr(path), principals))
- thumbs = {node for node, _, _ in triples if node.node_type == ns.bsfs.Preview}
+ thumbs = {node for node, _, _ in triples if node.node_type == ns.bsn.Preview}
self.assertEqual(len(thumbs), 1)
thumb = list(thumbs)[0]
# test properties
@@ -112,7 +107,7 @@ class TestPreview(unittest.TestCase):
self.assertEqual(principals, {ext.schema.predicate(ns.bse.preview)})
# extract a preview
triples = set(ext.extract(subject, rdr(path), principals))
- thumbs = {node for node, _, _ in triples if node.node_type == ns.bsfs.Preview}
+ thumbs = {node for node, _, _ in triples if node.node_type == ns.bsn.Preview}
self.assertEqual(len(thumbs), 2)
self.assertSetEqual({10, 20}, {
value for _, pred, value in triples if pred == ext.schema.predicate(ns.bsp.width)})