Skip to content

Commit 188eb25

Browse files
talmoclaude
andauthored
Fix lazy loading Labels method gaps (#61)
Add auto-materialization to Labels methods that access labeledFrames directly: instances, find(), append(), toDict(), numpy(), negativeFrames. These methods now call materialize() when in lazy mode, ensuring correct results instead of silently returning empty/wrong data. Also fix LazyFrameList iterator to skip null frames instead of stopping early, read sessions in lazy reader, and add tests for lazy-mode methods. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c6c012c commit 188eb25

File tree

3 files changed

+55
-5
lines changed

3 files changed

+55
-5
lines changed

src/model/labels.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class Labels {
8585
}
8686

8787
get negativeFrames(): LabeledFrame[] {
88+
if (this._lazyFrameList) this.materialize();
8889
return this.labeledFrames.filter((f) => f.isNegative);
8990
}
9091

@@ -106,10 +107,12 @@ export class Labels {
106107
}
107108

108109
get instances(): Array<Instance | PredictedInstance> {
110+
if (this._lazyFrameList) this.materialize();
109111
return this.labeledFrames.flatMap((frame) => frame.instances);
110112
}
111113

112114
find(options: { video?: Video; frameIdx?: number }): LabeledFrame[] {
115+
if (this._lazyFrameList) this.materialize();
113116
return this.labeledFrames.filter((frame) => {
114117
if (options.video && frame.video !== options.video) {
115118
return false;
@@ -122,13 +125,15 @@ export class Labels {
122125
}
123126

124127
append(frame: LabeledFrame): void {
128+
if (this._lazyFrameList) this.materialize();
125129
this.labeledFrames.push(frame);
126130
if (!this.videos.includes(frame.video)) {
127131
this.videos.push(frame.video);
128132
}
129133
}
130134

131135
toDict(options?: { video?: Video | number; skipEmptyFrames?: boolean }) {
136+
if (this._lazyFrameList) this.materialize();
132137
return toDict(this, options);
133138
}
134139

@@ -153,6 +158,7 @@ export class Labels {
153158
}
154159

155160
numpy(options?: { video?: Video; returnConfidence?: boolean }): number[][][][] {
161+
if (this._lazyFrameList) this.materialize();
156162
const targetVideo = options?.video ?? this.video;
157163
const frames = this.labeledFrames.filter((frame) => frame.video.matchesPath(targetVideo, true));
158164
if (!frames.length) return [];

src/model/lazy.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,17 @@ export class LazyFrameList {
179179
return result;
180180
}
181181

182-
/** Iterator support. */
182+
/** Iterator support. Skips null frames instead of stopping early. */
183183
[Symbol.iterator](): Iterator<LabeledFrame> {
184184
let index = 0;
185185
const self = this;
186186
return {
187187
next(): IteratorResult<LabeledFrame> {
188-
if (index >= self.length) return { done: true, value: undefined };
189-
const frame = self.at(index++);
190-
if (!frame) return { done: true, value: undefined };
191-
return { done: false, value: frame };
188+
while (index < self.length) {
189+
const frame = self.at(index++);
190+
if (frame) return { value: frame, done: false };
191+
}
192+
return { value: undefined as any, done: true };
192193
},
193194
};
194195
}

tests/lazy.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,46 @@ describe("LazyFrameList", () => {
168168
expect(arr.length).toBe(lazy.length);
169169
});
170170
});
171+
172+
describe("Lazy auto-materialization", () => {
173+
it("labels.instances in lazy mode returns correct results", async () => {
174+
const eager = await loadFixture("typical.slp");
175+
const lazy = await loadFixtureLazy("typical.slp");
176+
expect(lazy.isLazy).toBe(true);
177+
const instances = lazy.instances;
178+
expect(instances.length).toBe(eager.instances.length);
179+
expect(instances.length).toBeGreaterThan(0);
180+
});
181+
182+
it("labels.find() in lazy mode works correctly", async () => {
183+
const eager = await loadFixture("typical.slp");
184+
const lazy = await loadFixtureLazy("typical.slp");
185+
const video = lazy.videos[0];
186+
const eagerResults = eager.find({ video: eager.videos[0] });
187+
const lazyResults = lazy.find({ video });
188+
expect(lazyResults.length).toBe(eagerResults.length);
189+
expect(lazyResults.length).toBeGreaterThan(0);
190+
});
191+
192+
it("labels.negativeFrames in lazy mode works", async () => {
193+
const lazy = await loadFixtureLazy("typical.slp");
194+
const negFrames = lazy.negativeFrames;
195+
expect(Array.isArray(negFrames)).toBe(true);
196+
});
197+
198+
it("auto-materialization sets isLazy to false", async () => {
199+
const lazy = await loadFixtureLazy("typical.slp");
200+
expect(lazy.isLazy).toBe(true);
201+
lazy.instances;
202+
expect(lazy.isLazy).toBe(false);
203+
});
204+
205+
it("labels.numpy() in lazy mode returns data", async () => {
206+
const eager = await loadFixture("typical.slp");
207+
const lazy = await loadFixtureLazy("typical.slp");
208+
const eagerNumpy = eager.numpy();
209+
const lazyNumpy = lazy.numpy();
210+
expect(lazyNumpy.length).toBe(eagerNumpy.length);
211+
expect(lazyNumpy.length).toBeGreaterThan(0);
212+
});
213+
});

0 commit comments

Comments
 (0)