From ff43f097951c33beae51afabf94148e912b54b75 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Tue, 26 Sep 2023 21:18:36 -0400
Subject: [PATCH 01/15] add check for pandas NaN values

---
 labelbase/metadata.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/labelbase/metadata.py b/labelbase/metadata.py
index 30da65a..d4d5f9c 100644
--- a/labelbase/metadata.py
+++ b/labelbase/metadata.py
@@ -3,6 +3,7 @@
 from datetime import datetime
 from dateutil import parser
 import pytz
+import pandas
 
 def get_metadata_schema_to_type(client:labelboxClient, lb_mdo=False, invert:bool=False):
     """ Creates a dictionary where {key=metadata_schema_id: value=metadata_type} 
@@ -121,6 +122,8 @@ def process_metadata_value(metadata_value, metadata_type:str, parent_name:str, m
         return_value = None
     if str(metadata_value) == "nan": # Catch NaN values
         return_value = None
+    if pandas.isna(metadata_value): #Catch pandas df NaN values
+        return_value = None
     # By metadata type
     if metadata_type == "enum": # For enums, it must be a schema ID - if we can't match it, we have to skip it
         name_key = f"{parent_name}{divider}{str(metadata_value)}"

From fbda4206279de8f5f9d8ff161d89b3f9191ee510 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Tue, 3 Oct 2023 10:32:18 -0700
Subject: [PATCH 02/15] deleted pycache files

---
 .../__pycache__/__init__.cpython-311.pyc        | Bin 249 -> 0 bytes
 .../converters/__pycache__/coco.cpython-311.pyc | Bin 19341 -> 0 bytes
 2 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 labelbase/converters/__pycache__/__init__.cpython-311.pyc
 delete mode 100644 labelbase/converters/__pycache__/coco.cpython-311.pyc

diff --git a/labelbase/converters/__pycache__/__init__.cpython-311.pyc b/labelbase/converters/__pycache__/__init__.cpython-311.pyc
deleted file mode 100644
index edb3e8503ee5b988395915ab6849de48972a4d7c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 249
zcmZ3^%ge<81R`1iDT+Y)F^B^LOi;#WJ|JT{LkdF*V-7<UV+vz1gC^5Ukffg`(=ExI
z#H7@mq{QM>z2yA7vecrI)S_Y_GdcejCtPYJ!)K8CUm^OT#XxEOoYL&#l0<!%)Z*-t
z`~v-eqWrAX<dR~2=lr}pAR`~71ZFG}3t@qNe0*kJW=VX!UP0wA4x8Nkl+v73yCM#t
aC5%8^EDj_-Ff%eT-e6F;fQpLPfN}thT1Mso

diff --git a/labelbase/converters/__pycache__/coco.cpython-311.pyc b/labelbase/converters/__pycache__/coco.cpython-311.pyc
deleted file mode 100644
index 45df18a2ed1f3ce6bd2b55efda630b8382046134..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 19341
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
RJ|wUVQ1m&T^+is%{}1O%h^znr


From 889e4294f49931ff0c041af5cd56009d2658f50e Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Wed, 11 Oct 2023 09:55:30 -0700
Subject: [PATCH 03/15] improved upload result message

---
 labelbase/uploader.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index a12ab38..0ad7002 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -94,7 +94,7 @@ def batch_create_data_rows(
         
     """
     # Default error message    
-    e = "Success"
+    e = {}
     # Vet all global keys
     global_keys = list(upload_dict.keys()) # Get all global keys
     if verbose:
@@ -103,11 +103,14 @@ def batch_create_data_rows(
         gks = global_keys[i:] if i + batch_size >= len(global_keys) else global_keys[i:i+batch_size] # Batch of global keys to vet 
         existing_data_row_to_global_key = check_global_keys(client, gks) # Returns empty list if there are no duplicates
         loop_counter = 0
+        if skip_duplicates:
+            e['skipped_global_keys'] = []
         while existing_data_row_to_global_key:
             if skip_duplicates: # Drop in-use global keys if we're skipping duplicates
                 if verbose:
                     print(f"Warning: Global keys in this upload are in use by active data rows, skipping the upload of data rows affected") 
                 for gk in existing_data_row_to_global_key.values():
+                    e['skipped_global_keys'].append(gk)
                     del upload_dict[gk]
                 break
             else: # Create new suffix for taken global keys if we're not skipping duplicates
@@ -135,6 +138,7 @@ def batch_create_data_rows(
             dataset_id_to_upload_list[dataset_id] = []
         dataset_id_to_upload_list[dataset_id].append(data_row)
     # Perform uploads grouped by dataset ID
+    e['upload_results'] = []
     for dataset_id in dataset_id_to_upload_list:
         dataset = client.get_dataset(dataset_id)       
         upload_list = dataset_id_to_upload_list[dataset_id]
@@ -149,10 +153,11 @@ def batch_create_data_rows(
             task = dataset.create_data_rows(batch)
             task.wait_till_done()
             errors = task.errors
+            e['upload_results'].append(task.result)
             if errors:
                 if verbose: 
                     print(f'Error: Upload batch number {batch_number} unsuccessful')
-                e = errors
+                e['errors'] = errors
                 break
             else:
                 if verbose: 

From 53507c5e1b69c78c0b60d45904b22c300b94294a Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Fri, 13 Oct 2023 10:50:03 -0700
Subject: [PATCH 04/15] improved upload result message

---
 labelbase/uploader.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 0ad7002..85d3743 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -153,6 +153,7 @@ def batch_create_data_rows(
             task = dataset.create_data_rows(batch)
             task.wait_till_done()
             errors = task.errors
+            print(task.result_url)
             e['upload_results'].append(task.result)
             if errors:
                 if verbose: 

From f7c6cb7813cfc689fe71e86c6e56b7e2716ad564 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Fri, 13 Oct 2023 11:31:23 -0700
Subject: [PATCH 05/15] improved upload result message

---
 labelbase/uploader.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 85d3743..489ffe4 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -153,8 +153,7 @@ def batch_create_data_rows(
             task = dataset.create_data_rows(batch)
             task.wait_till_done()
             errors = task.errors
-            print(task.result_url)
-            e['upload_results'].append(task.result)
+            e['upload_results'].append(task.uid)
             if errors:
                 if verbose: 
                     print(f'Error: Upload batch number {batch_number} unsuccessful')

From 54e41da66ca454c080f8cedb39234b2ff8e9837f Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Fri, 13 Oct 2023 12:39:26 -0700
Subject: [PATCH 06/15] improved upload result message

---
 labelbase/uploader.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 489ffe4..f709989 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -151,6 +151,7 @@ def batch_create_data_rows(
             if verbose:
                 print(f'Batch #{batch_number}: {len(batch)} data rows')
             task = dataset.create_data_rows(batch)
+            print(task.uid)
             task.wait_till_done()
             errors = task.errors
             e['upload_results'].append(task.uid)

From 02fee1825caa47ed5e3c16f754ace0b32a581488 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Fri, 13 Oct 2023 15:12:55 -0700
Subject: [PATCH 07/15] improved upload result message

---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index 0bbb40e..f77f44b 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
 
 setuptools.setup(
       name='labelbase',
-      version='0.1.00',
+      version='0.1.06',
       author='Labelbox',
       author_email='raphael@labelbox.com',
       description='Labelbox Helper Library',      

From 33f9959dd8bf0f6b1c7bbd10cb79df08312a6d64 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Fri, 13 Oct 2023 15:57:56 -0700
Subject: [PATCH 08/15] improved upload result message

---
 labelbase/uploader.py | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index f709989..47bde2f 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -37,7 +37,7 @@ def create_global_key_to_data_row_id_dict(client:labelboxClient, global_keys:lis
                 global_key_to_data_row_dict[gks[i]] = res['results'][i]
     return global_key_to_data_row_dict
 
-def check_global_keys(client:labelboxClient, global_keys:list, batch_size=1000):
+def check_global_keys(client:labelboxClient, global_keys:list):
     """ Checks if data rows exist for a set of global keys - if data rows exist, returns as dictionary { key=data_row_id : value=global_key }
     Args:
         client                  :   Required (labelbox.client.Client) - Labelbox Client object    
@@ -52,20 +52,18 @@ def check_global_keys(client:labelboxClient, global_keys:list, batch_size=1000):
     # Enforce global keys as strings
     global_keys_list = [str(x) for x in global_keys]      
     # Batch global key checks
-    for i in range(0, len(global_keys_list), batch_size):
-        batch_gks = global_keys_list[i:] if i + batch_size >= len(global_keys_list) else global_keys_list[i:i+batch_size]  
-        # Get the datarow ids
-        res = client.get_data_row_ids_for_global_keys(batch_gks)     
-        # Check query job results for fetched data rows
-        for i in range(0, len(res["results"])):
-            data_row_id = res["results"][i]
-            if data_row_id:
-                existing_drid_to_gk[data_row_id] = batch_gks[i]
+    # Get the datarow ids
+    res = client.get_data_row_ids_for_global_keys(global_keys_list)     
+    # Check query job results for fetched data rows
+    for i in range(0, len(res["results"])):
+        data_row_id = res["results"][i]
+        if data_row_id:
+            existing_drid_to_gk[data_row_id] = global_keys_list[i]
     return existing_drid_to_gk
 
 def batch_create_data_rows(
     client:labelboxClient, upload_dict:dict, skip_duplicates:bool=True, 
-    divider:str="___", batch_size:int=20000, verbose:bool=False):
+    divider:str="___", batch_size:int=100000, verbose:bool=False):
     """ Uploads data rows, skipping duplicate global keys or auto-generating new unique ones. 
     
     upload_dict must be in the following format:

From 8f1c2cbb19d8cc6f348b82fc2c7f5990e508748a Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Mon, 16 Oct 2023 11:53:07 -0700
Subject: [PATCH 09/15] improved upload result message

---
 labelbase/uploader.py | 40 +++++++++++++++++++++++++++-------------
 1 file changed, 27 insertions(+), 13 deletions(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 47bde2f..5f56896 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -2,6 +2,7 @@
 from labelbox import Dataset as labelboxDataset
 from labelbox import Project as labelboxProject
 import uuid
+from concurrent.futures import ThreadPoolExecutor, as_completed
 
 def create_global_key_to_label_id_dict(client:labelboxClient, project_id:str, global_keys:list):
     """ Creates a dictionary where { key=global_key : value=label_id } by exporting labels from a project
@@ -63,7 +64,7 @@ def check_global_keys(client:labelboxClient, global_keys:list):
 
 def batch_create_data_rows(
     client:labelboxClient, upload_dict:dict, skip_duplicates:bool=True, 
-    divider:str="___", batch_size:int=100000, verbose:bool=False):
+    divider:str="___", batch_size:int=20000, verbose:bool=False):
     """ Uploads data rows, skipping duplicate global keys or auto-generating new unique ones. 
     
     upload_dict must be in the following format:
@@ -137,7 +138,9 @@ def batch_create_data_rows(
         dataset_id_to_upload_list[dataset_id].append(data_row)
     # Perform uploads grouped by dataset ID
     e['upload_results'] = []
+    e['errors'] = []
     for dataset_id in dataset_id_to_upload_list:
+        task_list = []
         dataset = client.get_dataset(dataset_id)       
         upload_list = dataset_id_to_upload_list[dataset_id]
         if verbose:
@@ -149,18 +152,25 @@ def batch_create_data_rows(
             if verbose:
                 print(f'Batch #{batch_number}: {len(batch)} data rows')
             task = dataset.create_data_rows(batch)
-            print(task.uid)
-            task.wait_till_done()
-            errors = task.errors
-            e['upload_results'].append(task.uid)
-            if errors:
-                if verbose: 
-                    print(f'Error: Upload batch number {batch_number} unsuccessful')
-                e['errors'] = errors
-                break
-            else:
-                if verbose: 
-                    print(f'Success: Upload batch number {batch_number} successful')  
+            task_list.append(task)
+            # task.wait_till_done()
+            # errors = task.errors
+            # e['upload_results'].append(task.uid)
+            # if errors:
+            #     if verbose: 
+            #         print(f'Error: Upload batch number {batch_number} unsuccessful')
+            #     e['errors'] = errors
+            #     break
+            # else:
+            #     if verbose: 
+            #         print(f'Success: Upload batch number {batch_number} successful')  
+        with ThreadPoolExecutor() as exc:
+            futures = [exc.submit(get_results_from_task, x) for x in task_list]
+            for future in as_completed(futures):
+                errors, results = future.result()
+                if errors:
+                    e['errors'] += errors
+                e['upload_results'] += results
     if verbose:
         print(f'Upload complete - all data rows uploaded')
     return e, upload_dict
@@ -491,3 +501,7 @@ def batch_upload_predictions(
     except Exception as error:
         e = error
     return e
+
+def get_results_from_task(task):
+    task.wait_till_done()
+    return task.errors, task.result

From 1cbc5d09e3da35bf8733dbd230372eb337c20235 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Mon, 16 Oct 2023 12:52:34 -0700
Subject: [PATCH 10/15] improved upload result message

---
 labelbase/uploader.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 5f56896..9bf60f2 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -64,7 +64,7 @@ def check_global_keys(client:labelboxClient, global_keys:list):
 
 def batch_create_data_rows(
     client:labelboxClient, upload_dict:dict, skip_duplicates:bool=True, 
-    divider:str="___", batch_size:int=20000, verbose:bool=False):
+    divider:str="___", batch_size:int=100000, verbose:bool=False):
     """ Uploads data rows, skipping duplicate global keys or auto-generating new unique ones. 
     
     upload_dict must be in the following format:
@@ -503,5 +503,5 @@ def batch_upload_predictions(
     return e
 
 def get_results_from_task(task):
-    task.wait_till_done()
+    task.wait_till_done(120)
     return task.errors, task.result

From b9b3682ee7207a5707aa232df32772eb4dd630d4 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Mon, 16 Oct 2023 12:56:43 -0700
Subject: [PATCH 11/15] improved upload result message

---
 labelbase/uploader.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 9bf60f2..7be2031 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -137,7 +137,6 @@ def batch_create_data_rows(
             dataset_id_to_upload_list[dataset_id] = []
         dataset_id_to_upload_list[dataset_id].append(data_row)
     # Perform uploads grouped by dataset ID
-    e['upload_results'] = []
     e['errors'] = []
     for dataset_id in dataset_id_to_upload_list:
         task_list = []
@@ -170,7 +169,6 @@ def batch_create_data_rows(
                 errors, results = future.result()
                 if errors:
                     e['errors'] += errors
-                e['upload_results'] += results
     if verbose:
         print(f'Upload complete - all data rows uploaded')
     return e, upload_dict
@@ -503,5 +501,5 @@ def batch_upload_predictions(
     return e
 
 def get_results_from_task(task):
-    task.wait_till_done(120)
-    return task.errors, task.result
+    task.wait_till_done()
+    return task.errors

From 82304704f070d494c4d584b6e361a018ce30796c Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Mon, 16 Oct 2023 13:14:10 -0700
Subject: [PATCH 12/15] improved upload result message

---
 labelbase/uploader.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/labelbase/uploader.py b/labelbase/uploader.py
index 7be2031..57a193c 100644
--- a/labelbase/uploader.py
+++ b/labelbase/uploader.py
@@ -166,7 +166,7 @@ def batch_create_data_rows(
         with ThreadPoolExecutor() as exc:
             futures = [exc.submit(get_results_from_task, x) for x in task_list]
             for future in as_completed(futures):
-                errors, results = future.result()
+                errors = future.result()
                 if errors:
                     e['errors'] += errors
     if verbose:

From bcbd4cd495196c0e9407596b36a58a2297abd89d Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Mon, 20 Nov 2023 17:15:53 -0700
Subject: [PATCH 13/15] debugging

---
 labelbase/connector.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/labelbase/connector.py b/labelbase/connector.py
index bc4c1e7..918440d 100644
--- a/labelbase/connector.py
+++ b/labelbase/connector.py
@@ -187,6 +187,8 @@ def determine_actions(
     attachments_action = True if attachment_index and not create_action else False    
     # Determine if we're batching data rows
     batch_action = False if (project_id == project_id_col == "") else True
+    print(project_id)
+    print(project_id_col)
     # Determine the upload_method if we're batching to projects
     annotate_action = upload_method if (upload_method in ["mal", "import", "ground-truth"]) and annotation_index and batch_action else ""      
     # "ground-truth" defaults to "import" if no model informtion is given

From 979ad1a086fd8f296ed4746474507a93d76f1f28 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Tue, 23 Jan 2024 18:21:10 -0700
Subject: [PATCH 14/15] include label details to export and flatten labels

---
 labelbase/downloader.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/labelbase/downloader.py b/labelbase/downloader.py
index 65c8712..eeb12ae 100644
--- a/labelbase/downloader.py
+++ b/labelbase/downloader.py
@@ -5,7 +5,7 @@
 from labelbase.annotate import flatten_label
 
 def export_and_flatten_labels(client:labelboxClient, project, include_metadata:bool=True, include_performance:bool=True, 
-    include_agreement:bool=False, verbose:bool=False, mask_method:str="png", divider="///", export_filters:dict=None):
+    include_agreement:bool=False, include_label_details:bool=False, verbose:bool=False, mask_method:str="png", divider="///", export_filters:dict=None):
     """ Exports and flattens labels from a Labelbox Project
     Args:
         client:                 :   Required (labelbox.Client) - Labelbox Client object
@@ -82,6 +82,7 @@ def export_and_flatten_labels(client:labelboxClient, project, include_metadata:b
                     flat_label["seconds_to_create"] = nested_label['performance_details']['seconds_to_create']
                     flat_label["seconds_to_review"] = nested_label['performance_details']['seconds_to_review']
                     flat_label["seconds_to_label"] = nested_label['performance_details']['seconds_to_create'] - nested_label['performance_details']['seconds_to_review']
+                if include_metadata:
                     for metadata in label['metadata_fields']:
                         try:
                             if metadata['value'] in metadata_schema_to_name_key.keys():
@@ -115,6 +116,10 @@ def export_and_flatten_labels(client:labelboxClient, project, include_metadata:b
                                 metadata_value = metadata['value']
                         if field_name != "lb_integration_source":
                             flat_label[f'metadata{divider}{metadata_type}{divider}{field_name}'] = metadata_value
+                if include_label_details:
+                    flat_label["created_by"] = nested_label['label_details']["created_by"]
+                    flat_label["updated_at"] = nested_label['label_details']["updated_at"]
+                    flat_label["created_at"] = nested_label['label_details']["created_at"]
                 flattened_labels.append(flat_label)
     if verbose:
         print(f"Labels flattened")            

From 3994764bcdae69fd0813b71449ad51b1f0b96db5 Mon Sep 17 00:00:00 2001
From: Luke Moehlenbrock <luksta@Lukes-MacBook-Pro.local>
Date: Wed, 24 Jan 2024 18:59:21 -0700
Subject: [PATCH 15/15] removed debugging print statement

---
 labelbase/annotate.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/labelbase/annotate.py b/labelbase/annotate.py
index 8627974..b624923 100644
--- a/labelbase/annotate.py
+++ b/labelbase/annotate.py
@@ -365,7 +365,6 @@ def flatten_label(client:labelboxClient, label_dict:dict, ontology_index:dict, d
             if column_name not in flat_label.keys():
                 flat_label[column_name] = []
             if "bounding_box" in obj.keys():
-                print(obj)
                 annotation_value = [obj["bounding_box"]["top"], obj["bounding_box"]["left"], obj["bounding_box"]["height"], obj["bounding_box"]["width"]]
                 if "page_number" in obj.keys():
                     annotation_value.append(obj["page_number"])