Repository: bids-standard/pybids

Issue:
layout.get doesn't find any entities when passed a numpy.int64 value for run
When doing a `layout.get()`, if the type of the run argument is np.int64, it fails to find any entities. Here's a minimal reproduction using pybids test data:

```
In [1]: from os.path import join 
   ...: from bids.analysis import Analysis 
   ...: from bids.analysis.analysis import ContrastInfo, DesignMatrixInfo 
   ...: from bids.layout import BIDSLayout 
   ...: from bids.tests import get_test_data_path 
   ...: import numpy as np 
   ...:  
   ...: def analysis(): 
   ...:     layout_path = join(get_test_data_path(), 'ds005') 
   ...:     layout = BIDSLayout(layout_path) 
   ...:     json_file = join(layout_path, 'models', 'ds-005_type-test_model.json') 
   ...:     analysis = Analysis(layout, json_file) 
   ...:     analysis.setup(scan_length=480, subject=['01', '02']) 
   ...:     return analysis 
   ...: foo = analysis()                                                                                                                                                          

In [2]: layout_path = join(get_test_data_path(), 'ds005') 
   ...: layout = BIDSLayout(layout_path) 
   ...: layout.get_runs()                                                                                                                                                         
Out[2]: [1, 2, 3]

In [3]: layout.get(suffix="bold",subject='01', run=1)                                                                                                                             
Out[3]: [<BIDSImageFile filename='/gpfs/gsfs11/users/MBDU/midla/notebooks/code/pybids/bids/tests/data/ds005/sub-01/func/sub-01_task-mixedgamblestask_run-01_bold.nii.gz'>]

In [4]: layout.get(suffix="bold",subject='01', run=np.int64(1)) 
   ...:                                                                                                                                                                           
Out[4]: []

In [5]: # Now just to show that pybids itself is generating numpy.int64 run values, here's the output from get design matrix                                                      

In [6]: for _, _, ents in foo.steps[0].get_design_matrix(): 
   ...:     break 
   ...: ents                                                                                                                                                                      
Out[6]: 
{'task': 'mixedgamblestask',
 'run': 1,
 'suffix': 'bold',
 'datatype': 'func',
 'subject': '01'}

In [7]: type(ents['run'])                                                                                                                                                         
Out[7]: numpy.int64

In [8]: layout.get(**ents)                                                                                                                                                        
Out[8]: []

In [9]: ents['run']=1 
   ...: layout.get(**ents)                                                                                                                                                        
Out[9]: [<BIDSImageFile filename='/gpfs/gsfs11/users/MBDU/midla/notebooks/code/pybids/bids/tests/data/ds005/sub-01/func/sub-01_task-mixedgamblestask_run-01_bold.nii.gz'>]
```

And before anyone suggests that the solution is to avoid passing int64s as run numbers, notice that `get_design_matrix` returns an int64. I've reproduced the above results with both 0.9.2 and 0.9.3. 

Comments:

Patch:
diff --git a/bids/layout/layout.py b/bids/layout/layout.py
index 32cf3c03..b00336ea 100644
--- a/bids/layout/layout.py
+++ b/bids/layout/layout.py
@@ -381,6 +381,25 @@ class BIDSLayout(object):
         layouts = [l for l in collect_layouts(self) if l._in_scope(scope)]
         return list(set(layouts))
 
+    def _sanitize_query_dtypes(self, entities):
+        """ Automatically convert entity query values to correct dtypes. """
+        entities = entities.copy()
+        names = list(entities.keys())
+        ents = {e.name: e for e in
+                self.session.query(Entity)
+                    .filter(Entity.name.in_(names)).all()}
+            # Fail silently because the DB may still know how to reconcile
+            # type differences.
+        for name, val in entities.items():
+            try:
+                if isinstance(val, (list, tuple)):
+                    entities[name] = [ents[name]._astype(v) for v in val]
+                else:
+                    entities[name] = ents[name]._astype(val)
+            except:
+                pass
+        return entities
+
     @property
     def entities(self):
         """Get the entities."""
@@ -786,6 +805,9 @@ class BIDSLayout(object):
         if filters:
             query = query.join(BIDSFile.tags)
             regex = kwargs.get('regex_search', False)
+
+            filters = self._sanitize_query_dtypes(filters)
+
             for name, val in filters.items():
                 if isinstance(val, (list, tuple)) and len(val) == 1:
                     val = val[0]


Test patch:
diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py
index b1f5238c..1faa7132 100644
--- a/bids/layout/tests/test_layout.py
+++ b/bids/layout/tests/test_layout.py
@@ -2,16 +2,20 @@
 functionality should go in the grabbit package. """
 
 import os
+import re
+import tempfile
+from os.path import join, abspath, basename, dirname
+
+import numpy as np
 import pytest
+
 import bids
-import re
 from bids.layout import BIDSLayout, parse_file_entities, add_config_paths
 from bids.layout.models import (BIDSFile, BIDSImageFile, Entity, Config,
                                 FileAssociation)
-from os.path import join, abspath, basename, dirname
 from bids.tests import get_test_data_path
 from bids.utils import natural_sort
-import tempfile
+
 
 
 def test_layout_init(layout_7t_trt):
@@ -557,4 +561,13 @@ def test_indexing_tag_conflict():
         layout = BIDSLayout(data_dir)
         print(exc.value.message)
         assert exc.value.message.startswith("Conflicting values found")
-        assert 'run' in exc.value.message
\ No newline at end of file
+        assert 'run' in exc.value.message
+
+
+def test_get_with_wrong_dtypes(layout_7t_trt):
+    ''' Test automatic dtype sanitization. '''
+    l = layout_7t_trt
+    assert (l.get(run=1) == l.get(run='1') == l.get(run=np.int64(1)) ==
+            l.get(run=[1, '15']))
+    assert not l.get(run='not_numeric')
+    assert l.get(session=1) == l.get(session='1')
\ No newline at end of file