Skip to content

Commit

Permalink
Release 1.4 (#1725)
Browse files Browse the repository at this point in the history
* Make 1.4rc0

* Update frontend (#1721)

* Update frontend

* Pull latest changes from LSF

Co-authored-by: Nick <nr@fenelon.ru>

* Update frontend scripts

* Update LSF

* Update frontend

* Bump 1.4rc1

* Temporary disable py3.9 tests

* [fix] [dm] onlyVirtualTabs of undefined

* [fix] db naming

* [fix] Empty annotations and predictions in DM TaskSerializer (#1733)

* [fix] Serializer with empty annotations and predictions

* Update serializers.py

* Fix tests

* [fix] Update LSF build with ctrl+z and not working label hotkey (DEV-1121) (#1748)

* Fix quickstart example (#1726)

* Fix brush mask to rle description (#1728)

* Docs for custom agreement metric for private cloud LSE (#1729)

* Update to add perms notes

* Finish updating custom metric doc for perms

* fixing link

* updating custom metric doc for review feedback

- restructured create policy steps
- added a bunch of links to aws docs for creating roles
- clarified order and links for things

* Configure ssl certs in nginx (#1707)

* [docs] Usecase with blocked internet access (#1732)

* [docs] Usecase with blocked internet access

* update k8s install for proxy/airgapped internet

added more links and keywords and clarity

Co-authored-by: smoreface <smoreface@users.noreply.github.com>

* Add docs how to enable tls on pgsql (#1737)

* Update LSF build with ctrl+z and not working label hotkeys

Co-authored-by: smoreface <smoreface@users.noreply.github.com>
Co-authored-by: Sergey Zhuk <sergey.zhuk@heartex.com>

* DEV-1154: Rotation fixes (#1756)

* Update LSF

* Update LSF to master

Co-authored-by: 4iGAN <4iGAN.yO@gmail.com>

* DEV-438: SDK tutorial (#1711)

* Create sdk.md

* Update sdk.md

* Update sdk.md

* Update sdk.md

* Add some fixes

* review feedback on sdk doc

* Update docs header with SDK reference link

* Add more prediction examples

* Update sdk.md

* Update header.ejs

* fix imports in doc

* fix links to jupyter notebooks

* [docs] update filters header per review feedback

Co-authored-by: Max Tkachenko <makseq@gmail.com>
Co-authored-by: nik <nik@heartex.net>

* DEV-819: Fix image size problems with appearance of scrollbar (#1758)

* Make get-build works with long branch names

(cherry picked from commit 4c8329e)
(cherry picked from commit 56d42da)

* [fix] [lsf] Add observing of image size for special cases

* Update LSF

Co-authored-by: makseq-ubnt <makseq@gmail.com>

* Update DM from master commit

* Update LS version

* Update converter and LS version

* Update converter to 0.0.36

* Add verify flag to sqlite download

* Fix --ml-backend option

* [fix] fix code editor vertical scroll after #1669 (#1765)

* Fix changing avatar permission (DEV-1183) (#1764)

* Get only task ids via drf-flexfields (DEV-1102) (#1747)

* Change version to rc6

* Change version to 1.4

Co-authored-by: nik <nik@heartex.net>
Co-authored-by: Nick <nr@fenelon.ru>
Co-authored-by: hlomzik <hlomzik@gmail.com>
Co-authored-by: makseq-ubnt <makseq@gmail.com>
Co-authored-by: smoreface <smoreface@users.noreply.github.com>
Co-authored-by: Sergey Zhuk <sergey.zhuk@heartex.com>
Co-authored-by: Nick Skriabin <767890+nicholasrq@users.noreply.github.com>
Co-authored-by: 4iGAN <4iGAN.yO@gmail.com>
Co-authored-by: Sergey <Gondragos@gmail.com>
Co-authored-by: Sergei Ivashchenko <triklozoid@gmail.com>
  • Loading branch information
11 people authored Nov 19, 2021
1 parent d4c4fc9 commit 3e28c16
Show file tree
Hide file tree
Showing 31 changed files with 248 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.6, 3.7, 3.8]

env:
DJANGO_SETTINGS_MODULE: core.settings.label_studio
Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ recursive-include label_studio/frontend/dist/dm/ *
# html template files
recursive-include label_studio *.html

# exclude node modules
prune label_studio/frontend/node_modules
prune label_studio/frontend/build-tmp

# annotation templates
recursive-include label_studio/annotation_templates *

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ frontend-build:

# Run tests
test:
cd label_studio && pytest -v -m "not integration_tests"
cd label_studio && DJANGO_DB=sqlite pytest -v -m "not integration_tests"
2 changes: 1 addition & 1 deletion deploy/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ colorama>=0.4.4
boxing>=0.1.4
redis>=3.5.0
sentry-sdk>=1.1.0
label-studio-converter==0.0.33rc5
label-studio-converter==0.0.36
175 changes: 175 additions & 0 deletions docs/source/guide/sdk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
title: Python SDK Tutorial for Label Studio
short: Python SDK Tutorial
type: guide
order: 675
meta_title: Label Studio Python SDK Tutorial
meta_description: Tutorial documentation for the Label Studio Python SDK that covers how and why to use the SDK to easily include data labeling project creation and annotated task parsing in your data pipeline python scripts for data science and machine learning projects.
---

You can use the Label Studio Python SDK to make annotating data a more integrated part of your data science and machine learning pipelines. This software development kit (SDK) lets you call the Label Studio API directly from scripts using predefined classes and methods.

With the Label Studio Python SDK, you can perform the following tasks in a Python script:
- [Authenticate to the Label Studio API](#Start-using-the-Label-Studio-Python-SDK)
- [Create a Label Studio project](#Create-a-project-with-the-Label-Studio-Python-SDK), including setting up a labeling configuration.
- [Import tasks](#Import-tasks-with-the-Label-Studio-Python-SDK).
- [Manage pre-annotated tasks and model predictions](#Add-predictions-to-existing-tasks-with-the-Label-Studio-Python-SDK).
- [Connect to a cloud storage provider](https://github.com/heartexlabs/label-studio-sdk/blob/master/examples/annotate_data_from_gcs/annotate_data_from_gcs.ipynb), such as Amazon S3, Microsoft Azure, or Google Cloud Services (GCS), to retrieve unlabeled tasks and store annotated tasks.
- [Modify project settings](/sdk/project.html#label_studio_sdk.project.Project.set_params), such as task sampling or the model version used to display predictions.

See the [full SDK reference documentation for all available modules](/sdk/index.html), or review the available [API endpoints](/api) for any tasks that the SDK does not cover.

## Start using the Label Studio Python SDK

1. Install the SDK:
`pip install label-studio-sdk`
2. In your Python script, do the following:
1. Import the SDK.
2. Define your API key and Label Studio URL (API key is available at _Account_ page).
3. Connect to the API.
```python
# Define the URL where Label Studio is accessible and the API key for your user account
LABEL_STUDIO_URL = 'http://localhost:8080'
API_KEY = 'd6f8a2622d39e9d89ff0dfef1a80ad877f4ee9e3'

# Import the SDK and the client module
from label_studio_sdk import Client

# Connect to the Label Studio API and check the connection
ls = Client(url=LABEL_STUDIO_URL, api_key=API_KEY)
ls.check_connection()
```

## Create a project with the Label Studio Python SDK

Create a project in Label Studio using the SDK. Specify the project title and the labeling configuration. Choose your labeling configuration based on the type of labeling that you wish to perform. See the available [templates for Label Studio projects](/templates), or set a blank configuration with `<View></View>`.

For example, create an audio transcription project in your Python code:
```python
project = ls.start_project(
title='Audio Transcription Project',
label_config='''
<View>
<Header value="Listen to the audio" />
<Audio name="audio" value="$audio" />
<Header value="Write the transcription" />
<TextArea name="transcription" toName="audio"
rows="4" editable="true" maxSubmissions="1" />
</View>
'''
)
```

For more about what you can do with the project module of the SDK, see the [project module SDK reference](/sdk/project.html).

## Import tasks with the Label Studio Python SDK

You can import tasks from your script using the Label Studio Python SDK.

For a specific project, you can import tasks in [Label Studio JSON format](tasks.html#Basic-Label-Studio-JSON-format) or [connect to cloud storage providers](https://github.com/heartexlabs/label-studio-sdk/blob/master/examples/annotate_data_from_gcs/annotate_data_from_gcs.ipynb) and import image, audio, or video files directly.

```python
project.import_tasks(
[
{'image': 'https://data.heartex.net/open-images/train_0/mini/0045dd96bf73936c.jpg'},
{'image': 'https://data.heartex.net/open-images/train_0/mini/0083d02f6ad18b38.jpg'}
]
)
```

You can also import predictions:
- [Add predictions to an existing task](#Add-predictions-to-existing-tasks-with-the-Label-Studio-Python-SDK)
- [Import pre-annotated tasks](#Import-pre-annotated-tasks-into-Label-Studio)

### Add predictions to existing tasks with the Label Studio Python SDK

You can add predictions to existing tasks in Label Studio in your Python script.

For an existing simple image classification project, you can do the following to add predictions of "Dog" for image tasks that you retrieve:
```python
task_ids = project.get_tasks_ids()
project.create_prediction(task_ids[0], result='Dog', score=0.9)
```

For complex cases, such as object detection with bounding boxes, you can specify structured results:
```python
project.create_prediction(task_ids[1], result={"x": 10, "y": 20, "width": 30, "height": 40, "label": ["Dog"]}, score=0.9)
```

For another example, see the [Jupyter notebook example of importing pre-annotated data](https://github.com/heartexlabs/label-studio-sdk/blob/master/examples/import_preannotations/import_preannotations.ipynb).

### Import pre-annotated tasks into Label Studio

You can also import predictions together with tasks as pre-annotated tasks. The SDK offers several ways that you can import pre-annotations into Label Studio.

One way is to import tasks in a simple JSON format, where one key in the JSON identifies the data object being labeled, and the other is the key containing the prediction.

In this example, import predictions for an image classification task:
```python
project.import_tasks(
[{'image': f'https://data.heartex.net/open-images/train_0/mini/0045dd96bf73936c.jpg', 'pet': 'Dog'},
{'image': f'https://data.heartex.net/open-images/train_0/mini/0083d02f6ad18b38.jpg', 'pet': 'Cat'}],
preannotated_from_fields=['pet']
)
```
The image is specified in the `image` key using a public URL, and the prediction is referenced in an arbitrary `pet` key, which is then specified in the `preannotated_from_fields()` method.

For more examples, see the [Jupyter notebook example of importing pre-annotated data](https://github.com/heartexlabs/label-studio-sdk/blob/master/examples/import_preannotations/import_preannotations.ipynb).

## Prepare and manage data with filters

You can also use the SDK to control how tasks appear in the data manager to annotators or reviewers. You can create custom filters and ordering for the tasks based on parameters that you specify with the SDK. This lets you have more granular control over which tasks in your dataset get labeled or reviewed, and in which order.

### Prepare unlabeled data with filters

For example, you can create a filter to prepare tasks to be annotated. For example, if you want annotators to focus on tasks in the first 1000 tasks in a dataset that contain the word "possum" in the field "text" in the task data, do the following:
```python
from label_studio_sdk.data_manager import Filters, Column, Type, Operator

Filters.create(Filters.AND, [
Filters.item(
Column.id,
Operator.GREATER_OR_EQUAL,
Type.Number,
Filters.value(1)
),
Filters.item(
Column.id,
Operator.LESS_OR_EQUAL,
Type.Number,
Filters.value(1000)
),
Filters.item(
Column.data(text),
Operator.CONTAINS,
Type.String,
Filters.value("possum")
)
])
```

### Manage annotations with filters

For example, to create a filter that displays only tasks with an ID greater than 42 or that were annotated between November 1, 2021, and now, do the following:
```python
from label_studio_sdk.data_manager import Filters, Column, Type, Operator

Filters.create(Filters.OR, [
Filters.item(
Column.id,
Operator.GREATER,
Type.Number,
Filters.value(42)
),
Filters.item(
Column.completed_at,
Operator.IN,
Type.Datetime,
Filters.value(
datetime(2021, 11, 1),
datetime.now()
)
)
])
```
You can use this example filter to prepare completed tasks for review in Label Studio Enterprise.
2 changes: 1 addition & 1 deletion docs/source/guide/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The following command line arguments are optional and must be specified with `la
| `-d` `--debug` | N/A | `False` | Enable debug mode for troubleshooting Label Studio. |
| `-c` `--config` | `CONFIG_PATH` | `default_config.json` | Deprecated, do not use. Specify the path to the server configuration for Label Studio. |
| `-l` `--label-config` | `LABEL_STUDIO_LABEL_CONFIG` | `None` | Path to the label configuration file for a specific Label Studio project. See [Set up your labeling project](setup.html). |
| `--ml-backends` | `LABEL_STUDIO_ML_BACKENDS` | `None` | Command line argument is deprecated. Specify the URLs for one or more machine learning backends. See [Set up machine learning with your labeling process](ml.html). |
| `--ml-backends` | N/A | `None` | Command line argument is deprecated. Specify the URLs for one or more machine learning backends. See [Set up machine learning with your labeling process](ml.html). |
| `--sampling` | N/A | `sequential` | Specify one of sequential or uniform to define the order for labeling tasks. See [Set up task sampling for your project](start.html#Set_up_task_sampling_for_your_project) on this page. |
| `--log-level` | N/A | `ERROR` | One of DEBUG, INFO, WARNING, or ERROR. Use to specify the logging level for the Label Studio server. |
| `-p` `--port` | `LABEL_STUDIO_PORT` | `8080` | Specify the web server port for Label Studio. Defaults to 8080. See [Run Label Studio on localhost with a different port](start.html#Run-Label-Studio-on-localhost-with-a-different-port) on this page. |
Expand Down
2 changes: 2 additions & 0 deletions docs/themes/htx/layout/partials/header.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
<a href="/guide/install.html" class="w-dropdown-link">Install</a>
<a href="/guide/tasks.html" class="w-dropdown-link">Import Data</a>
<a href="/api" class="w-dropdown-link">API Reference</a>
<a href="/sdk" class="w-dropdown-link">SDK Reference</a>
<div style="margin-top: 7px; margin-bottom:7px; border-bottom:1px solid #ccc"></div>
<a href="/tags" class="w-dropdown-link <%- page.type === "tags" ? "current": "" %>">Customizable Tags</a>
<a href="/templates" class="w-dropdown-link <%- page.type === "templates" ? "current": "" %>">Labeling Templates</a>
</nav>
Expand Down
2 changes: 1 addition & 1 deletion label_studio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
package_name = 'label-studio'

# Package version
__version__ = '1.3.post1'
__version__ = '1.4'

# pypi info
__latest_version__ = None
Expand Down
8 changes: 7 additions & 1 deletion label_studio/core/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DJANGO_DB_MYSQL = 'mysql'
DJANGO_DB_SQLITE = 'sqlite'
DJANGO_DB_POSTGRESQL = 'postgresql'
DJANGO_DB = 'default'
DATABASE_NAME_DEFAULT = os.path.join(BASE_DATA_DIR, 'label_studio.sqlite3')
DATABASE_NAME = get_env('DATABASE_NAME', DATABASE_NAME_DEFAULT)
DATABASES_ALL = {
'default': {
DJANGO_DB_POSTGRESQL: {
'ENGINE': 'django.db.backends.postgresql',
'USER': get_env('POSTGRE_USER', 'postgres'),
'PASSWORD': get_env('POSTGRE_PASSWORD', 'postgres'),
Expand All @@ -95,6 +96,7 @@
},
},
}
DATABASES_ALL['default'] = DATABASES_ALL[DJANGO_DB_POSTGRESQL]
DATABASES = {'default': DATABASES_ALL.get(get_env('DJANGO_DB', 'default'))}

LOGGING = {
Expand Down Expand Up @@ -448,3 +450,7 @@ def collect_versions_dummy(**kwargs):

mimetypes.add_type("application/javascript", ".js", True)
mimetypes.add_type("image/png", ".png", True)


# fields name was used in DM api before
REST_FLEX_FIELDS = {"FIELDS_PARAM": "include"}
4 changes: 2 additions & 2 deletions label_studio/core/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ def collect_versions(force=False):

# main pypi package
result = {
'release': label_studio.__version__,
'label-studio-os-package': {
'version': label_studio.__version__,
'short_version': '.'.join(label_studio.__version__.split('.')[:2]),
Expand All @@ -431,8 +432,7 @@ def collect_versions(force=False):
'current_version_is_outdated': label_studio.__current_version_is_outdated__
},
# backend full git info
'label-studio-os-backend': version.get_git_commit_info(),
'release': label_studio.__version__
'label-studio-os-backend': version.get_git_commit_info()
}

# label studio frontend
Expand Down
12 changes: 6 additions & 6 deletions label_studio/core/utils/windows_sqlite_fix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import colorama
import logging
import urllib.request
import requests


logger = logging.getLogger('main')
Expand All @@ -22,11 +22,11 @@ def start_fix():

src = os.path.join(work_dir, 'sqlite.zip')
try:
with urllib.request.urlopen(url) as f:
with open(src, 'wb') as f_out:
f_out.write(f.read())
except:
print(colorama.Fore.LIGHTRED_EX + "\nCan't download sqlite.zip. Please, download it manually:\n" + url)
with open(src, 'wb') as f_out:
resp = requests.get(url, verify=False)
f_out.write(resp.content)
except Exception as e:
print(colorama.Fore.LIGHTRED_EX + f"\nCan't download sqlite.zip: {e}\nPlease, download it manually and extract in the current directory:\n" + url)
print(colorama.Fore.WHITE)
exit()

Expand Down
9 changes: 2 additions & 7 deletions label_studio/data_manager/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,6 @@ class TaskListAPI(generics.ListAPIView):
DELETE=all_permissions.tasks_delete,
)

def get_serializer_class(self):
if bool_from_request(self.request.query_params, 'only_ids', False):
return TaskIDOnlySerializer
return self.task_serializer_class

@staticmethod
def get_task_serializer_context(request, project):
storage = find_first_many_to_one_related_field_by_prefix(project, '.*io_storages.*')
Expand Down Expand Up @@ -254,7 +249,7 @@ def get(self, request):
tasks_for_predictions = Task.objects.filter(id__in=ids, predictions__isnull=True)
evaluate_predictions(tasks_for_predictions)

serializer = self.get_serializer_class()(page, many=True, context=context)
serializer = self.task_serializer_class(page, many=True, context=context)
return self.get_paginated_response(serializer.data)

# all tasks
Expand All @@ -263,7 +258,7 @@ def get(self, request):
queryset = Task.prepared.annotate_queryset(
queryset, fields_for_evaluation=fields_for_evaluation, all_fields=all_fields
)
serializer = self.get_serializer_class()(queryset, many=True, context=context)
serializer = self.task_serializer_class(queryset, many=True, context=context)
return Response(serializer.data)


Expand Down
14 changes: 10 additions & 4 deletions label_studio/data_manager/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ class Meta:
'predictions_model_versions'
]

def to_representation(self, obj):
""" Dynamically manage including of some fields in the API result
"""
ret = super(DataManagerTaskSerializer, self).to_representation(obj)
if not self.context.get('annotations'):
ret.pop('annotations', None)
if not self.context.get('predictions'):
ret.pop('predictions', None)
return ret

def _pretty_results(self, task, field, unique=False):
if not hasattr(task, field) or getattr(task, field) is None:
return ''
Expand Down Expand Up @@ -231,13 +241,9 @@ def get_predictions_results(self, task):
return self._pretty_results(task, 'predictions_results')

def get_annotations(self, task):
if not self.context.get('annotations'):
return []
return AnnotationSerializer(task.annotations, many=True, default=[], read_only=True).data

def get_predictions(self, task):
if not self.context.get('predictions'):
return []
return PredictionSerializer(task.predictions, many=True, default=[], read_only=True).data

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion label_studio/frontend/dist/dm/js/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/dm/js/main.js.map

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions label_studio/frontend/dist/dm/version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"message": "Merge branch 'feature/dev-609/source-from-server' of github.com:heartexlabs/dm2 into feature/dev-609/source-from-server",
"commit": "e52959c02510bf31bfae8e7c3e336135097570e0",
"branch": "feature/dev-609/source-from-server",
"date": "2021-11-16T16:51:40Z"
"message": "DEV-609: Load task data from server (#22)\n\n* Load task data from server\r\n\r\n* Load task data from server\r\n\r\n* Run actions on branch\r\n\r\nCo-authored-by: Sergey <Gondragos@gmail.com>",
"commit": "feb9f1db923039b098fd0122f3d6a87bdc224a79",
"branch": "master",
"date": "2021-11-18T20:01:55Z"
}
2 changes: 1 addition & 1 deletion label_studio/frontend/dist/lsf/css/main.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/lsf/css/main.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/lsf/js/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion label_studio/frontend/dist/lsf/js/main.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions label_studio/frontend/dist/lsf/version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"message": "[fix] Linter issues",
"commit": "d0261d7362eaee235f6ab1f2d7f64ac17e7226c6",
"message": "[fix] Add observing of image size for special cases (#340)\n\n* [fix] Add observing of image size for special cases\r\n\r\n* Add ResizeObserver fallback\r\n\r\n* Run actions on sub-branches\r\n\r\n(cherry picked from commit c1f1744ecf68c32db8adcbae0ae1ca62fba92780)\r\n(cherry picked from commit 862e7d58476ad169457b68224b09301d04e067d1)\r\n\r\n* Fix tests\r\n\r\nCo-authored-by: Nick Skriabin <767890+nicholasrq@users.noreply.github.com>\r\nCo-authored-by: Nick <nr@fenelon.ru>\r\nCo-authored-by: Max Tkachenko <makseq@gmail.com>\r\nCo-authored-by: niklub <lubimov.nicolas@gmail.com>",
"commit": "cb2fd37cda67dd456700f95e64947b00319dc8b8",
"branch": "master",
"date": "2021-10-26T12:28:02Z"
"date": "2021-11-18T15:41:46Z"
}
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.

Loading

0 comments on commit 3e28c16

Please sign in to comment.