diff --git a/.conda/meta.yaml b/.conda/meta.yaml index 30b0fc158..c1781a3ee 100644 --- a/.conda/meta.yaml +++ b/.conda/meta.yaml @@ -55,7 +55,7 @@ requirements: - conda-forge::seaborn - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 - conda-forge::importlib-metadata ==4.11.4 run: - conda-forge::python ==3.7.12 # Run into _MAX_WINDOWS_WORKERS not found if < @@ -90,7 +90,7 @@ requirements: - conda-forge::tensorflow-hub <0.14.0 # Causes pynwb conflicts on linux GH-1446 - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 - conda-forge::importlib-metadata ==4.11.4 # This no longer works so we have moved it to the build workflow diff --git a/.conda_mac/meta.yaml b/.conda_mac/meta.yaml index 403558f7c..8f773badf 100644 --- a/.conda_mac/meta.yaml +++ b/.conda_mac/meta.yaml @@ -57,7 +57,7 @@ requirements: - conda-forge::seaborn - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 run: - conda-forge::python >=3.9.0, <3.10.0 @@ -89,7 +89,7 @@ requirements: # - conda-forge::tensorflow-hub # pulls in tensorflow cpu from conda-forge - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 # test: # imports: diff --git a/environment.yml b/environment.yml index 06a0633d2..d8f752759 100644 --- a/environment.yml +++ b/environment.yml @@ -40,7 +40,7 @@ dependencies: - conda-forge::tensorflow-hub # Pinned in meta.yml, but no problems here... yet - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 # Packages required by tensorflow to find/use GPUs - conda-forge::cudatoolkit ==11.3.1 diff --git a/environment_mac.yml b/environment_mac.yml index 9ab10a1b8..2026154fa 100644 --- a/environment_mac.yml +++ b/environment_mac.yml @@ -39,6 +39,6 @@ dependencies: # - conda-forge::tensorflow-hub # pulls in tensorflow cpu from conda-forge - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 - pip: - "--editable=.[conda_dev]" \ No newline at end of file diff --git a/environment_no_cuda.yml b/environment_no_cuda.yml index ba2b54a22..721c27fca 100644 --- a/environment_no_cuda.yml +++ b/environment_no_cuda.yml @@ -41,7 +41,7 @@ dependencies: - conda-forge::tensorflow-hub - conda-forge::qudida - conda-forge::albumentations - - conda-forge::ndx-pose + - conda-forge::ndx-pose <0.2.0 - pip: - "--editable=.[conda_dev]" \ No newline at end of file diff --git a/pypi_requirements.txt b/pypi_requirements.txt index ad34e2ad8..775ce584e 100644 --- a/pypi_requirements.txt +++ b/pypi_requirements.txt @@ -37,7 +37,7 @@ tensorflow>=2.6.3,<2.9; platform_machine != 'arm64' # tensorflow ==2.7.4; platform_machine != 'arm64' tensorflow-hub<=0.14.0 albumentations -ndx-pose +ndx-pose<0.2.0 # These dependencies are untested since we do not offer a wheel for apple silicon atm. tensorflow-macos==2.9.2; sys_platform == 'darwin' and platform_machine == 'arm64' tensorflow-metal==0.5.0; sys_platform == 'darwin' and platform_machine == 'arm64' diff --git a/sleap/skeleton.py b/sleap/skeleton.py index f6477cf66..fbd1b909c 100644 --- a/sleap/skeleton.py +++ b/sleap/skeleton.py @@ -18,7 +18,6 @@ import attr import cattr import h5py -import jsonpickle import networkx as nx import numpy as np from networkx.readwrite import json_graph @@ -421,11 +420,30 @@ def encode(cls, data: Dict[str, Any]) -> str: Returns: json_str: The JSON string representation of the data. """ + + # This is required for backwards compatibility with SLEAP <=1.3.4 + sorted_data = cls._recursively_sort_dict(data) + encoder = cls() - encoded_data = encoder._encode(data) + encoded_data = encoder._encode(sorted_data) json_str = json.dumps(encoded_data) return json_str + @staticmethod + def _recursively_sort_dict(dictionary: Dict[str, Any]) -> Dict[str, Any]: + """Recursively sorts the dictionary by keys.""" + sorted_dict = dict(sorted(dictionary.items())) + for key, value in sorted_dict.items(): + if isinstance(value, dict): + sorted_dict[key] = SkeletonEncoder._recursively_sort_dict(value) + elif isinstance(value, list): + for i, item in enumerate(value): + if isinstance(item, dict): + sorted_dict[key][i] = SkeletonEncoder._recursively_sort_dict( + item + ) + return sorted_dict + def _encode(self, obj: Any) -> Any: """Recursively encodes the input object. @@ -1477,7 +1495,7 @@ def to_json(self, node_to_idx: Optional[Dict[Node, int]] = None) -> str: Returns: A string containing the JSON representation of the skeleton. """ - jsonpickle.set_encoder_options("simplejson", sort_keys=True, indent=4) + if node_to_idx is not None: # Map Nodes to int indexed_node_graph = nx.relabel_nodes(G=self._graph, mapping=node_to_idx) diff --git a/tests/test_skeleton.py b/tests/test_skeleton.py index 7c5216316..2320342f6 100644 --- a/tests/test_skeleton.py +++ b/tests/test_skeleton.py @@ -45,6 +45,18 @@ def test_decoded_encoded_Skeleton(skeleton_fixture_name, request): # Encode the graph as a json string to test .encode method encoded_json_str = SkeletonEncoder.encode(graph) + # Assert that the encoded json has keys in sorted order (backwards compatibility) + encoded_dict = json.loads(encoded_json_str) + sorted_keys = sorted(encoded_dict.keys()) + assert list(encoded_dict.keys()) == sorted_keys + for key, value in encoded_dict.items(): + if isinstance(value, dict): + assert list(value.keys()) == sorted(value.keys()) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + assert list(item.keys()) == sorted(item.keys()) + # Get the skeleton from the encoded json string decoded_skeleton = Skeleton.from_json(encoded_json_str)