ddacs 2.0.0__py3-none-any.whl

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.
ddacs/__init__.py ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ DDACS - Deep Drawing and Cutting Simulations Dataset.
3
+
4
+ A Python package for accessing and processing the DDACS dataset containing
5
+ FEM simulations of sheet metal forming processes.
6
+
7
+ Examples:
8
+ >>> from ddacs import iter_ddacs, count_available_simulations
9
+ >>> count = count_available_simulations('./data')
10
+ >>> for sim_id, metadata, h5_path in iter_ddacs('./data'):
11
+ ... print(f"Simulation {sim_id}")
12
+
13
+ >>> from ddacs.utils import extract_mesh, extract_element_thickness
14
+ >>> vertices, triangles = extract_mesh('simulation.h5', 'blank')
15
+ """
16
+
17
+ __version__ = "2.0.0"
18
+
19
+ from .generators import (
20
+ count_available_simulations,
21
+ get_simulation_by_id,
22
+ iter_ddacs,
23
+ iter_h5_files,
24
+ sample_simulations,
25
+ )
26
+ from .utils import (
27
+ compute_von_mises,
28
+ display_structure,
29
+ extract_element_strain,
30
+ extract_element_stress,
31
+ extract_element_thickness,
32
+ extract_mesh,
33
+ extract_point_cloud,
34
+ extract_point_springback,
35
+ non_degenerate_mask,
36
+ )
37
+
38
+ # Optional PyTorch dataset (only if PyTorch is available)
39
+ try:
40
+ from .pytorch import DDACSDataset
41
+ except ImportError:
42
+ pass
43
+
44
+ __all__ = [
45
+ # Version
46
+ "__version__",
47
+ # Generators
48
+ "iter_ddacs",
49
+ "iter_h5_files",
50
+ "get_simulation_by_id",
51
+ "sample_simulations",
52
+ "count_available_simulations",
53
+ # Utils
54
+ "extract_point_cloud",
55
+ "extract_mesh",
56
+ "extract_element_thickness",
57
+ "extract_element_stress",
58
+ "extract_element_strain",
59
+ "extract_point_springback",
60
+ "compute_von_mises",
61
+ "non_degenerate_mask",
62
+ "display_structure",
63
+ # PyTorch (optional)
64
+ "DDACSDataset",
65
+ ]
ddacs/cli.py ADDED
@@ -0,0 +1,462 @@
1
+ """
2
+ DDACS dataset CLI for downloading data from DaRUS.
3
+
4
+ Provides commands to view dataset information and download files from the
5
+ DDACS (Deep Drawing and Cutting Simulations) dataset hosted on DaRUS.
6
+
7
+ Usage:
8
+ ddacs info # Show dataset info and versions
9
+ ddacs download # Download dataset v2.0, extract, ready to use
10
+ ddacs download --small # Download small test set for demos
11
+ ddacs download --files a.zip # Download specific files
12
+ ddacs download --no-extract # Download without extracting
13
+ ddacs download --keep-zip # Keep zip files after extraction
14
+ """
15
+
16
+ __version__ = "2.0.0"
17
+
18
+ import argparse
19
+ import os
20
+ import zipfile
21
+
22
+ import requests
23
+ from humanfriendly import format_size
24
+ from rich.console import Console
25
+ from rich.panel import Panel
26
+ from rich.progress import (
27
+ BarColumn,
28
+ DownloadColumn,
29
+ Progress,
30
+ TextColumn,
31
+ TimeRemainingColumn,
32
+ TransferSpeedColumn,
33
+ )
34
+ from rich.prompt import Confirm
35
+ from rich.table import Table
36
+
37
+ console = Console()
38
+
39
+ # DaRUS repository configuration
40
+ DARUS_URL = "https://darus.uni-stuttgart.de"
41
+ DDACS_DOI = "doi:10.18419/darus-4801"
42
+ DDACS_NAME = "DDACS"
43
+ SMALL_TEST_FILES = ["metadata.csv", "403926_406296.zip"]
44
+
45
+
46
+ def _api_get(endpoint: str, headers: dict[str, str]) -> dict | None:
47
+ """
48
+ Make GET request to DaRUS API.
49
+
50
+ Args:
51
+ endpoint: API endpoint path (without base URL).
52
+ headers: HTTP headers including optional API token.
53
+
54
+ Returns:
55
+ Response data dict if successful, None on error.
56
+ """
57
+ try:
58
+ r = requests.get(f"{DARUS_URL}/api/{endpoint}", headers=headers)
59
+ r.raise_for_status()
60
+ return r.json()["data"]
61
+ except Exception as e:
62
+ console.print(f"[red]API Error:[/red] {e}")
63
+ return None
64
+
65
+
66
+ def _get_version_files(version: str, headers: dict[str, str]) -> set[str]:
67
+ """
68
+ Fetch filenames for a specific dataset version.
69
+
70
+ Args:
71
+ version: Dataset version string (e.g., "2.0").
72
+ headers: HTTP headers for API authentication.
73
+
74
+ Returns:
75
+ Set of filenames in the specified version.
76
+ """
77
+ data = _api_get(f"datasets/:persistentId/versions/{version}?persistentId={DDACS_DOI}", headers)
78
+ if data:
79
+ return {f["dataFile"]["filename"] for f in data.get("files", [])}
80
+ return set()
81
+
82
+
83
+ def _get_file_info(file_meta: dict, original: bool = True) -> tuple[str, int]:
84
+ """
85
+ Extract filename and size from DaRUS file metadata.
86
+
87
+ Args:
88
+ file_meta: File metadata dict from DaRUS API.
89
+ original: If True, prefer original filename over transformed.
90
+
91
+ Returns:
92
+ Tuple of (filename, file_size_bytes).
93
+ """
94
+ df = file_meta["dataFile"]
95
+ if original and "originalFileName" in df:
96
+ return df["originalFileName"], df.get("originalFileSize", df["filesize"])
97
+ return df["filename"], df["filesize"]
98
+
99
+
100
+ def _compute_changes(current_files: set[str], previous_files: set[str]) -> list[str]:
101
+ """
102
+ Compute file changes between two dataset versions.
103
+
104
+ Args:
105
+ current_files: Set of filenames in current version.
106
+ previous_files: Set of filenames in previous version.
107
+
108
+ Returns:
109
+ List of change descriptions with Rich markup for display.
110
+ """
111
+ added = current_files - previous_files
112
+ removed = previous_files - current_files
113
+
114
+ changes = []
115
+ for f in sorted(added):
116
+ changes.append(f"[green]+ {f}[/green]")
117
+ for f in sorted(removed):
118
+ changes.append(f"[red]- {f}[/red]")
119
+
120
+ return changes if changes else ["no changes"]
121
+
122
+
123
+ def cmd_info(args: argparse.Namespace) -> None:
124
+ """
125
+ Display dataset information and available versions.
126
+
127
+ Args:
128
+ args: Parsed command-line arguments containing optional token.
129
+ """
130
+ headers = {"X-Dataverse-key": args.token} if args.token else {}
131
+
132
+ with console.status("[bold blue]Fetching dataset information..."):
133
+ versions = _api_get(f"datasets/:persistentId/versions?persistentId={DDACS_DOI}", headers)
134
+ if not versions:
135
+ return
136
+
137
+ dataset_url = f"{DARUS_URL}/dataset.xhtml?persistentId={DDACS_DOI}"
138
+ latest = versions[0] if versions else {}
139
+ license_name = latest.get("license", {}).get("name", "Unknown")
140
+
141
+ info = (
142
+ f"[bold]URL:[/bold] [link={dataset_url}]{dataset_url}[/link]\n"
143
+ f"[bold]Persistent ID:[/bold] {DDACS_DOI}\n"
144
+ f"[bold]Authors:[/bold] Sebastian Baum, Pascal Heinzelmann\n"
145
+ f"[bold]License:[/bold] {license_name}"
146
+ )
147
+ console.print()
148
+ console.print(Panel(info, title=f"{DDACS_NAME} Dataset", border_style="cyan"))
149
+
150
+ version_files = {}
151
+ with console.status("[bold blue]Fetching version details..."):
152
+ for v in versions:
153
+ ver_num = v.get("versionNumber", "?")
154
+ ver_minor = v.get("versionMinorNumber", "0")
155
+ ver_str = f"{ver_num}.{ver_minor}"
156
+ if "files" in v:
157
+ version_files[ver_str] = {f["dataFile"]["filename"] for f in v.get("files", [])}
158
+ else:
159
+ version_files[ver_str] = _get_version_files(ver_str, headers)
160
+
161
+ table = Table(
162
+ title="Available Versions", show_header=True, header_style="bold cyan", show_lines=True
163
+ )
164
+ table.add_column("Version", style="white")
165
+ table.add_column("State", style="yellow")
166
+ table.add_column("Release Date", style="green")
167
+ table.add_column("Files", justify="right")
168
+ table.add_column("Changes", max_width=40)
169
+ table.add_column("Notes", max_width=40)
170
+
171
+ version_list = list(version_files.keys())
172
+ for i, v in enumerate(versions):
173
+ version_num = f"{v.get('versionNumber', '?')}.{v.get('versionMinorNumber', '0')}"
174
+ state = v.get("versionState", "Unknown")
175
+ release_date = v.get("releaseTime", v.get("createTime", "Unknown"))
176
+ if release_date and release_date != "Unknown":
177
+ release_date = release_date[:10]
178
+
179
+ file_count = v.get("fileCount", len(v.get("files", [])))
180
+ version_note = v.get("versionNote", "-")
181
+
182
+ if state == "RELEASED":
183
+ state_str = "[green]Released[/green]"
184
+ elif state == "DRAFT":
185
+ state_str = "[yellow]Draft[/yellow]"
186
+ else:
187
+ state_str = state
188
+
189
+ if i < len(version_list) - 1:
190
+ current_files = version_files.get(version_num, set())
191
+ previous_files = version_files.get(version_list[i + 1], set())
192
+ changes = _compute_changes(current_files, previous_files)
193
+ else:
194
+ changes = ["initial release"]
195
+
196
+ table.add_row(
197
+ version_num, state_str, release_date, str(file_count), "\n".join(changes), version_note
198
+ )
199
+
200
+ console.print()
201
+ console.print(table)
202
+ console.print()
203
+ console.print("Use 'ddacs download <version>' to download a specific version of the dataset.")
204
+
205
+
206
+ def cmd_download(args: argparse.Namespace) -> None:
207
+ """
208
+ Download dataset files from DaRUS repository.
209
+
210
+ Handles file selection, download with progress display, and optional
211
+ extraction of zip archives.
212
+
213
+ Args:
214
+ args: Parsed command-line arguments containing version, output path,
215
+ and download options (files, small, no_extract, keep_zip, yes).
216
+ """
217
+ headers = {"X-Dataverse-key": args.token} if args.token else {}
218
+
219
+ with console.status("[bold blue]Fetching dataset metadata..."):
220
+ data = _api_get(
221
+ f"datasets/:persistentId/versions/{args.version}?persistentId={DDACS_DOI}",
222
+ headers,
223
+ )
224
+ if not data:
225
+ return
226
+ all_files = data["files"]
227
+
228
+ version_number = data.get("versionNumber", "?")
229
+ version_minor = data.get("versionMinorNumber", "0")
230
+ version_state = data.get("versionState", "")
231
+ last_update = data.get("lastUpdateTime", "Unknown")[:10]
232
+ license_name = data.get("license", {}).get("name", "Unknown")
233
+ dataset_url = f"{DARUS_URL}/dataset.xhtml?persistentId={DDACS_DOI}"
234
+
235
+ version_str = f"{version_number}.{version_minor}"
236
+ if version_state == "DRAFT":
237
+ version_str += " [yellow](Draft)[/yellow]"
238
+
239
+ dataset_info = (
240
+ f"[bold]URL:[/bold] [link={dataset_url}]{dataset_url}[/link]\n"
241
+ f"[bold]Persistent ID:[/bold] {DDACS_DOI}\n"
242
+ f"[bold]Version:[/bold] {version_str}\n"
243
+ f"[bold]Last Update:[/bold] {last_update}\n"
244
+ f"[bold]License:[/bold] {license_name}"
245
+ )
246
+ console.print()
247
+ console.print(Panel(dataset_info, title=f"{DDACS_NAME} Dataset", border_style="cyan"))
248
+
249
+ original = True # Always download original format
250
+
251
+ # Determine which files to download
252
+ if args.files:
253
+ # User specified specific files
254
+ selected_files = [
255
+ f
256
+ for f in all_files
257
+ if _get_file_info(f, original)[0] in args.files
258
+ or f["dataFile"]["filename"] in args.files
259
+ ]
260
+ elif args.small:
261
+ # Download small test set for demos/testing
262
+ selected_files = [
263
+ f
264
+ for f in all_files
265
+ if _get_file_info(f, original)[0] in SMALL_TEST_FILES
266
+ or f["dataFile"]["filename"] in SMALL_TEST_FILES
267
+ ]
268
+ else:
269
+ # Default: download all files
270
+ selected_files = all_files
271
+
272
+ if not selected_files:
273
+ console.print("[yellow]No files found matching criteria.[/yellow]")
274
+ return
275
+
276
+ selected_files.sort(key=lambda f: f["dataFile"]["filesize"])
277
+
278
+ table = Table(title="Files to Download", header_style="bold cyan")
279
+ table.add_column("Filename", max_width=40)
280
+ table.add_column("Size", justify="right", style="green")
281
+ table.add_column("Description", max_width=50)
282
+
283
+ total_bytes = 0
284
+ for f in selected_files:
285
+ name, size = _get_file_info(f, original)
286
+ desc = f.get("description", "No description")
287
+ total_bytes += size
288
+ table.add_row(name, format_size(size), desc)
289
+
290
+ console.print()
291
+ console.print(table)
292
+
293
+ summary = (
294
+ f"[bold]Files:[/bold] {len(selected_files)}\n"
295
+ f"[bold]Total size:[/bold] {format_size(total_bytes)}\n"
296
+ f"[bold]Destination:[/bold] {os.path.abspath(args.out)}"
297
+ )
298
+ console.print()
299
+ console.print(Panel(summary, title="Download Summary", border_style="blue"))
300
+
301
+ console.print()
302
+ if not args.yes and not Confirm.ask("Proceed with download?"):
303
+ console.print("[yellow]Download cancelled.[/yellow]")
304
+ return
305
+
306
+ os.makedirs(args.out, exist_ok=True)
307
+
308
+ downloaded_files = []
309
+ failed_downloads = []
310
+ failed_extractions = []
311
+
312
+ with Progress(
313
+ TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
314
+ BarColumn(bar_width=40),
315
+ "[progress.percentage]{task.percentage:>3.1f}%",
316
+ DownloadColumn(),
317
+ TransferSpeedColumn(),
318
+ TimeRemainingColumn(),
319
+ console=console,
320
+ ) as progress:
321
+ for f in selected_files:
322
+ file_name, _ = _get_file_info(f, original)
323
+ file_id = f["dataFile"]["id"]
324
+ directory = f.get("directoryLabel", "")
325
+
326
+ dl_url = f"{DARUS_URL}/api/access/datafile/{file_id}"
327
+ if original:
328
+ dl_url += "?format=original"
329
+
330
+ target_dir = os.path.join(args.out, directory)
331
+ os.makedirs(target_dir, exist_ok=True)
332
+ local_path = os.path.join(target_dir, file_name)
333
+
334
+ try:
335
+ with requests.get(dl_url, headers=headers, stream=True) as r:
336
+ r.raise_for_status()
337
+ total_file_size = int(r.headers.get("content-length", 0))
338
+
339
+ task = progress.add_task(
340
+ "download",
341
+ filename=file_name[:30],
342
+ total=total_file_size,
343
+ )
344
+
345
+ with open(local_path, "wb") as out_f:
346
+ for chunk in r.iter_content(chunk_size=8192):
347
+ written = out_f.write(chunk)
348
+ progress.update(task, advance=written)
349
+
350
+ downloaded_files.append(local_path)
351
+ except Exception as e:
352
+ failed_downloads.append((file_name, str(e)))
353
+ console.print(f"[red]Failed to download {file_name}:[/red] {e}")
354
+
355
+ if not args.no_extract and downloaded_files:
356
+ console.print()
357
+ zip_files = [p for p in downloaded_files if p.endswith(".zip")]
358
+ if zip_files:
359
+ with Progress(
360
+ TextColumn("[bold blue]{task.description}", justify="right"),
361
+ BarColumn(bar_width=40),
362
+ "[progress.percentage]{task.percentage:>3.1f}%",
363
+ TimeRemainingColumn(),
364
+ TextColumn("{task.fields[current_file]}"),
365
+ console=console,
366
+ ) as progress:
367
+ for local_path in zip_files:
368
+ zip_name = os.path.basename(local_path)
369
+ try:
370
+ extract_dir = os.path.dirname(local_path)
371
+ with zipfile.ZipFile(local_path, "r") as zf:
372
+ members = zf.namelist()
373
+ task = progress.add_task(
374
+ f"Extracting {zip_name[:20]}",
375
+ total=len(members),
376
+ current_file="",
377
+ )
378
+ for member in members:
379
+ progress.update(task, current_file=os.path.basename(member)[:30])
380
+ zf.extract(member, extract_dir)
381
+ progress.advance(task)
382
+ console.print(f"[green]Extracted:[/green] {zip_name}")
383
+ if not args.keep_zip:
384
+ os.remove(local_path)
385
+ console.print(f"[dim]Removed:[/dim] {zip_name}")
386
+ except Exception as e:
387
+ failed_extractions.append((zip_name, str(e)))
388
+ console.print(f"[red]Failed to extract {zip_name}:[/red] {e}")
389
+
390
+ console.print()
391
+ success_count = len(downloaded_files)
392
+ total_count = len(selected_files)
393
+
394
+ if failed_downloads or failed_extractions:
395
+ summary_lines = [f"[green]Downloaded {success_count}/{total_count} file(s)[/green]"]
396
+ if failed_downloads:
397
+ summary_lines.append(f"[red]Failed downloads: {len(failed_downloads)}[/red]")
398
+ if failed_extractions:
399
+ summary_lines.append(f"[red]Failed extractions: {len(failed_extractions)}[/red]")
400
+ console.print(
401
+ Panel("\n".join(summary_lines), title="Complete with errors", border_style="yellow")
402
+ )
403
+ else:
404
+ console.print(
405
+ Panel(
406
+ f"[green]Successfully downloaded {success_count} file(s)[/green]",
407
+ title="Complete",
408
+ border_style="green",
409
+ )
410
+ )
411
+
412
+
413
+ def main() -> None:
414
+ """CLI entry point for DDACS dataset commands."""
415
+ parser = argparse.ArgumentParser(
416
+ prog="ddacs",
417
+ description="DDACS Dataset CLI - Download simulation data from DaRUS",
418
+ )
419
+ parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}")
420
+ parser.add_argument("--token", help="DaRUS API token (for draft access)")
421
+
422
+ subparsers = parser.add_subparsers(dest="command", help="Command")
423
+
424
+ subparsers.add_parser(
425
+ "info",
426
+ help="Show dataset info and versions",
427
+ description="Display dataset metadata, available versions, and changelog.",
428
+ )
429
+
430
+ dl_parser = subparsers.add_parser(
431
+ "download",
432
+ help="Download dataset files",
433
+ description="Download DDACS dataset files from DaRUS repository.",
434
+ )
435
+ dl_parser.add_argument(
436
+ "version", nargs="?", default="2.0", help="Dataset version (default: 2.0)"
437
+ )
438
+ dl_parser.add_argument("--files", nargs="+", help="Specific filenames to download")
439
+ dl_parser.add_argument(
440
+ "--small",
441
+ action="store_true",
442
+ help="Download small test set for demos/testing",
443
+ )
444
+ dl_parser.add_argument("--out", default="./data", help="Output directory (default: ./data)")
445
+ dl_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompt")
446
+ dl_parser.add_argument("--no-extract", action="store_true", help="Skip extraction of zip files")
447
+ dl_parser.add_argument(
448
+ "--keep-zip", action="store_true", help="Keep zip files after extraction"
449
+ )
450
+
451
+ args = parser.parse_args()
452
+
453
+ if args.command == "info":
454
+ cmd_info(args)
455
+ elif args.command == "download":
456
+ cmd_download(args)
457
+ else:
458
+ parser.print_help()
459
+
460
+
461
+ if __name__ == "__main__":
462
+ main()