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