unitlab 2.3.46__tar.gz → 2.3.48__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitlab-2.3.46/src/unitlab.egg-info → unitlab-2.3.48}/PKG-INFO +1 -1
- {unitlab-2.3.46 → unitlab-2.3.48}/setup.py +2 -2
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab/client.py +73 -21
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab/main.py +21 -3
- {unitlab-2.3.46 → unitlab-2.3.48/src/unitlab.egg-info}/PKG-INFO +1 -1
- {unitlab-2.3.46 → unitlab-2.3.48}/LICENSE.md +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/README.md +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/setup.cfg +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab/__init__.py +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab/__main__.py +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab/exceptions.py +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab/utils.py +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab.egg-info/SOURCES.txt +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab.egg-info/dependency_links.txt +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab.egg-info/entry_points.txt +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab.egg-info/requires.txt +0 -0
- {unitlab-2.3.46 → unitlab-2.3.48}/src/unitlab.egg-info/top_level.txt +0 -0
|
@@ -2,11 +2,11 @@ from setuptools import find_packages, setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="unitlab",
|
|
5
|
-
version="2.3.
|
|
5
|
+
version="2.3.48",
|
|
6
6
|
license="MIT",
|
|
7
7
|
author="Unitlab Inc.",
|
|
8
8
|
author_email="team@unitlab.ai",
|
|
9
|
-
packages=find_packages("src"),
|
|
9
|
+
packages=find_packages(where="src"),
|
|
10
10
|
include_package_data=True,
|
|
11
11
|
package_data={"static": ["*"], "Potato": ["*.so"]},
|
|
12
12
|
classifiers=[
|
|
@@ -113,24 +113,43 @@ class UnitlabClient:
|
|
|
113
113
|
def project_members(self, project_id, pretty=0):
|
|
114
114
|
return self._get(f"/api/sdk/projects/{project_id}/members/?pretty={pretty}")
|
|
115
115
|
|
|
116
|
-
def
|
|
116
|
+
def project_upload_info(self, project_id):
|
|
117
|
+
return self._get(f"/api/sdk/projects/{project_id}/upload-info/")
|
|
118
|
+
|
|
119
|
+
def project_upload_data(
|
|
120
|
+
self,
|
|
121
|
+
project_id,
|
|
122
|
+
directory,
|
|
123
|
+
batch_size=100,
|
|
124
|
+
sentences_per_chunk=10,
|
|
125
|
+
fps=1.0,
|
|
126
|
+
):
|
|
117
127
|
if not os.path.isdir(directory):
|
|
118
128
|
raise ValueError(f"Directory {directory} does not exist")
|
|
119
129
|
|
|
130
|
+
upload_info = self.project_upload_info(project_id)
|
|
131
|
+
accepted_formats = upload_info["accepted_formats"]
|
|
132
|
+
max_file_size_bytes = upload_info["max_file_size"]
|
|
133
|
+
generic_type = upload_info["generic_type"]
|
|
134
|
+
|
|
120
135
|
files = [
|
|
121
136
|
file
|
|
122
137
|
for files_list in (
|
|
123
|
-
glob.glob(os.path.join(directory, "") +
|
|
124
|
-
for
|
|
138
|
+
glob.glob(os.path.join(directory, "") + f"*.{ext}")
|
|
139
|
+
for ext in accepted_formats
|
|
125
140
|
)
|
|
126
141
|
for file in files_list
|
|
127
142
|
]
|
|
143
|
+
|
|
128
144
|
filtered_files = []
|
|
145
|
+
max_file_size_mb = max_file_size_bytes / 1024 / 1024
|
|
129
146
|
for file in files:
|
|
130
|
-
|
|
131
|
-
if
|
|
147
|
+
file_size_bytes = os.path.getsize(file)
|
|
148
|
+
if file_size_bytes > max_file_size_bytes:
|
|
149
|
+
file_size_mb = file_size_bytes / 1024 / 1024
|
|
132
150
|
logger.warning(
|
|
133
|
-
f"File {file} is too large ({
|
|
151
|
+
f"File {file} is too large ({file_size_mb:.4f} MB) skipping, "
|
|
152
|
+
f"max size is {max_file_size_mb:.2f} MB"
|
|
134
153
|
)
|
|
135
154
|
continue
|
|
136
155
|
filtered_files.append(file)
|
|
@@ -141,14 +160,20 @@ class UnitlabClient:
|
|
|
141
160
|
async def post_file(session: aiohttp.ClientSession, file: str, project_id: str):
|
|
142
161
|
async with aiofiles.open(file, "rb") as f:
|
|
143
162
|
form_data = aiohttp.FormData()
|
|
144
|
-
form_data.add_field("project", project_id)
|
|
145
163
|
form_data.add_field(
|
|
146
164
|
"file", await f.read(), filename=os.path.basename(file)
|
|
147
165
|
)
|
|
166
|
+
if generic_type == "text":
|
|
167
|
+
form_data.add_field("sentences_per_chunk", str(sentences_per_chunk))
|
|
168
|
+
elif generic_type == "video":
|
|
169
|
+
form_data.add_field("fps", str(fps))
|
|
170
|
+
|
|
148
171
|
try:
|
|
149
172
|
await asyncio.sleep(0.1)
|
|
150
173
|
async with session.post(
|
|
151
|
-
urllib.parse.urljoin(
|
|
174
|
+
urllib.parse.urljoin(
|
|
175
|
+
self.api_url, f"/api/sdk/projects/{project_id}/upload-data/"
|
|
176
|
+
),
|
|
152
177
|
data=form_data,
|
|
153
178
|
) as response:
|
|
154
179
|
response.raise_for_status()
|
|
@@ -204,16 +229,44 @@ class UnitlabClient:
|
|
|
204
229
|
return os.path.abspath(filename)
|
|
205
230
|
|
|
206
231
|
def dataset_download_files(self, dataset_id):
|
|
232
|
+
"""Download files from a dataset.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
dataset_id: UUID of the dataset/release to download.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Path to the download folder.
|
|
239
|
+
"""
|
|
207
240
|
response = self._post(
|
|
208
241
|
f"/api/sdk/datasets/{dataset_id}/", data={"download_type": "files"}
|
|
209
242
|
)
|
|
210
|
-
|
|
211
|
-
os.makedirs(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
243
|
+
base_folder = f"dataset-files-{dataset_id}"
|
|
244
|
+
os.makedirs(base_folder, exist_ok=True)
|
|
245
|
+
|
|
246
|
+
files_to_download = []
|
|
247
|
+
for dataset_file in response:
|
|
248
|
+
file_name = dataset_file["file_name"]
|
|
249
|
+
file_path = os.path.join(base_folder, file_name)
|
|
250
|
+
|
|
251
|
+
parent_dir = os.path.dirname(file_path)
|
|
252
|
+
if parent_dir:
|
|
253
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
254
|
+
|
|
255
|
+
if "content" in dataset_file:
|
|
256
|
+
if not os.path.isfile(file_path):
|
|
257
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
258
|
+
f.write(dataset_file["content"])
|
|
259
|
+
elif "source" in dataset_file and not os.path.isfile(file_path):
|
|
260
|
+
files_to_download.append(
|
|
261
|
+
{
|
|
262
|
+
"file_name": file_name,
|
|
263
|
+
"source": dataset_file["source"],
|
|
264
|
+
"file_path": file_path,
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if not files_to_download:
|
|
269
|
+
return base_folder
|
|
217
270
|
|
|
218
271
|
async def download_file(session: aiohttp.ClientSession, dataset_file: dict):
|
|
219
272
|
async with session.get(url=dataset_file["source"]) as r:
|
|
@@ -224,21 +277,20 @@ class UnitlabClient:
|
|
|
224
277
|
f"Error downloading file {dataset_file['file_name']} - {e}"
|
|
225
278
|
)
|
|
226
279
|
return 0
|
|
227
|
-
async with aiofiles.open(
|
|
228
|
-
os.path.join(folder, dataset_file["file_name"]), "wb"
|
|
229
|
-
) as f:
|
|
280
|
+
async with aiofiles.open(dataset_file["file_path"], "wb") as f:
|
|
230
281
|
async for chunk in r.content.iter_any():
|
|
231
282
|
await f.write(chunk)
|
|
232
283
|
return 1
|
|
233
284
|
|
|
234
285
|
async def main():
|
|
235
|
-
with tqdm.tqdm(total=len(
|
|
286
|
+
with tqdm.tqdm(total=len(files_to_download), ncols=80) as pbar:
|
|
236
287
|
async with aiohttp.ClientSession() as session:
|
|
237
288
|
tasks = [
|
|
238
|
-
download_file(session=session, dataset_file=
|
|
239
|
-
for
|
|
289
|
+
download_file(session=session, dataset_file=df)
|
|
290
|
+
for df in files_to_download
|
|
240
291
|
]
|
|
241
292
|
for f in asyncio.as_completed(tasks):
|
|
242
293
|
pbar.update(await f)
|
|
243
294
|
|
|
244
295
|
asyncio.run(main())
|
|
296
|
+
return base_folder
|
|
@@ -72,8 +72,25 @@ def upload(
|
|
|
72
72
|
directory: Annotated[
|
|
73
73
|
Path, typer.Option(help="Directory containing the data to be uploaded")
|
|
74
74
|
],
|
|
75
|
+
sentences_per_chunk: Annotated[
|
|
76
|
+
int,
|
|
77
|
+
typer.Option(
|
|
78
|
+
help="Number of sentences per chunk for text projects (default: 10, range: 3-50)"
|
|
79
|
+
),
|
|
80
|
+
] = 10,
|
|
81
|
+
fps: Annotated[
|
|
82
|
+
float,
|
|
83
|
+
typer.Option(
|
|
84
|
+
help="Frames per second for video projects (default: 1.0, min: 0.25)"
|
|
85
|
+
),
|
|
86
|
+
] = 1.0,
|
|
75
87
|
):
|
|
76
|
-
get_client(api_key).project_upload_data(
|
|
88
|
+
get_client(api_key).project_upload_data(
|
|
89
|
+
str(pk),
|
|
90
|
+
directory=directory,
|
|
91
|
+
sentences_per_chunk=sentences_per_chunk,
|
|
92
|
+
fps=fps,
|
|
93
|
+
)
|
|
77
94
|
|
|
78
95
|
|
|
79
96
|
@dataset_app.command(name="list", help="List datasets")
|
|
@@ -105,8 +122,9 @@ def dataset_download(
|
|
|
105
122
|
raise typer.BadParameter(
|
|
106
123
|
"Split type is required when download type is annotation"
|
|
107
124
|
)
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
print(get_client(api_key).dataset_download(pk, export_type, split_type))
|
|
126
|
+
else:
|
|
127
|
+
print(get_client(api_key).dataset_download_files(pk))
|
|
110
128
|
|
|
111
129
|
|
|
112
130
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|