|
48 | 48 | from kili.use_cases.label.process_shapefiles import get_json_response_from_shapefiles |
49 | 49 | from kili.use_cases.label.types import LabelToCreateUseCaseInput |
50 | 50 | from kili.use_cases.project.project import ProjectUseCases |
51 | | -from kili.utils.labels.geojson import enrich_geojson_with_kili_properties |
| 51 | +from kili.utils.labels.geojson import ( |
| 52 | + enrich_geojson_with_kili_properties, |
| 53 | + enrich_geojson_with_specific_mapping, |
| 54 | +) |
52 | 55 | from kili.utils.labels.parsing import ParsedLabel |
53 | 56 | from kili.utils.logcontext import for_all_methods, log_call |
54 | 57 |
|
@@ -1409,55 +1412,121 @@ def append_labels_from_geojson_files( |
1409 | 1412 | project_id: str, |
1410 | 1413 | asset_external_id: str, |
1411 | 1414 | geojson_file_paths: List[str], |
| 1415 | + job_names: Optional[List[str]] = None, |
| 1416 | + category_names: Optional[List[str]] = None, |
1412 | 1417 | ): |
1413 | 1418 | """Import and convert GeoJSON files into annotations for a specific asset in a Kili project. |
1414 | 1419 |
|
1415 | 1420 | This method processes GeoJSON feature collections, converts them to the appropriate |
1416 | 1421 | Kili annotation format, and appends them as labels to the specified asset. |
1417 | | - The GeoJSON features can contain job names and category information in their properties, |
1418 | | - or they will be automatically mapped based on geometry type and available jobs. |
| 1422 | +
|
| 1423 | + Three modes of import are supported: |
| 1424 | +
|
| 1425 | + 1. **With `kili` properties**: GeoJSON features contain 'kili' metadata in their properties |
| 1426 | + with job, type, and category information. |
| 1427 | +
|
| 1428 | + 2. **With specific job/category names**: Provide `job_names` and `category_names` to map |
| 1429 | + all compatible geometries from each file to the specified job and category. |
| 1430 | +
|
| 1431 | + 3. **Automatic mapping**: When no 'kili' properties or specific names are provided, |
| 1432 | + geometries are automatically mapped based on type and available jobs. |
1419 | 1433 |
|
1420 | 1434 | Args: |
1421 | 1435 | project_id: The ID of the Kili project to add the labels to. |
1422 | 1436 | asset_external_id: The external ID of the asset to label. |
1423 | 1437 | geojson_file_paths: List of file paths to the GeoJSON files to be processed. |
1424 | | - Each file should contain a FeatureCollection with features that optionally have |
1425 | | - 'kili' metadata in their properties, including 'job', 'type', and 'categories'. |
| 1438 | + Each file should contain a FeatureCollection with features. |
| 1439 | + job_names: Optional list of job names in the Kili project, one for each GeoJSON file. |
| 1440 | + When provided, all compatible geometries from the corresponding file will be |
| 1441 | + mapped to this job. Must have the same length as `geojson_file_paths`. |
| 1442 | + category_names: Optional list of category names, one for each GeoJSON file. |
| 1443 | + When provided, all geometries from the corresponding file will be assigned |
| 1444 | + to this category. Must have the same length as `geojson_file_paths`. |
| 1445 | + Each category must exist in the corresponding job's ontology. |
1426 | 1446 |
|
1427 | 1447 | Note: |
1428 | | - The GeoJSON features can contain a 'kili' property with the following structure: |
1429 | | - - 'job': The name of the job in the Kili project |
1430 | | - - 'type': The annotation type ('marker', 'polyline', 'semantic', etc.) |
1431 | | - - 'categories': List of category objects with 'name' field |
1432 | | -
|
1433 | | - If 'kili' properties are missing, they will be automatically mapped based on: |
1434 | | - - Point geometries → first 'marker' job |
1435 | | - - LineString geometries → first 'polyline' job |
1436 | | - - Polygon geometries → first 'polygon' job, or 'semantic' if no polygon job found |
1437 | | - - MultiPolygon geometries → first job with 'semantic' tool |
| 1448 | + **Geometry-to-job compatibility:** |
| 1449 | + - Point geometries → jobs with 'marker' tool |
| 1450 | + - LineString geometries → jobs with 'polyline' tool |
| 1451 | + - Polygon geometries → jobs with 'polygon' or 'semantic' tool |
| 1452 | + - MultiPolygon geometries → jobs with 'semantic' tool |
| 1453 | +
|
| 1454 | + **GeoJSON 'kili' properties structure (Mode 1):** |
| 1455 | + ```json |
| 1456 | + { |
| 1457 | + "properties": { |
| 1458 | + "kili": { |
| 1459 | + "job": "job_name", |
| 1460 | + "type": "marker|polyline|polygon|semantic", |
| 1461 | + "categories": [{"name": "category_name"}] |
| 1462 | + } |
| 1463 | + } |
| 1464 | + } |
| 1465 | + ``` |
1438 | 1466 |
|
1439 | | - Supported geometries: Point, LineString, Polygon, and MultiPolygon. |
| 1467 | + **Automatic mapping priority (Mode 3):** |
| 1468 | + - Point → first available 'marker' job |
| 1469 | + - LineString → first available 'polyline' job |
| 1470 | + - Polygon → first available 'polygon' job, fallback to 'semantic' |
| 1471 | + - MultiPolygon → first available 'semantic' job |
1440 | 1472 |
|
1441 | 1473 | Examples: |
| 1474 | + Mode 1 - With kili properties in GeoJSON: |
1442 | 1475 | >>> kili.append_labels_from_geojson_files( |
1443 | 1476 | project_id="project_id", |
1444 | 1477 | asset_external_id="asset_1", |
1445 | 1478 | geojson_file_paths=["annotations.geojson"] |
1446 | 1479 | ) |
| 1480 | +
|
| 1481 | + Mode 2 - With specific job/category mapping: |
| 1482 | + >>> kili.append_labels_from_geojson_files( |
| 1483 | + project_id="project_id", |
| 1484 | + asset_external_id="asset_1", |
| 1485 | + geojson_file_paths=["points.geojson", "polygons.geojson"], |
| 1486 | + job_names=["MARKERS", "POLYGONS"], |
| 1487 | + category_names=["BUILDING", "ROAD"] |
| 1488 | + ) |
| 1489 | +
|
| 1490 | + Mode 3 - Automatic mapping: |
| 1491 | + >>> kili.append_labels_from_geojson_files( |
| 1492 | + project_id="project_id", |
| 1493 | + asset_external_id="asset_1", |
| 1494 | + geojson_file_paths=["mixed_geometries.geojson"] |
| 1495 | + ) |
1447 | 1496 | """ |
| 1497 | + if job_names is not None and category_names is not None: |
| 1498 | + if len(job_names) != len(geojson_file_paths): |
| 1499 | + raise ValueError("job_names must have the same length as geojson_file_paths") |
| 1500 | + if len(category_names) != len(geojson_file_paths): |
| 1501 | + raise ValueError("category_names must have the same length as geojson_file_paths") |
| 1502 | + if len(job_names) != len(category_names): |
| 1503 | + raise ValueError("job_names and category_names must have the same length") |
| 1504 | + elif job_names is not None or category_names is not None: |
| 1505 | + raise ValueError( |
| 1506 | + "Both job_names and category_names must be provided together, or both must be None" |
| 1507 | + ) |
| 1508 | + |
1448 | 1509 | json_interface = self.kili_api_gateway.get_project( |
1449 | 1510 | ProjectId(project_id), ("jsonInterface",) |
1450 | 1511 | )["jsonInterface"] |
1451 | 1512 |
|
1452 | 1513 | merged_json_response = {} |
1453 | 1514 |
|
1454 | | - for file_path in geojson_file_paths: |
1455 | | - with open(file_path, encoding="utf-8") as f: |
1456 | | - feature_collection = json.load(f) |
| 1515 | + for file_index, file_path in enumerate(geojson_file_paths): |
| 1516 | + with open(file_path, encoding="utf-8") as file: |
| 1517 | + feature_collection = json.load(file) |
1457 | 1518 |
|
1458 | | - enriched_feature_collection = enrich_geojson_with_kili_properties( |
1459 | | - feature_collection, json_interface |
1460 | | - ) |
| 1519 | + if job_names is not None and category_names is not None: |
| 1520 | + enriched_feature_collection = enrich_geojson_with_specific_mapping( |
| 1521 | + feature_collection, |
| 1522 | + json_interface, |
| 1523 | + job_names[file_index], |
| 1524 | + category_names[file_index], |
| 1525 | + ) |
| 1526 | + else: |
| 1527 | + enriched_feature_collection = enrich_geojson_with_kili_properties( |
| 1528 | + feature_collection, json_interface |
| 1529 | + ) |
1461 | 1530 |
|
1462 | 1531 | json_response = geojson_feature_collection_to_kili_json_response( |
1463 | 1532 | enriched_feature_collection |
|
0 commit comments