Skip to content

Commit 65ffe2a

Browse files
Enforce stricter interval algebra rules for overlaps and contains (#760)
* Create python-package.yml * Update flake8 config * Enforce stricter interval algebra rules for overlaps operator and update trackAlgorithm accordingly. * Enforce stricter interval algebra rules for contains operator and update trackAlgorithm accordingly * Minor fixes * Add intersects predicate, constexpr functions to compare time and reformatting * Make greater_than() & less_than() just inline * Delete python-package.yml Co-authored-by: Nick Porcino <meshula@hotmail.com>
1 parent 887575a commit 65ffe2a

File tree

5 files changed

+155
-52
lines changed

5 files changed

+155
-52
lines changed

src/opentime/timeRange.h

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace opentime {
1818
*/
1919

2020
/**
21-
* This default epsilon value is used in comparison between floating numbers.
21+
* This default epsilon_s value is used in comparison between floating numbers.
2222
* It is computed to be twice 192khz, the fastest commonly used audio rate.
2323
* It can be changed in the future if necessary due to higher sampling rates
2424
* or some other kind of numeric tolerance detected in the library.
@@ -92,8 +92,9 @@ class TimeRange {
9292
*/
9393

9494
/**
95-
* In the relations that follow, epsilon indicates the tolerance,in the sense that if abs(a-b) < epsilon,
96-
* we consider a and b to be equal
95+
* In the relations that follow, epsilon_s indicates the tolerance,in the sense that if abs(a-b) < epsilon_s,
96+
* we consider a and b to be equal.
97+
* The time comparison is done in double seconds.
9798
*/
9899

99100
/**
@@ -117,8 +118,12 @@ class TimeRange {
117118
* The converse would be <em>other.contains(this)</em>
118119
* @param other
119120
*/
120-
bool contains(TimeRange other) const {
121-
return _start_time <= other._start_time && end_time_exclusive() >= other.end_time_exclusive();
121+
bool contains(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
122+
double thisStart = _start_time.to_seconds();
123+
double thisEnd = end_time_exclusive().to_seconds();
124+
double otherStart = other._start_time.to_seconds();
125+
double otherEnd = other.end_time_exclusive().to_seconds();
126+
return greater_than(otherStart, thisStart, epsilon_s) && lesser_than(otherEnd, thisEnd, epsilon_s);
122127
}
123128

124129
/**
@@ -134,48 +139,49 @@ class TimeRange {
134139
}
135140

136141
/**
137-
* The start of <b>this</b> strictly precedes end of <b>other</b> by a value >= <b>epsilon</b>.
138-
* The end of <b>this</b> strictly antecedes start of <b>other</b> by a value >= <b>epsilon</b>.
142+
* The start of <b>this</b> strictly precedes end of <b>other</b> by a value >= <b>epsilon_s</b>.
143+
* The end of <b>this</b> strictly antecedes start of <b>other</b> by a value >= <b>epsilon_s</b>.
139144
* [ this ]
140145
* [ other ]
141146
* The converse would be <em>other.overlaps(this)</em>
142147
* @param other
143-
* @param epsilon
148+
* @param epsilon_s
144149
*/
145-
bool overlaps(TimeRange other, double epsilon = DEFAULT_EPSILON_s) const {
150+
bool overlaps(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
146151
double thisStart = _start_time.to_seconds();
147152
double thisEnd = end_time_exclusive().to_seconds();
148153
double otherStart = other._start_time.to_seconds();
149154
double otherEnd = other.end_time_exclusive().to_seconds();
150-
return (otherEnd - thisStart >= epsilon) &&
151-
(thisEnd - otherStart >= epsilon);
155+
return lesser_than(thisStart, otherStart, epsilon_s) &&
156+
greater_than(thisEnd, otherStart, epsilon_s) &&
157+
greater_than(otherEnd, thisEnd, epsilon_s);
152158
}
153159

154160
/**
155-
* The end of <b>this</b> strictly precedes the start of <b>other</b> by a value >= <b>epsilon</b>.
161+
* The end of <b>this</b> strictly precedes the start of <b>other</b> by a value >= <b>epsilon_s</b>.
156162
* [ this ] [ other ]
157163
* The converse would be <em>other.before(this)</em>
158164
* @param other
159-
* @param epsilon
165+
* @param epsilon_s
160166
*/
161-
bool before(TimeRange other, double epsilon = DEFAULT_EPSILON_s) const {
167+
bool before(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
162168
double thisEnd = end_time_exclusive().to_seconds();
163169
double otherStart = other._start_time.to_seconds();
164-
return otherStart - thisEnd >= epsilon;
170+
return greater_than(otherStart, thisEnd, epsilon_s);
165171
}
166172

167173
/**
168-
* The end of <b>this</b> strictly precedes <b>other</b> by a value >= <b>epsilon</b>.
174+
* The end of <b>this</b> strictly precedes <b>other</b> by a value >= <b>epsilon_s</b>.
169175
* other
170176
* ↓
171177
* [ this ] *
172178
* @param other
173-
* @param epsilon
179+
* @param epsilon_s
174180
*/
175-
bool before(RationalTime other, double epsilon = DEFAULT_EPSILON_s) const {
181+
bool before(RationalTime other, double epsilon_s = DEFAULT_EPSILON_s) const {
176182
double thisEnd = end_time_exclusive().to_seconds();
177183
double otherTime = other.to_seconds();
178-
return otherTime - thisEnd >= epsilon;
184+
return lesser_than(thisEnd, otherTime, epsilon_s);
179185
}
180186

181187
/**
@@ -184,29 +190,29 @@ class TimeRange {
184190
* [this][other]
185191
* The converse would be <em>other.meets(this)</em>
186192
* @param other
187-
* @param epsilon
193+
* @param epsilon_s
188194
*/
189-
bool meets(TimeRange other, double epsilon = DEFAULT_EPSILON_s) const {
195+
bool meets(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
190196
double thisEnd = end_time_exclusive().to_seconds();
191197
double otherStart = other._start_time.to_seconds();
192-
return otherStart - thisEnd <= epsilon && otherStart - thisEnd >= 0;
198+
return otherStart - thisEnd <= epsilon_s && otherStart - thisEnd >= 0;
193199
}
194200

195201
/**
196202
* The start of <b>this</b> strictly equals the start of <b>other</b>.
197-
* The end of <b>this</b> strictly precedes the end of <b>other</b> by a value >= <b>epsilon</b>.
203+
* The end of <b>this</b> strictly precedes the end of <b>other</b> by a value >= <b>epsilon_s</b>.
198204
* [ this ]
199205
* [ other ]
200206
* The converse would be <em>other.begins(this)</em>
201207
* @param other
202-
* @param epsilon
208+
* @param epsilon_s
203209
*/
204-
bool begins(TimeRange other, double epsilon = DEFAULT_EPSILON_s) const {
210+
bool begins(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
205211
double thisStart = _start_time.to_seconds();
206212
double thisEnd = end_time_exclusive().to_seconds();
207213
double otherStart = other._start_time.to_seconds();
208214
double otherEnd = other.end_time_exclusive().to_seconds();
209-
return fabs(otherStart - thisStart) <= epsilon && otherEnd - thisEnd >= epsilon;
215+
return fabs(otherStart - thisStart) <= epsilon_s && lesser_than(thisEnd, otherEnd, epsilon_s);
210216
}
211217

212218
/**
@@ -217,27 +223,27 @@ class TimeRange {
217223
* [ this ]
218224
* @param other
219225
*/
220-
bool begins(RationalTime other, double epsilon = DEFAULT_EPSILON_s) const {
226+
bool begins(RationalTime other, double epsilon_s = DEFAULT_EPSILON_s) const {
221227
double thisStart = _start_time.to_seconds();
222228
double otherStart = other.to_seconds();
223-
return fabs(otherStart - thisStart) <= epsilon;
229+
return fabs(otherStart - thisStart) <= epsilon_s;
224230
}
225231

226232
/**
227-
* The start of <b>this</b> strictly antecedes the start of <b>other</b> by a value >= <b>epsilon</b>.
233+
* The start of <b>this</b> strictly antecedes the start of <b>other</b> by a value >= <b>epsilon_s</b>.
228234
* The end of <b>this</b> strictly equals the end of <b>other</b>.
229235
* [ this ]
230236
* [ other ]
231237
* The converse would be <em>other.finishes(this)</em>
232238
* @param other
233-
* @param epsilon
239+
* @param epsilon_s
234240
*/
235-
bool finishes(TimeRange other, double epsilon = DEFAULT_EPSILON_s) const {
241+
bool finishes(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
236242
double thisStart = _start_time.to_seconds();
237243
double thisEnd = end_time_exclusive().to_seconds();
238244
double otherStart = other._start_time.to_seconds();
239245
double otherEnd = other.end_time_exclusive().to_seconds();
240-
return fabs(thisEnd - otherEnd) <= epsilon && thisStart - otherStart >= epsilon;
246+
return fabs(thisEnd - otherEnd) <= epsilon_s && greater_than(thisStart, otherStart, epsilon_s);
241247
}
242248

243249
/**
@@ -247,12 +253,29 @@ class TimeRange {
247253
* *
248254
* [ this ]
249255
* @param other
250-
* @param epsilon
256+
* @param epsilon_s
251257
*/
252-
bool finishes(RationalTime other, double epsilon = DEFAULT_EPSILON_s) const {
258+
bool finishes(RationalTime other, double epsilon_s = DEFAULT_EPSILON_s) const {
253259
double thisEnd = end_time_exclusive().to_seconds();
254260
double otherEnd = other.to_seconds();
255-
return fabs(thisEnd - otherEnd) <= epsilon;
261+
return fabs(thisEnd - otherEnd) <= epsilon_s;
262+
}
263+
264+
/**
265+
* The start of <b>this</b> precedes or equals the end of <b>other</b> by a value >= <b>epsilon_s</b>.
266+
* The end of <b>this</b> antecedes or equals the start of <b>other</b> by a value >= <b>epsilon_s</b>.
267+
* [ this ] OR [ other ]
268+
* [ other ] [ this ]
269+
* The converse would be <em>other.finishes(this)</em>
270+
* @param other
271+
* @param epsilon_s
272+
*/
273+
bool intersects(TimeRange other, double epsilon_s = DEFAULT_EPSILON_s) const {
274+
double thisStart = _start_time.to_seconds();
275+
double thisEnd = end_time_exclusive().to_seconds();
276+
double otherStart = other._start_time.to_seconds();
277+
double otherEnd = other.end_time_exclusive().to_seconds();
278+
return lesser_than(thisStart, otherEnd, epsilon_s) && greater_than(thisEnd, otherStart, epsilon_s);
256279
}
257280

258281

@@ -288,6 +311,14 @@ class TimeRange {
288311
private:
289312
RationalTime _start_time, _duration;
290313
friend class TimeTransform;
314+
315+
inline bool greater_than(double lhs, double rhs, double epsilon) const{
316+
return lhs - rhs >= epsilon;
317+
}
318+
319+
inline bool lesser_than(double lhs, double rhs, double epsilon) const{
320+
return rhs - lhs >= epsilon;
321+
}
291322
};
292323

293324
} }

src/opentimelineio/trackAlgorithm.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Track* track_trimmed_to_range(Track* in_track, TimeRange trim_range, ErrorStatus
2727
}
2828

2929
auto child_range = child_range_it->second;
30-
if (!trim_range.overlaps(child_range)) {
30+
if (!trim_range.intersects(child_range)) {
3131
new_track->remove_child(static_cast<int>(i), error_status);
3232
if (*error_status) {
3333
return nullptr;

src/py-opentimelineio/opentime-bindings/opentime_timeRange.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,17 @@ void opentime_timeRange_bindings(py::module m) {
4343
.def("clamped", (RationalTime (TimeRange::*)(RationalTime) const) &TimeRange::clamped, "other"_a)
4444
.def("clamped", (TimeRange (TimeRange::*)(TimeRange) const) &TimeRange::clamped, "other"_a)
4545
.def("contains", (bool (TimeRange::*)(RationalTime) const) &TimeRange::contains, "other"_a)
46-
.def("contains", (bool (TimeRange::*)(TimeRange) const) &TimeRange::contains, "other"_a)
46+
.def("contains", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::contains, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
4747
.def("overlaps", (bool (TimeRange::*)(RationalTime) const) &TimeRange::overlaps, "other"_a)
48-
.def("overlaps", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::overlaps, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
49-
.def("before", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::before, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
50-
.def("before", (bool (TimeRange::*)(RationalTime, double ) const) &TimeRange::before, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
51-
.def("meets", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::meets, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
52-
.def("begins", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::begins, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
53-
.def("begins", (bool (TimeRange::*)(RationalTime, double) const) &TimeRange::begins, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
54-
.def("finishes", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::finishes, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
55-
.def("finishes", (bool (TimeRange::*)(RationalTime, double) const) &TimeRange::finishes, "other"_a, "epsilon"_a=opentime::DEFAULT_EPSILON_s)
48+
.def("overlaps", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::overlaps, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
49+
.def("before", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::before, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
50+
.def("before", (bool (TimeRange::*)(RationalTime, double ) const) &TimeRange::before, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
51+
.def("meets", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::meets, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
52+
.def("begins", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::begins, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
53+
.def("begins", (bool (TimeRange::*)(RationalTime, double) const) &TimeRange::begins, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
54+
.def("finishes", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::finishes, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
55+
.def("finishes", (bool (TimeRange::*)(RationalTime, double) const) &TimeRange::finishes, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
56+
.def("intersects", (bool (TimeRange::*)(TimeRange, double) const) &TimeRange::intersects, "other"_a, "epsilon_s"_a=opentime::DEFAULT_EPSILON_s)
5657
.def("__copy__", [](TimeRange tr) {
5758
return tr;
5859
})

src/py-opentimelineio/opentimelineio/algorithms/track_algo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def track_trimmed_to_range(in_track, trim_range):
4747
# iterate backwards so we can delete items
4848
for c, child in reversed(list(enumerate(new_track))):
4949
child_range = track_map[child]
50-
if not trim_range.overlaps(child_range):
50+
if not trim_range.intersects(child_range):
5151
# completely outside the trim range, so we discard it
5252
del new_track[c]
5353
elif trim_range.contains(child_range):

tests/test_opentime.py

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ def test_contains(self):
886886
self.assertFalse(tr.contains(tstart + tdur))
887887
self.assertFalse(tr.contains(tstart - tdur))
888888

889-
self.assertTrue(tr.contains(tr))
889+
self.assertFalse(tr.contains(tr))
890890

891891
tr_2 = otio.opentime.TimeRange(tstart - tdur, tdur)
892892
self.assertFalse(tr.contains(tr_2))
@@ -915,25 +915,25 @@ def test_overlaps_timerange(self):
915915
tdur = otio.opentime.RationalTime(3, 25)
916916
tr_t = otio.opentime.TimeRange(tstart, tdur)
917917

918-
self.assertTrue(tr.overlaps(tr_t))
918+
self.assertFalse(tr.overlaps(tr_t))
919919

920920
tstart = otio.opentime.RationalTime(13, 25)
921921
tdur = otio.opentime.RationalTime(1, 25)
922922
tr_t = otio.opentime.TimeRange(tstart, tdur)
923923

924-
self.assertTrue(tr.overlaps(tr_t))
924+
self.assertFalse(tr.overlaps(tr_t))
925925

926926
tstart = otio.opentime.RationalTime(2, 25)
927927
tdur = otio.opentime.RationalTime(30, 25)
928928
tr_t = otio.opentime.TimeRange(tstart, tdur)
929929

930-
self.assertTrue(tr.overlaps(tr_t))
930+
self.assertFalse(tr.overlaps(tr_t))
931931

932932
tstart = otio.opentime.RationalTime(2, 50)
933933
tdur = otio.opentime.RationalTime(60, 50)
934934
tr_t = otio.opentime.TimeRange(tstart, tdur)
935935

936-
self.assertTrue(tr.overlaps(tr_t))
936+
self.assertFalse(tr.overlaps(tr_t))
937937

938938
tstart = otio.opentime.RationalTime(2, 50)
939939
tdur = otio.opentime.RationalTime(14, 50)
@@ -945,14 +945,85 @@ def test_overlaps_timerange(self):
945945
tdur = otio.opentime.RationalTime(400, 50)
946946
tr_t = otio.opentime.TimeRange(tstart, tdur)
947947

948-
self.assertTrue(tr.overlaps(tr_t))
948+
self.assertFalse(tr.overlaps(tr_t))
949949

950950
tstart = otio.opentime.RationalTime(100, 50)
951951
tdur = otio.opentime.RationalTime(400, 50)
952952
tr_t = otio.opentime.TimeRange(tstart, tdur)
953953

954954
self.assertFalse(tr.overlaps(tr_t))
955955

956+
def test_intersects_timerange(self):
957+
tstart = otio.opentime.RationalTime(12, 25)
958+
tdur = otio.opentime.RationalTime(3, 25)
959+
tr = otio.opentime.TimeRange(tstart, tdur)
960+
961+
tstart = otio.opentime.RationalTime(0, 25)
962+
tdur = otio.opentime.RationalTime(3, 25)
963+
tr_t = otio.opentime.TimeRange(tstart, tdur)
964+
965+
self.assertFalse(tr.intersects(tr_t))
966+
967+
tstart = otio.opentime.RationalTime(10, 25)
968+
tdur = otio.opentime.RationalTime(3, 25)
969+
tr_t = otio.opentime.TimeRange(tstart, tdur)
970+
971+
self.assertTrue(tr.intersects(tr_t))
972+
973+
tstart = otio.opentime.RationalTime(10, 25)
974+
tdur = otio.opentime.RationalTime(2, 25)
975+
tr_t = otio.opentime.TimeRange(tstart, tdur)
976+
977+
self.assertFalse(tr.intersects(tr_t))
978+
979+
tstart = otio.opentime.RationalTime(14, 25)
980+
tdur = otio.opentime.RationalTime(2, 25)
981+
tr_t = otio.opentime.TimeRange(tstart, tdur)
982+
983+
self.assertTrue(tr.intersects(tr_t))
984+
985+
tstart = otio.opentime.RationalTime(15, 25)
986+
tdur = otio.opentime.RationalTime(2, 25)
987+
tr_t = otio.opentime.TimeRange(tstart, tdur)
988+
989+
self.assertFalse(tr.intersects(tr_t))
990+
991+
tstart = otio.opentime.RationalTime(13, 25)
992+
tdur = otio.opentime.RationalTime(1, 25)
993+
tr_t = otio.opentime.TimeRange(tstart, tdur)
994+
995+
self.assertTrue(tr.intersects(tr_t))
996+
997+
tstart = otio.opentime.RationalTime(2, 25)
998+
tdur = otio.opentime.RationalTime(30, 25)
999+
tr_t = otio.opentime.TimeRange(tstart, tdur)
1000+
1001+
self.assertTrue(tr.intersects(tr_t))
1002+
1003+
tstart = otio.opentime.RationalTime(2, 50)
1004+
tdur = otio.opentime.RationalTime(60, 50)
1005+
tr_t = otio.opentime.TimeRange(tstart, tdur)
1006+
1007+
self.assertTrue(tr.intersects(tr_t))
1008+
1009+
tstart = otio.opentime.RationalTime(2, 50)
1010+
tdur = otio.opentime.RationalTime(14, 50)
1011+
tr_t = otio.opentime.TimeRange(tstart, tdur)
1012+
1013+
self.assertFalse(tr.intersects(tr_t))
1014+
1015+
tstart = otio.opentime.RationalTime(-100, 50)
1016+
tdur = otio.opentime.RationalTime(400, 50)
1017+
tr_t = otio.opentime.TimeRange(tstart, tdur)
1018+
1019+
self.assertTrue(tr.intersects(tr_t))
1020+
1021+
tstart = otio.opentime.RationalTime(100, 50)
1022+
tdur = otio.opentime.RationalTime(400, 50)
1023+
tr_t = otio.opentime.TimeRange(tstart, tdur)
1024+
1025+
self.assertFalse(tr.intersects(tr_t))
1026+
9561027
def test_before_timerange(self):
9571028
tstart = otio.opentime.RationalTime(12, 25)
9581029
tdur = otio.opentime.RationalTime(3, 25)

0 commit comments

Comments
 (0)