Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5ff9c51
feat: DEV-1646: Move model_version to ML backend and add get versions…
KonstantinKorotaev Feb 14, 2022
0ab92a3
Fix models extraction for data manager
KonstantinKorotaev Feb 16, 2022
440896e
Fix tests
KonstantinKorotaev Feb 16, 2022
571eb35
Add model version to setup
KonstantinKorotaev Feb 17, 2022
994d187
Merge branch 'develop' into DEV-1646
KonstantinKorotaev Jun 7, 2022
ca468fe
Add feature flag
KonstantinKorotaev Jun 8, 2022
e7ec4e5
Merge branch 'develop' into DEV-1646
KonstantinKorotaev Jun 24, 2022
da55f4c
Merge branch 'develop' into DEV-1646
KonstantinKorotaev Jul 14, 2022
5892fad
Add test and old ML backend version support
KonstantinKorotaev Jul 18, 2022
e664e5e
Fix test
KonstantinKorotaev Jul 19, 2022
04465f1
fix: DEV-2905: Change the model version selector api response handlin…
bmartel Jul 19, 2022
cd3e7a0
Build frontend
robot-ci-heartex Jul 19, 2022
bdadb50
Update ModelVersionSelector.js
bmartel Jul 19, 2022
72f310e
Build frontend
robot-ci-heartex Jul 19, 2022
dc1f927
Merge branch 'DEV-1646', remote-tracking branch 'origin' into fb-dev-…
bmartel Jul 20, 2022
84eeb97
Merge remote-tracking branch 'origin' into fb-dev-2905
bmartel Jul 21, 2022
713540d
Build frontend
robot-ci-heartex Jul 21, 2022
7ab8d74
fix: DEV-2905: Allow both error and versions to be set in model versi…
bmartel Jul 21, 2022
0688133
Build frontend
robot-ci-heartex Jul 21, 2022
c81cd93
fix: DEV-2905: ml backend model version key was incorrect
bmartel Jul 21, 2022
73f7634
Merge branch 'fb-dev-2905' of github.com:heartexlabs/label-studio int…
bmartel Jul 21, 2022
eac4d3a
Build frontend
robot-ci-heartex Jul 21, 2022
86a7dc7
fix: DEV-2905: ml backend model version requires further date handling
bmartel Jul 21, 2022
8abea67
Merge branch 'fb-dev-2905' of github.com:heartexlabs/label-studio int…
bmartel Jul 21, 2022
d61a7fe
Build frontend
robot-ci-heartex Jul 21, 2022
67f6d1c
fix: DEV-2905: ml backend model version date handling should allow st…
bmartel Jul 21, 2022
ab0044e
Merge branch 'fb-dev-2905' of github.com:heartexlabs/label-studio int…
bmartel Jul 21, 2022
438705e
Build frontend
robot-ci-heartex Jul 21, 2022
0fc2e93
fix: DEV-2905: ml backend model version date handling should convert …
bmartel Jul 21, 2022
a5b422b
Merge branch 'fb-dev-2905' of github.com:heartexlabs/label-studio int…
bmartel Jul 21, 2022
649d2a1
Build frontend
robot-ci-heartex Jul 21, 2022
eed39a7
fix: DEV-2905: model version selector should be enabled even if error…
bmartel Jul 21, 2022
3cfcdb4
Build frontend
robot-ci-heartex Jul 21, 2022
628b385
fix: DEV-2905: model version selector value should reflect current se…
bmartel Jul 22, 2022
6c9f792
Merge branch 'fb-dev-2905' of github.com:heartexlabs/label-studio int…
bmartel Jul 22, 2022
70a13b8
Build frontend
robot-ci-heartex Jul 22, 2022
e970feb
fix: DEV-2905: model version selector value should reflect current se…
bmartel Jul 22, 2022
2deed6b
Merge branch 'fb-dev-2905' of github.com:heartexlabs/label-studio int…
bmartel Jul 22, 2022
fc8f8be
Build frontend
robot-ci-heartex Jul 22, 2022
3f17c21
fix: DEV-2905: model version selector options loading state to ensure…
bmartel Jul 22, 2022
ecc4b9f
Build frontend
robot-ci-heartex Jul 22, 2022
4900702
Add auto model update to setup
KonstantinKorotaev Aug 2, 2022
6a26fda
Merge branch 'develop' into DEV-1646
KonstantinKorotaev Aug 5, 2022
9a2bb55
Merge branch 'DEV-1646' into fb-dev-2905
KonstantinKorotaev Aug 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions label_studio/data_manager/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,14 +516,25 @@ def annotate_predictions_score(queryset):
if not first_task:
return queryset

model_version = first_task.project.model_version
if model_version is None:
return queryset.annotate(predictions_score=Avg("predictions__score"))
# new approach with each ML backend contains it's version
if flag_set('ff_front_dev_1682_model_version_dropdown_070622_short', first_task.project.organization.created_by):
model_versions = list(first_task.project.ml_backends.filter(project=first_task.project).
values_list("model_version", flat=True))
if len(model_versions) == 0:
return queryset.annotate(predictions_score=Avg("predictions__score"))

else:
return queryset.annotate(predictions_score=Avg(
"predictions__score", filter=Q(predictions__model_version__in=model_versions)
))
else:
return queryset.annotate(predictions_score=Avg(
"predictions__score", filter=Q(predictions__model_version=model_version)
))
model_version = first_task.project.model_version
if model_version is None:
return queryset.annotate(predictions_score=Avg("predictions__score"))
else:
return queryset.annotate(predictions_score=Avg(
"predictions__score", filter=Q(predictions__model_version=model_version)
))


def annotate_annotations_ids(queryset):
Expand Down
2 changes: 1 addition & 1 deletion label_studio/frontend/dist/react-app/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/react-app/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/react-app/main.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/react-app/main.css.map

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { format } from 'date-fns';
import { format, isValid } from 'date-fns';
import { useCallback, useContext } from 'react';
import { FaEllipsisV } from 'react-icons/fa';
import truncate from 'truncate-middle';
Expand Down Expand Up @@ -46,7 +46,7 @@ export const MachineLearningList = ({ backends, fetchBackends, onEdit }) => {
);
};

const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
const BackendCard = ({ backend, onStartTrain, onEdit, onDelete }) => {
const confirmDelete = useCallback((backend) => {
confirm({
title: "Delete ML Backend",
Expand All @@ -57,7 +57,7 @@ const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
}, [backend, onDelete]);

return (
<Card style={{marginTop: 0}} header={backend.title} extra={(
<Card style={{ marginTop: 0 }} header={backend.title} extra={(
<div className={cn('ml').elem('info')}>
<BackendState backend={backend}/>

Expand All @@ -72,7 +72,7 @@ const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
</div>
)}>
<DescriptionList className={cn('ml').elem('summary')}>
<DescriptionList.Item term="URL" termStyle={{whiteSpace: 'nowrap'}}>
<DescriptionList.Item term="URL" termStyle={{ whiteSpace: 'nowrap' }}>
{truncate(backend.url, 20, 10, '...')}
</DescriptionList.Item>
{backend.description && (
Expand All @@ -82,7 +82,9 @@ const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
/>
)}
<DescriptionList.Item term="Version">
{backend.version ? format(new Date(backend.version), 'MMMM dd, yyyy ∙ HH:mm:ss') : 'unknown'}
{backend.model_version && isValid(backend.model_version) ?
format(new Date(isNaN(backend.model_version) ? backend.model_version: Number(backend.model_version)), 'MMMM dd, yyyy ∙ HH:mm:ss')
: backend.model_version || 'unknown'}
</DescriptionList.Item>
</DescriptionList>

Expand All @@ -93,11 +95,12 @@ const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
);
};

const BackendState = ({backend}) => {
const BackendState = ({ backend }) => {
const { state } = backend;

return (
<div className={cn('ml').elem('status')}>
<span className={cn('ml').elem('indicator').mod({state})}></span>
<span className={cn('ml').elem('indicator').mod({ state })}></span>
<Oneof value={state} className={cn('ml').elem('status-label')}>
<span case="DI">Disconnected</span>
<span case="CO">Connected</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const MachineLearningSettings = () => {
<ModelVersionSelector
object={backend}
apiName="modelVersions"
valueName="version"
label="Version"
/>
</Form.Row>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useCallback, useEffect, useState } from 'react';
import { useAPI } from '../../../providers/ApiProvider';
import { Select } from '../../../components/Form';
import { Block, Elem } from '../../../utils/bem';

import './ModelVersionSelector.styl';

export const ModelVersionSelector = ({
name = "model_version",
Expand All @@ -11,7 +14,14 @@ export const ModelVersionSelector = ({
...props
}) => {
const api = useAPI();
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const [versions, setVersions] = useState([]);
const [version, setVersion] = useState(object?.[valueName] || null);

useEffect(() => {
setVersion(object?.[valueName] || null);
}, [object?.[valueName]]);

const fetchMLVersions = useCallback(async () => {
const pk = object?.id;
Expand All @@ -24,24 +34,50 @@ export const ModelVersionSelector = ({
},
});

if (!modelVersions) return;
// handle possible error
if (modelVersions?.message) {
setError(modelVersions.message);
}

if (modelVersions?.versions?.length) {
setVersions(modelVersions.versions.map(version => ({
value: version,
label: version,
})));
}

setVersions(Object.entries(modelVersions).reduce((v, [key, value]) => [...v, {
value: key,
label: key + " (" + value + " predictions)",
}], []));
setLoading(false);
}, [api, object?.id, apiName]);

useEffect(fetchMLVersions, []);

return (
<Select
name={name}
disabled={!versions.length}
defaultValue={object?.[valueName] || null}
options={versions}
placeholder={placeholder}
{...props}
/>
<Block name="modelVersionSelector">
{loading ? (
<Select
disabled={true}
value={null}
options={[]}
placeholder={"Loading ..."}
{...props}
/>
)
: (
<Select
name={name}
disabled={!versions.length}
value={version}
onChange={e => setVersion(e.target.value)}
options={versions}
placeholder={placeholder}
{...props}
/>
)}
{error && (
<Elem name="message">
{error}
</Elem>
)}
</Block>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.modelVersionSelector
width 100%
display flex
flex-direction column
gap 12px

&__message
font-size 16px
color #DD0000
margin-bottom 12px

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ export const ProjectModelVersionSelector = ({
}) => {
const api = useAPI();
const { project, updateProject } = useContext(ProjectContext);
const [loading, setLoading] = useState(true);
const [versions, setVersions] = useState([]);
const [version, setVersion] = useState(project?.[valueName] || null);

useEffect(() => {
setVersion(project?.[valueName] || null);
}, [project?.[valueName]]);

const resetMLVersion = useCallback(async (e) => {
e.preventDefault();
Expand All @@ -35,12 +41,14 @@ export const ProjectModelVersionSelector = ({
},
});

if (!modelVersions) return;
if (modelVersions) {
setVersions(Object.entries(modelVersions).reduce((v, [key, value]) => [...v, {
value: key,
label: `${key} (${value} predictions)`,
}], []));
}

setVersions(Object.entries(modelVersions).reduce((v, [key, value]) => [...v, {
value: key,
label: key + " (" + value + " predictions)",
}], []));
setLoading(false);
}, [api, project?.id, apiName]);

useEffect(fetchMLVersions, [fetchMLVersions]);
Expand All @@ -52,10 +60,10 @@ export const ProjectModelVersionSelector = ({
description={(
<>
Model version allows you to specify which prediction will be shown to the annotators.
{project.model_version && (
{version && (
<>
<br />
<b>Current project model version: {project.model_version}</b>
<b>Current project model version: {version}</b>
</>
)}
</>
Expand All @@ -66,14 +74,26 @@ export const ProjectModelVersionSelector = ({

<div style={{ display: 'flex', alignItems: 'center', width: 400, paddingLeft: 16 }}>
<div style={{ flex: 1, paddingRight: 16 }}>
<Select
name={name}
disabled={!versions.length}
defaultValue={project?.[valueName] || null}
options={versions}
placeholder={placeholder}
{...props}
/>
{loading ? (
<Select
disabled={true}
value={null}
options={[]}
placeholder={"Loading ..."}
{...props}
/>
)
: (
<Select
name={name}
disabled={!versions.length}
value={version}
onChange={e => setVersion(e.target.value)}
options={versions}
placeholder={placeholder}
{...props}
/>
)}
</div>

<Button onClick={resetMLVersion}>
Expand Down
29 changes: 29 additions & 0 deletions label_studio/ml/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,32 @@ def post(self, request, *args, **kwargs):
result,
status=status.HTTP_200_OK,
)


@method_decorator(
name='get',
decorator=swagger_auto_schema(
tags=['Machine Learning'],
operation_summary='Get model versions',
operation_description='Get available versions of the model.',
responses={"200": "List of available versions."},
),
)
class MLBackendVersionsAPI(generics.RetrieveAPIView):

permission_required = all_permissions.projects_change

def get(self, request, *args, **kwargs):
ml_backend = get_object_with_check_and_log(request, MLBackend, pk=self.kwargs['pk'])
self.check_object_permissions(self.request, ml_backend)
versions_response = ml_backend.get_versions()
if versions_response.status_code == 200:
result = {'versions': versions_response.response.get("versions", [])}
return Response(data=result, status=200)
elif versions_response.status_code == 404:
result = {'versions': [ml_backend.model_version], 'message': 'Upgrade your ML backend version to latest.'}
return Response(data=result, status=200)
else:
result = {'error': str(versions_response.error_message)}
status_code = versions_response.status_code if versions_response.status_code > 0 else 500
return Response(data=result, status=status_code)
10 changes: 8 additions & 2 deletions label_studio/ml/api_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,13 @@ def health(self):
def validate(self, config):
return self._request('validate', request={'config': config}, timeout=self._validate_request_timeout)

def setup(self, project):
def setup(self, project, model_version=None):
return self._request('setup', request={
'project': self._create_project_uid(project),
'schema': project.label_config,
'hostname': settings.HOSTNAME if settings.HOSTNAME else ('http://localhost:' + settings.INTERNAL_PORT),
'access_token': project.created_by.auth_token.key
'access_token': project.created_by.auth_token.key,
'model_version': model_version
}, timeout=TIMEOUT_SETUP)

def duplicate_model(self, project_src, project_dst):
Expand All @@ -233,6 +234,11 @@ def delete(self, project):
def get_train_job_status(self, train_job):
return self._request('job_status', request={'job': train_job.job_id}, timeout=TIMEOUT_TRAIN_JOB_STATUS)

def get_versions(self, project):
return self._request('versions', request={
'project': self._create_project_uid(project)
}, timeout=TIMEOUT_SETUP, method='POST')


def get_ml_api(project):
if project.ml_backend_active_connection is None:
Expand Down
18 changes: 18 additions & 0 deletions label_studio/ml/migrations/0006_mlbackend_auto_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.1.14 on 2022-02-14 15:32

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('ml', '0005_auto_20211010_1344'),
]

operations = [
migrations.AddField(
model_name='mlbackend',
name='auto_update',
field=models.BooleanField(default=True, help_text='If false, model version is set by the user, if true - getting latest version from backend.', verbose_name='auto_update'),
),
]
Loading