Repository: activeloopai/deeplake

Issue:
[BUG] 'float' object has no attribute 'shape' in Data pipelines
## 🐛🐛 Bug Report


### ⚗️ Current Behavior
Receive `float` object has no attribute `shape` inside data pipelines. 

```
Traceback (most recent call last):
  File "examples/upload_mpi.py", line 52, in <module>
    res_ds = out_ds.store(tag)
  File "/Hub/hub/compute/transform.py", line 372, in store
    n_results = self.store_shard(ds_in_shard, ds_out, start, token=token)
  File "/Hub/hub/compute/transform.py", line 288, in store_shard
    self.upload(
  File "/Hub/hub/compute/transform.py", line 222, in upload
    list(self.map(upload_chunk, index_batched_values))
  File "/Hub/hub/compute/transform.py", line 212, in upload_chunk
    ds[key, i * length : i * length + batch_length] = batch
  File "/Hub/hub/api/datasetview.py", line 131, in __setitem__
    self.dataset._tensors[subpath][slice_list] = assign_value
  File "/Hub/hub/store/dynamic_tensor.py", line 187, in __setitem__
    max_shape = value[0].shape
AttributeError: 'float' object has no attribute 'shape'
```

**Input Code**

```python
my_schema = {
    "img_paths": Tensor(max_shape=(1,), dtype="float64"),
}


@hub.transform(schema=my_schema)
def my_transform(annotation):
    return {
        "img_paths": 2.0,
    }


t1 = time.time()
out_ds = my_transform(range(100))
res_ds = out_ds.store(tag)
print(time.time() - t1)
```

**Expected behavior/code**
Data pipelines should be able to process floats without creating a NumPy array.

### ⚙️ Environment

- `Python` version(s): [Clang 11.0.3 (clang-1103.0.32.62)] on darwin
- `OS`: OSX 10.15.7
- `IDE`:  VS-Code

### 🧰 Possible Solution  (optional)
- temporary solution to wrap it in NumPy.array or set shape
- The problem underlies in setting dynamic shapes.

[BUG] Schema errors are not clear
## 🐛🐛 Bug Report

### ⚗️ Current Behavior
When the schema is misused, the error thrown to the user does not explain the issue itself. 

```python
tag = "./MPII_human_pose_1"
img_path = '/content/drive/MyDrive/mpii/mpii_extracted/images/'
jsonfile = '/content/drive/MyDrive/mpii/mpii_extracted/mpii_annotations.json'
my_schema = {
    "image": schema.Image(shape = (None, None, 3), max_shape = (1920, 1920, 3), dtype = 'uint8'),
    "isValidation": Tensor(max_shape = (1,), dtype = 'float64'),
    "img_paths": Tensor(max_shape = (1,), dtype = 'float64'), 
    "img_width":Tensor(max_shape = (1,), dtype = 'float64'),
    "img_height": Tensor(max_shape = (1,), dtype = 'float64'),
    "objpos": Tensor(max_shape = (100,), dtype = 'float64'),
    "joint_self": Tensor(shape = (None, None), max_shape = (100, 100), dtype = 'float64'),
    "scale_provided": Tensor(max_shape = (100,), dtype = 'float64'),
    "annolist_index": Tensor(max_shape = (100,), dtype = 'float64'),
    "people_index": Tensor(max_shape = (100,), dtype = 'float64'),
    "numOtherPeople": Tensor(max_shape = (100,), dtype = 'float64'),
}
ds = Dataset(tag, mode = "w+", schema = my_schema, shape = (100,), storage_cache = None)
with open(jsonfile) as f:
    instances = json.load(f)    
annotations = []
for i in range(100):
    annotations.append(instances[i])
@hub.transform(schema = my_schema)
def my_transform(annotation):
    return{
    "image": np.array(Image.open(img_path + annotation['img_paths'])),
    "isValidation": annotation['isValidation'],
    "img_paths": annotation['img_paths'],
    "img_width": annotation['img_width'],
    "img_height": annotation['img_height'],
    "objpos": np.array(annotation['objpos']),
    "joint_self":  np.array(annotation['joint_self']),
    "scale_provided": annotation['scale_provided'],
    "annolist_index": annotation['annolist_index'],
    "people_index": annotation['people_index'],
    "numOtherPeople": annotation['numOtherPeople'],
    }
t1 = time.time()
out_ds = my_transform(annotations)
res_ds = out_ds.store(tag)
print(time.time() - t1)
```

```
Computing the transormation:   0%|                                                         | 0.00/100 [00:00<?, ? items/s]
Traceback (most recent call last):
  File "examples/upload_mpi.py", line 68, in <module>
    res_ds = out_ds.store(tag)
  File "/Users/davitb/Git/Hub/hub/compute/transform.py", line 372, in store
    n_results = self.store_shard(ds_in_shard, ds_out, start, token=token)
  File "/Users/davitb/Git/Hub/hub/compute/transform.py", line 288, in store_shard
    self.upload(
  File "/Users/davitb/Git/Hub/hub/compute/transform.py", line 222, in upload
    list(self.map(upload_chunk, index_batched_values))
  File "/Users/davitb/Git/Hub/hub/compute/transform.py", line 212, in upload_chunk
    ds[key, i * length : i * length + batch_length] = batch
  File "/Users/davitb/Git/Hub/hub/api/datasetview.py", line 131, in __setitem__
    self.dataset._tensors[subpath][slice_list] = assign_value
  File "/Users/davitb/Git/Hub/hub/store/dynamic_tensor.py", line 187, in __setitem__
    max_shape = value[0].shape
AttributeError: 'float' object has no attribute 'shape'
```

**Expected behavior/code**
It turns out the solution to the problem is to use the following schema for the provided data. 

```python
my_schema = {
    "image": Image(shape=(None, None, 3), max_shape=(1920, 1920, 3), dtype="uint8"),
    "isValidation": "float64",
    "img_paths": Text(max_shape=(15,)),
    "img_width": "int32",
    "img_height": "int32",
    "objpos": Tensor(max_shape=(100,), dtype="float64"),
    "joint_self": Tensor(shape=(None, None), max_shape=(100, 100), dtype="float64"),
    "scale_provided": "float64",
    "annolist_index": "int32",
    "people_index": "int32",
    "numOtherPeople": "int32",
}
```

The exception should provide 
- [ ] the key of the tensor which threw an error.
- [ ] The mismatch of expected value and received value. Only shape error is given, but from the user perspective, it is not clear what should be done.
- [ ] Casting of input e.g. primitive float to single-element array is not done.

### ⚙️ Environment

- `Python` version(s): [Clang 11.0.3 (clang-1103.0.32.62)] on darwin
- `OS`: OSX 10.15.7
- `IDE`:  VS-Code

### 🧰 Possible Solution  (optional)
<!--- Only if you have suggestions on a fix for the bug -->

### 🖼 Additional context/Screenshots (optional)
Add any other context about the problem here. If applicable, add screenshots to help explain.



Discussion:
I think the issue here is that the shape has been set to (1,) which is why it expects a numpy array. If the shape is (), it should probably work. 
Could be but it's strange/not expected, if you try just using primitive type, then it would work. 

We should follow [broadcasting rules](https://numpy.org/doc/stable/user/basics.broadcasting.html#general-broadcasting-rules) as seen in Numpy.  
Agreed. But the weird part is that this shouldn't even be treated as a dynamic tensor, line 187 in dynamic_tensor shouldn't be hit. I think this might even be happening because max_shape is set but the shape isn't. Will need to test it out.
@davidbuniat Why should casting to a single element array be done? if you have 1000 samples, you will get an array with shape (1000,), your way it will be (1000, 1).
I think we should add proper broadcasting to avoid those issues. 

Resolving patch:
diff --git a/hub/store/dynamic_tensor.py b/hub/store/dynamic_tensor.py
--- a/hub/store/dynamic_tensor.py
+++ b/hub/store/dynamic_tensor.py
@@ -234,12 +234,12 @@ def check_value_shape(self, value, slice_):
                 if isinstance(value, list):
                     value = np.array(value)
                 if isinstance(value, np.ndarray):
-                    if value.shape[0] == 1 and expected_value_shape[0] != 1:
-                        value = np.squeeze(value, axis=0)
-                    if value.shape[-1] == 1 and expected_value_shape[-1] != 1:
-                        value = np.squeeze(value, axis=-1)
-                    if value.shape != expected_value_shape:
+                    value_shape = [dim for dim in value.shape if dim != 1]
+                    expected_shape = [dim for dim in expected_value_shape if dim != 1]
+                    if value_shape != expected_shape:
                         raise ValueShapeError(expected_value_shape, value.shape)
+                    else:
+                        value = value.reshape(expected_value_shape)
             else:
                 expected_value_shape = (1,)
                 if isinstance(value, list):


Test patch:
diff --git a/hub/api/tests/test_dataset.py b/hub/api/tests/test_dataset.py
--- a/hub/api/tests/test_dataset.py
+++ b/hub/api/tests/test_dataset.py
@@ -3,7 +3,7 @@
 from hub.exceptions import HubException
 import numpy as np
 import pytest
-
+from hub import transform
 import hub.api.dataset as dataset
 from hub.schema import Tensor, Text, Image
 from hub.utils import (
@@ -533,6 +533,33 @@ def test_datasetview_repr():
     assert dsv.__repr__() == print_text
 
 
+def test_dataset_casting():
+    my_schema = {
+        "a": Tensor(shape=(1,), dtype="float64"),
+    }
+
+    @transform(schema=my_schema)
+    def my_transform(annotation):
+        return {
+            "a": 2.4,
+        }
+
+    out_ds = my_transform(range(100))
+    res_ds = out_ds.store("./data/casting")
+    assert res_ds["a", 30].compute() == np.array([2.4])
+
+    ds = Dataset(schema=my_schema, url="./data/casting2", shape=(100,))
+    for i in range(100):
+        ds["a", i] = 0.2
+    assert ds["a", 30].compute() == np.array([0.2])
+
+    ds2 = Dataset(schema=my_schema, url="./data/casting3", shape=(100,))
+    ds2["a", 0:100] = np.ones(
+        100,
+    )
+    assert ds2["a", 30].compute() == np.array([1])
+
+
 def test_dataset_setting_shape():
     schema = {"text": Text(shape=(None,), dtype="int64", max_shape=(10,))}