Skip to content

Commit 515228e

Browse files
Merge remote-tracking branch 'origin/develop' into fb-ROOT-11
2 parents c45fbfd + 353b6eb commit 515228e

File tree

33 files changed

+1568
-43
lines changed

33 files changed

+1568
-43
lines changed

.github/workflows/apply-linters.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
ref: ${{ inputs.branch_name }}
2525

2626
- name: Set up Python
27-
uses: actions/setup-python@v4
27+
uses: actions/setup-python@v5
2828
with:
2929
python-version: '3.12'
3030

.github/workflows/docker-build-ontop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ jobs:
110110
${{ steps.calculate-docker-tags.outputs.docker-tags }}
111111
112112
- name: Push Docker image
113-
uses: docker/build-push-action@v6.16.0
113+
uses: docker/build-push-action@v6.17.0
114114
id: docker_build_and_push
115115
with:
116116
context: .

.github/workflows/docker-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ jobs:
130130
type=raw,value=${{ steps.version.outputs.build_version }}
131131
132132
- name: Push Docker image
133-
uses: docker/build-push-action@v6.16.0
133+
uses: docker/build-push-action@v6.17.0
134134
id: docker_build_and_push
135135
with:
136136
context: .

.github/workflows/docker-release-promote.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ jobs:
195195
${{ steps.generate-tags.outputs.ubuntu-tags }}
196196
197197
- name: Build and Push Release Ubuntu Docker image
198-
uses: docker/build-push-action@v6.16.0
198+
uses: docker/build-push-action@v6.17.0
199199
id: docker_build
200200
with:
201201
context: ${{ steps.release_dockerfile.outputs.release_dir }}

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ jobs:
140140

141141
- name: Upload coverage to Codecov
142142
if: ${{ github.event.pull_request.head.repo.fork == false && github.event.pull_request.user.login != 'dependabot[bot]' }}
143-
uses: codecov/codecov-action@v5.4.2
143+
uses: codecov/codecov-action@v5.4.3
144144
with:
145145
name: codecov-python-${{ matrix.python-version }}
146146
flags: pytests

docs/themes/v2/layout/templates.ejs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
<div class="templates-grid">
66
<% page.cards.forEach(function(card) { %>
7+
<% const tags = typeof card.categories === "object" ? card.categories.join(", ") : card.categories %>
78
<div class="templates-card">
89
<img src="<%= card.image %>" alt="" />
910
<a href="<%= card.url %>"><%- partial("component/heading", {text: card.title, size: "XXSmall", tag: "span" }) %></a>
10-
<%- partial("component/text", {text: card.categories, tag: "p", size: "Small"}) %>
11+
<%- partial("component/text", {text: tags, tag: "p", size: "Small"}) %>
1112
</div>
1213
<% }); %>
1314
</div>

label_studio/feature_flags.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3120,6 +3120,33 @@
31203120
"version": 2,
31213121
"deleted": false
31223122
},
3123+
"fflag_feat_root_11_support_jsonl_cloud_storage": {
3124+
"key": "fflag_feat_root_11_support_jsonl_cloud_storage",
3125+
"on": false,
3126+
"prerequisites": [],
3127+
"targets": [],
3128+
"contextTargets": [],
3129+
"rules": [],
3130+
"fallthrough": {
3131+
"variation": 0
3132+
},
3133+
"offVariation": 1,
3134+
"variations": [
3135+
true,
3136+
false
3137+
],
3138+
"clientSideAvailability": {
3139+
"usingMobileKey": false,
3140+
"usingEnvironmentId": false
3141+
},
3142+
"clientSide": false,
3143+
"salt": "85e018dcd2e64c689a61ee7ed3c5edb2",
3144+
"trackEvents": false,
3145+
"trackEventsFallthrough": false,
3146+
"debugEventsUntilDate": null,
3147+
"version": 2,
3148+
"deleted": false
3149+
},
31233150
"fflag_feature_all_optic_1421_cold_start_v2": {
31243151
"key": "fflag_feature_all_optic_1421_cold_start_v2",
31253152
"on": false,

label_studio/io_storages/localfiles/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,16 @@ def iterkeys(self):
7878
continue
7979
yield str(file)
8080

81-
def get_data(self, key) -> list[dict]:
81+
def get_data(self, key) -> dict | list[dict]:
8282
path = Path(key)
8383
if self.use_blob_urls:
8484
# include self-hosted links pointed to local resources via
8585
# {settings.HOSTNAME}/data/local-files?d=<path/to/local/dir>
8686
document_root = Path(settings.LOCAL_FILES_DOCUMENT_ROOT)
8787
relative_path = str(path.relative_to(document_root))
88-
return [
89-
{settings.DATA_UNDEFINED_NAME: f'{settings.HOSTNAME}/data/local-files/?d={quote(str(relative_path))}'}
90-
]
88+
return {
89+
settings.DATA_UNDEFINED_NAME: f'{settings.HOSTNAME}/data/local-files/?d={quote(str(relative_path))}'
90+
}
9191

9292
try:
9393
with open(path, encoding='utf8') as f:
@@ -99,7 +99,7 @@ def get_data(self, key) -> list[dict]:
9999
)
100100

101101
if isinstance(value, dict):
102-
return [value]
102+
return value
103103
elif isinstance(value, list):
104104
for idx, item in enumerate(value):
105105
if not isinstance(item, dict):

label_studio/io_storages/s3/models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
get_client_and_resource,
2828
resolve_s3_url,
2929
)
30-
from io_storages.utils import storage_can_resolve_bucket_url
30+
from io_storages.utils import storage_can_resolve_bucket_url, load_tasks_json
3131
from tasks.models import Annotation
3232
from tasks.validation import ValidationError as TaskValidationError
3333

@@ -228,7 +228,6 @@ def get_data(self, key) -> Union[dict, list[dict]]:
228228
_, s3 = self.get_client_and_resource()
229229
bucket = s3.Bucket(self.bucket)
230230
obj = s3.Object(bucket.name, key).get()['Body'].read().decode('utf-8')
231-
from io_storages.utils import load_tasks_json
232231

233232
# TODO: Why do only S3 storages use TaskValidationError here? If the mystery is resolved, can remove this argument from load_tasks_json and use ValueError everywhere
234233
return load_tasks_json(obj, key, self.__class__.__name__, TaskValidationError)

label_studio/tests/data_manager/test_api_actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_action_remove_duplicates(business_client, project_id, storage_model, li
140140
# this task would have row_index=0 instead of None if it was created after multitask support was added
141141
link_model.objects.create(task=task4, key='duplicated.jpg', storage=storage)
142142

143-
# task 5: add not a duplicated task using the same key, ensuring multiple tasks in the same key don't interfere
143+
# task 5: add a non-duplicated task using the same key, ensuring multiple tasks in the same key don't interfere
144144
task_data = {'data': {'image': 'normal2.jpg'}}
145145
task5 = make_task(task_data, project)
146146
link_model.objects.create(task=task5, key='duplicated.jpg', row_index=1, storage=storage)

web/libs/editor/src/mixins/ToolManagerMixin.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { FF_DEV_3391 } from "../utils/feature-flags";
77
export const ToolManagerMixin = types.model().actions((self) => {
88
return {
99
afterAttach() {
10-
if (ff.isActive(FF_DEV_3391) && !self.annotation) {
10+
if (ff.isActive(FF_DEV_3391) && self.annotationStore.initialized) {
11+
self.tools = self.annotationStore.names.get(self.name).tools;
1112
return;
1213
}
1314
const toolNames = self.toolNames ?? [];

web/libs/editor/src/regions/PolygonPoint.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { Circle, Rect } from "react-konva";
33
import { observer } from "mobx-react";
44
import { getParent, getRoot, hasParent, types } from "mobx-state-tree";
55

6+
import { RELATIVE_STAGE_HEIGHT, RELATIVE_STAGE_WIDTH } from "../components/ImageView/Image";
67
import { guidGenerator } from "../core/Helpers";
78
import { useRegionStyles } from "../hooks/useRegionColor";
9+
import { AnnotationMixin } from "../mixins/AnnotationMixin";
810
import { FF_DEV_3793, isFF } from "../utils/feature-flags";
9-
import { RELATIVE_STAGE_HEIGHT, RELATIVE_STAGE_WIDTH } from "../components/ImageView/Image";
1011

1112
const PolygonPointAbsoluteCoordsDEV3793 = types
1213
.model()
@@ -83,12 +84,10 @@ const PolygonPointRelativeCoords = types
8384
return self.parent?.parent;
8485
},
8586

86-
get annotation() {
87-
return getRoot(self).annotationStore.selected;
88-
},
8987
get canvasX() {
9088
return isFF(FF_DEV_3793) ? self.stage?.internalToCanvasX(self.x) : self.x;
9189
},
90+
9291
get canvasY() {
9392
return isFF(FF_DEV_3793) ? self.stage?.internalToCanvasY(self.y) : self.y;
9493
},
@@ -196,10 +195,12 @@ const PolygonPointRelativeCoords = types
196195
},
197196
}));
198197

199-
const PolygonPoint = isFF(FF_DEV_3793)
198+
const PolygonPointModel = isFF(FF_DEV_3793)
200199
? PolygonPointRelativeCoords
201200
: types.compose("PolygonPoint", PolygonPointRelativeCoords, PolygonPointAbsoluteCoordsDEV3793);
202201

202+
const PolygonPoint = types.compose("PolygonPoint", AnnotationMixin, PolygonPointModel);
203+
203204
const PolygonPointView = observer(({ item, name }) => {
204205
if (!item.parent) return;
205206

web/libs/editor/src/regions/TimeSeriesRegion.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@ const Model = types
3131
return self.object;
3232
},
3333

34-
// Do not remove this annotation getter until saving/updating annotation in LS will work without errors
35-
get annotation() {
36-
const root = getRoot(self);
37-
38-
return root !== self ? root.annotationStore?.selected : null;
39-
},
40-
4134
getRegionElement() {
4235
return self._brushRef;
4336
},

web/libs/editor/src/regions/VideoRegion.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getRoot, types } from "mobx-state-tree";
22

33
import { guidGenerator } from "../core/Helpers";
44
import { AreaMixin } from "../mixins/AreaMixin";
5+
import { AnnotationMixin } from "../mixins/AnnotationMixin";
56
import NormalizationMixin from "../mixins/Normalization";
67
import RegionsMixin from "../mixins/Regions";
78
import { VideoModel } from "../tags/object/Video";
@@ -29,10 +30,6 @@ const Model = types
2930
return self.object;
3031
},
3132

32-
get annotation() {
33-
return getRoot(self)?.annotationStore?.selected;
34-
},
35-
3633
getShape() {
3734
throw new Error("Method getShape be implemented on a shape level");
3835
},
@@ -141,6 +138,13 @@ const Model = types
141138
},
142139
}));
143140

144-
const VideoRegion = types.compose("VideoRegionModel", RegionsMixin, AreaMixin, NormalizationMixin, Model);
141+
const VideoRegion = types.compose(
142+
"VideoRegionModel",
143+
RegionsMixin,
144+
AreaMixin,
145+
AnnotationMixin,
146+
NormalizationMixin,
147+
Model,
148+
);
145149

146150
export { VideoRegion };

web/libs/editor/src/tags/control/Label.jsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,6 @@ const Model = types
190190

191191
// if we are going to select label and it would be the first in this labels group
192192
if (!labels.selectedLabels.length && !self.selected) {
193-
// unselect labels from other groups of labels connected to this obj
194-
195-
self.annotation.toNames
196-
.get(labels.toname)
197-
.filter((tag) => tag.type && tag.type.endsWith("labels") && tag.name !== labels.name);
198-
199193
// unselect other tools if they exist and selected
200194
const manager = ToolsManager.getInstance({ name: self.parent.toname });
201195
const tool = Object.values(self.parent?.tools || {})[0];

web/libs/editor/src/tags/object/TimeSeries.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import * as d3 from "d3";
33
import { inject, observer } from "mobx-react";
4-
import { getEnv, getRoot, getType, types } from "mobx-state-tree";
4+
import { getEnv, getRoot, getType, isAlive, types } from "mobx-state-tree";
55
import throttle from "lodash.throttle";
66
import { Spin } from "antd";
77

@@ -494,6 +494,9 @@ const Model = types
494494
}
495495
[data, headers] = parseCSV(text, separator);
496496
}
497+
// @todo this actions might be called for detached instance with DEV-3391 FF
498+
// @todo at least it should be rewritten to use MST `flow()`
499+
if (!isAlive(self)) return;
497500
self.setData(data);
498501
self.setColumnNames(headers);
499502
self.updateValue(store);

web/libs/editor/src/tags/object/Video/Video.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import ObjectBase from "../Base";
1616
*
1717
* ### Video format
1818
*
19-
* Label Studio relies on your web browser to play videos, so it's essential that your videos use a format and codecs that are universally supported. To ensure maximum compatibility, we recommend using an MP4 container with video encoded using the H.264 (AVC) codec and audio encoded with AAC. This combination is widely supported across all modern browsers and minimizes issues like incorrect total duration detection or problems with playback. In addition, it's important to convert your videos to a constant frame rate (CFR), ideally around 30 fps, to avoid discrepancies in frame counts and issues with duplicated or missing frames.
19+
* Label Studio relies on your web browser to play videos and evaluate the total frame number. So, it's essential that your videos use a format and codecs that are universally supported. To ensure maximum compatibility, we recommend using an MP4 container with video encoded using the H.264 (AVC) codec and audio encoded with AAC. This combination is widely supported across all modern browsers and minimizes issues like incorrect total duration detection or problems with playback. In addition, it's important to convert your videos to a constant frame rate (CFR), ideally around 30 fps, to avoid discrepancies in frame counts and issues with duplicated or missing frames. All audio and video streams from your file must have the same durations; otherwise, you will have extra total frames.
2020
*
21-
* Converting your videos to this recommended format will help ensure that they play smoothly in Label Studio and that the frame rate and duration are correctly recognized for accurate annotations. To convert any video to this format, you can use FFmpeg. For example, the following command converts an input video to MP4 with H.264 video, AAC audio, and a constant frame rate of 30 fps:
21+
* Converting your videos to this recommended format will help ensure that they play smoothly in Label Studio and that the frame rate and duration are correctly recognized for accurate annotations. To convert any video to this format, you can use FFmpeg. For example, the following commands convert an input video to MP4 with H.264 video, AAC audio, and a constant frame rate of 30 fps:
2222
*
2323
* ```bash
24-
* ffmpeg -i input_video.mp4 -c:v libx264 -profile:v high -level 4.0 -pix_fmt yuv420p -r 30 -c:a aac -b:a 128k output_video.mp4
24+
* # Extract the exact video stream duration in seconds
25+
* DUR=$(ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=nokey=1:noprint_wrappers=1 input.mp4)
26+
* # Re-encode media file to recommended format
27+
* ffmpeg -i input_video.mp4 -c:v libx264 -profile:v high -level 4.0 -pix_fmt yuv420p -r 30 -c:a aac -b:a 128k -to $DUR output_video.mp4
2528
* ```
2629
*
2730
* In this command:
@@ -31,6 +34,7 @@ import ObjectBase from "../Base";
3134
* - `-pix_fmt yuv420p` ensures the pixel format is compatible with most browsers.
3235
* - `-r 30` forces a constant frame rate of 30 fps. You can also omit the -r option, ffmpeg will save your current frame rate. This is fine if you are 100% certain that your video has a constant frame rate.
3336
* - `-c:a aac -b:a 128k` encodes the audio in AAC at 128 kbps.
37+
* - `-to` stops writing output as soon as the container clock hits your video’s end timestamp, so any extra audio tail is automatically dropped.
3438
* - `output_video.mp4` is the converted video file ready for use in Label Studio.
3539
*
3640
* Using this FFmpeg command to re-encode your videos will help eliminate playback issues and ensure that Label Studio detects the total video duration accurately, providing a smooth annotation experience.

web/libs/editor/src/tags/visual/Collapse.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import Registry from "../../core/Registry";
88
import Types from "../../core/Types";
99
import Tree from "../../core/Tree";
1010
import { isSelfServe } from "../../utils/billing";
11-
import { FF_BULK_ANNOTATION } from "../../utils/feature-flags";
11+
import { FF_BULK_ANNOTATION, isFF } from "../../utils/feature-flags";
12+
import { guidGenerator } from "../../utils/unique";
1213

1314
const { Panel } = Collapse;
1415

@@ -88,6 +89,7 @@ const PanelModel = types
8889

8990
const Model = types
9091
.model({
92+
id: types.optional(types.identifier, guidGenerator),
9193
type: "collapse",
9294

9395
size: types.optional(types.string, "4"),

web/libs/editor/src/tools/Manager.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { ff } from "@humansignal/core";
12
import { destroy } from "mobx-state-tree";
3+
import { FF_DEV_3391 } from "../utils/feature-flags";
24
import { guidGenerator } from "../utils/unique";
35

46
/** @type {Map<any, ToolsManager>} */
@@ -57,6 +59,9 @@ class ToolsManager {
5759
}
5860

5961
get obj() {
62+
if (ff.isActive(FF_DEV_3391)) {
63+
return root.annotationStore.selected?.names.get(this.name);
64+
}
6065
return root.annotationStore.names.get(this.name);
6166
}
6267

0 commit comments

Comments
 (0)