{"action":"created","comment":{"url":"https://api.github.com/repos/nipy/dipy/pulls/comments/25536521","id":25536521,"diff_hunk":"@@ -0,0 +1,372 @@\n+import operator\n+import numpy as np\n+\n+from dipy.segment.metric import Metric\n+from dipy.segment.metric import AveragePointwiseEuclideanMetric\n+\n+\n+class Identity:\n+    def __getitem__(self, idx):\n+        return idx\n+\n+\n+class Cluster(object):\n+    \"\"\" Provides functionalities to interact with a cluster.\n+\n+    Useful container to retrieve index of elements grouped together. If\n+    a reference to the data is provided to `cluster_map`, elements will\n+    be returned instead of their index when possible.\n+\n+    Parameters\n+    ----------\n+    cluster_map : `ClusterMap` object\n+        Reference to the set of clusters this cluster is being part of.\n+    id : int\n+        Id of this cluster in its associated `cluster_map` object.\n+\n+    Notes\n+    -----\n+    A cluster does not contain actual data but instead knows how to\n+    retrieves them using its `ClusterMap` object.\n+    \"\"\"\n+    def __init__(self, id=0, indices=None, refdata=Identity()):\n+        self.id = id\n+        self.refdata = refdata\n+        self.indices = indices if indices is not None else []\n+\n+    def __len__(self):\n+        return len(self.indices)\n+\n+    def __getitem__(self, idx):\n+        if isinstance(idx, int) or isinstance(idx, np.integer):\n+            return self.refdata[self.indices[idx]]\n+        elif type(idx) is slice:\n+            return [self.refdata[i] for i in self.indices[idx]]\n+        elif type(idx) is list:\n+            return [self[i] for i in idx]\n+\n+        raise TypeError(\"Index must be a int or a slice! Not \" + str(type(idx)))\n+\n+    def __iter__(self):\n+        return (self[i] for i in range(len(self)))\n+\n+    def __str__(self):\n+        return \"[\" + \", \".join(map(str, self.indices)) + \"]\"\n+\n+    def __repr__(self):\n+        return \"Cluster(\" + str(self) + \")\"\n+\n+    def __eq__(self, other):\n+        return isinstance(other, Cluster) and self.indices == other.indices\n+\n+    def __ne__(self, other):\n+        return not self == other\n+\n+    def __cmp__(self, other):\n+        raise TypeError(\"Cannot compare Cluster objects.\")\n+\n+    def assign(self, *indices):\n+        \"\"\" Assigns indices to this cluster.\n+\n+        Parameters\n+        ----------\n+        *indices : list of indices\n+            Indices to add to this cluster.\n+        \"\"\"\n+        self.indices += indices\n+\n+\n+class ClusterCentroid(Cluster):\n+    \"\"\" Provides functionalities to interact with a cluster.\n+\n+    Useful container to retrieve index of elements grouped together and\n+    the cluster's centroid. If a reference to the data is provided to\n+    `cluster_map`, elements will be returned instead of their index when\n+    possible.\n+\n+    Parameters\n+    ----------\n+    cluster_map : `ClusterMapCentroid` object\n+        Reference to the set of clusters this cluster is being part of.\n+    id : int\n+        Id of this cluster in its associated `cluster_map` object.\n+\n+    Notes\n+    -----\n+    A cluster does not contain actual data but instead knows how to\n+    retrieves them using its `ClusterMapCentroid` object.\n+    \"\"\"\n+    def __init__(self, centroid, id=0, indices=None, refdata=Identity()):\n+        super(ClusterCentroid, self).__init__(id, indices, refdata)\n+        self.centroid = centroid.copy()\n+        self.new_centroid = centroid.copy()\n+\n+    def __eq__(self, other):\n+        return isinstance(other, ClusterCentroid) \\\n+            and np.all(self.centroid == other.centroid) \\\n+            and super(ClusterCentroid, self).__eq__(other)\n+\n+    def assign(self, id_datum, features):\n+        \"\"\" Assigns a data point to this cluster.\n+\n+        Parameters\n+        ----------\n+        id_datum : int\n+            Index of the data point to add to this cluster.\n+        features : 2D array\n+            Data point's features to modify this cluster's centroid.\n+        \"\"\"\n+        N = len(self)\n+        self.new_centroid = ((self.new_centroid * N) + features) / (N+1.)\n+        super(ClusterCentroid, self).assign(id_datum)\n+\n+    def update(self):\n+        \"\"\" Update centroid of this cluster.\n+\n+        Returns\n+        -------\n+        converged : bool\n+            Tells if the centroid has moved.\n+        \"\"\"\n+        converged = np.equal(self.centroid, self.new_centroid)\n+        self.centroid = self.new_centroid.copy()\n+        return converged\n+\n+\n+class ClusterMap(object):\n+    \"\"\" Provides functionalities to interact with clustering outputs.\n+\n+    Useful container to create, remove, retrieve and filter clusters.\n+    If `refdata` is given, elements will be returned instead of their\n+    index when using `Cluster` objects.\n+\n+    Parameters\n+    ----------\n+    refdata : list\n+        Actual elements that clustered indices refer to.\n+    \"\"\"\n+    def __init__(self, refdata=Identity()):\n+        self._clusters = []\n+        self.refdata = refdata\n+\n+    @property\n+    def clusters(self):\n+        return self._clusters\n+\n+    @property\n+    def refdata(self):\n+        return self._refdata\n+\n+    @refdata.setter\n+    def refdata(self, value):\n+        if value is None:\n+            value = Identity()\n+\n+        self._refdata = value\n+        for cluster in self.clusters:\n+            cluster.refdata = self._refdata\n+\n+    def __len__(self):\n+        return len(self.clusters)\n+\n+    def __getitem__(self, idx):\n+        if isinstance(idx, np.ndarray) and idx.dtype == np.bool:\n+            return [self.clusters[i] for i, take_it in enumerate(idx) if take_it]\n+        elif type(idx) is list:\n+            return [self.clusters[i] for i in idx]\n+\n+        return self.clusters[idx]\n+\n+    def __iter__(self):\n+        return iter(self.clusters)\n+\n+    def __str__(self):\n+        return \"[\" + \", \".join(map(str, self)) + \"]\"\n+\n+    def __repr__(self):\n+        return \"ClusterMap(\" + str(self) + \")\"\n+\n+    def _richcmp(self, other, op):\n+        \"\"\" Compares a cluster map with another cluster map or an integer.","path":"dipy/segment/clustering.py","position":190,"original_position":190,"commit_id":"caa711a3b9c04b7e53920e899bb54ba0b1659673","original_commit_id":"caa711a3b9c04b7e53920e899bb54ba0b1659673","user":{"login":"Garyfallidis","id":134276,"avatar_url":"https://avatars.githubusercontent.com/u/134276?v=3","gravatar_id":"","url":"https://api.github.com/users/Garyfallidis","html_url":"https://github.com/Garyfallidis","followers_url":"https://api.github.com/users/Garyfallidis/followers","following_url":"https://api.github.com/users/Garyfallidis/following{/other_user}","gists_url":"https://api.github.com/users/Garyfallidis/gists{/gist_id}","starred_url":"https://api.github.com/users/Garyfallidis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Garyfallidis/subscriptions","organizations_url":"https://api.github.com/users/Garyfallidis/orgs","repos_url":"https://api.github.com/users/Garyfallidis/repos","events_url":"https://api.github.com/users/Garyfallidis/events{/privacy}","received_events_url":"https://api.github.com/users/Garyfallidis/received_events","type":"User","site_admin":false},"body":"Compares this cluster map with another ... ","created_at":"2015-02-27T20:30:31Z","updated_at":"2015-02-27T20:30:31Z","html_url":"https://github.com/nipy/dipy/pull/568#discussion_r25536521","pull_request_url":"https://api.github.com/repos/nipy/dipy/pulls/568","_links":{"self":{"href":"https://api.github.com/repos/nipy/dipy/pulls/comments/25536521"},"html":{"href":"https://github.com/nipy/dipy/pull/568#discussion_r25536521"},"pull_request":{"href":"https://api.github.com/repos/nipy/dipy/pulls/568"}}},"pull_request":{"url":"https://api.github.com/repos/nipy/dipy/pulls/568","id":27995944,"html_url":"https://github.com/nipy/dipy/pull/568","diff_url":"https://github.com/nipy/dipy/pull/568.diff","patch_url":"https://github.com/nipy/dipy/pull/568.patch","issue_url":"https://api.github.com/repos/nipy/dipy/issues/568","number":568,"state":"open","locked":false,"title":"Quickbundles2","user":{"login":"MarcCote","id":660004,"avatar_url":"https://avatars.githubusercontent.com/u/660004?v=3","gravatar_id":"","url":"https://api.github.com/users/MarcCote","html_url":"https://github.com/MarcCote","followers_url":"https://api.github.com/users/MarcCote/followers","following_url":"https://api.github.com/users/MarcCote/following{/other_user}","gists_url":"https://api.github.com/users/MarcCote/gists{/gist_id}","starred_url":"https://api.github.com/users/MarcCote/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarcCote/subscriptions","organizations_url":"https://api.github.com/users/MarcCote/orgs","repos_url":"https://api.github.com/users/MarcCote/repos","events_url":"https://api.github.com/users/MarcCote/events{/privacy}","received_events_url":"https://api.github.com/users/MarcCote/received_events","type":"User","site_admin":false},"body":"This PR adds an improved version of Quickbundles to segment streamlines. This new version is faster (you can run `bench_streamlines.py`) and provides a more flexible API than the actual implementation. It is *not* a drop in replacement so the ancient version would have to be deprecated (I don't know how to do it though).\r\n\r\nThe proposed architecture allows someone to define features that would be extracted from the streamlines. Those features will then be used by metrics to compute a certain distance between two streamlines (based on extracted features). Finally, those metrics will be used by clustering algorithms that will output a `ClusterMap` object allowing interactions with the clustering result.\r\n\r\nRight now it contains only the basic features, metrics and algorithms to perform Quickbundles with the MDF metric as presented in the paper [Garyfallidis12].","created_at":"2015-01-25T18:49:00Z","updated_at":"2015-02-27T20:30:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"88f649d9355733748cbb1fe992361e30241c440d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/nipy/dipy/pulls/568/commits","review_comments_url":"https://api.github.com/repos/nipy/dipy/pulls/568/comments","review_comment_url":"https://api.github.com/repos/nipy/dipy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nipy/dipy/issues/568/comments","statuses_url":"https://api.github.com/repos/nipy/dipy/statuses/caa711a3b9c04b7e53920e899bb54ba0b1659673","head":{"label":"MarcCote:quickbundles2","ref":"quickbundles2","sha":"caa711a3b9c04b7e53920e899bb54ba0b1659673","user":{"login":"MarcCote","id":660004,"avatar_url":"https://avatars.githubusercontent.com/u/660004?v=3","gravatar_id":"","url":"https://api.github.com/users/MarcCote","html_url":"https://github.com/MarcCote","followers_url":"https://api.github.com/users/MarcCote/followers","following_url":"https://api.github.com/users/MarcCote/following{/other_user}","gists_url":"https://api.github.com/users/MarcCote/gists{/gist_id}","starred_url":"https://api.github.com/users/MarcCote/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarcCote/subscriptions","organizations_url":"https://api.github.com/users/MarcCote/orgs","repos_url":"https://api.github.com/users/MarcCote/repos","events_url":"https://api.github.com/users/MarcCote/events{/privacy}","received_events_url":"https://api.github.com/users/MarcCote/received_events","type":"User","site_admin":false},"repo":{"id":8189384,"name":"dipy","full_name":"MarcCote/dipy","owner":{"login":"MarcCote","id":660004,"avatar_url":"https://avatars.githubusercontent.com/u/660004?v=3","gravatar_id":"","url":"https://api.github.com/users/MarcCote","html_url":"https://github.com/MarcCote","followers_url":"https://api.github.com/users/MarcCote/followers","following_url":"https://api.github.com/users/MarcCote/following{/other_user}","gists_url":"https://api.github.com/users/MarcCote/gists{/gist_id}","starred_url":"https://api.github.com/users/MarcCote/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarcCote/subscriptions","organizations_url":"https://api.github.com/users/MarcCote/orgs","repos_url":"https://api.github.com/users/MarcCote/repos","events_url":"https://api.github.com/users/MarcCote/events{/privacy}","received_events_url":"https://api.github.com/users/MarcCote/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/MarcCote/dipy","description":"Diffusion MR Imaging in Python","fork":true,"url":"https://api.github.com/repos/MarcCote/dipy","forks_url":"https://api.github.com/repos/MarcCote/dipy/forks","keys_url":"https://api.github.com/repos/MarcCote/dipy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MarcCote/dipy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MarcCote/dipy/teams","hooks_url":"https://api.github.com/repos/MarcCote/dipy/hooks","issue_events_url":"https://api.github.com/repos/MarcCote/dipy/issues/events{/number}","events_url":"https://api.github.com/repos/MarcCote/dipy/events","assignees_url":"https://api.github.com/repos/MarcCote/dipy/assignees{/user}","branches_url":"https://api.github.com/repos/MarcCote/dipy/branches{/branch}","tags_url":"https://api.github.com/repos/MarcCote/dipy/tags","blobs_url":"https://api.github.com/repos/MarcCote/dipy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MarcCote/dipy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MarcCote/dipy/git/refs{/sha}","trees_url":"https://api.github.com/repos/MarcCote/dipy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MarcCote/dipy/statuses/{sha}","languages_url":"https://api.github.com/repos/MarcCote/dipy/languages","stargazers_url":"https://api.github.com/repos/MarcCote/dipy/stargazers","contributors_url":"https://api.github.com/repos/MarcCote/dipy/contributors","subscribers_url":"https://api.github.com/repos/MarcCote/dipy/subscribers","subscription_url":"https://api.github.com/repos/MarcCote/dipy/subscription","commits_url":"https://api.github.com/repos/MarcCote/dipy/commits{/sha}","git_commits_url":"https://api.github.com/repos/MarcCote/dipy/git/commits{/sha}","comments_url":"https://api.github.com/repos/MarcCote/dipy/comments{/number}","issue_comment_url":"https://api.github.com/repos/MarcCote/dipy/issues/comments{/number}","contents_url":"https://api.github.com/repos/MarcCote/dipy/contents/{+path}","compare_url":"https://api.github.com/repos/MarcCote/dipy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MarcCote/dipy/merges","archive_url":"https://api.github.com/repos/MarcCote/dipy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MarcCote/dipy/downloads","issues_url":"https://api.github.com/repos/MarcCote/dipy/issues{/number}","pulls_url":"https://api.github.com/repos/MarcCote/dipy/pulls{/number}","milestones_url":"https://api.github.com/repos/MarcCote/dipy/milestones{/number}","notifications_url":"https://api.github.com/repos/MarcCote/dipy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MarcCote/dipy/labels{/name}","releases_url":"https://api.github.com/repos/MarcCote/dipy/releases{/id}","created_at":"2013-02-13T22:36:58Z","updated_at":"2015-01-21T21:06:11Z","pushed_at":"2015-01-26T00:56:50Z","git_url":"git://github.com/MarcCote/dipy.git","ssh_url":"git@github.com:MarcCote/dipy.git","clone_url":"https://github.com/MarcCote/dipy.git","svn_url":"https://github.com/MarcCote/dipy","homepage":"http://dipy.org","size":15527,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"nipy:master","ref":"master","sha":"150ae87cf82d043a5226ddfa663996b7b0f6a54d","user":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"repo":{"id":505209,"name":"dipy","full_name":"nipy/dipy","owner":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nipy/dipy","description":"Diffusion MR Imaging in Python","fork":false,"url":"https://api.github.com/repos/nipy/dipy","forks_url":"https://api.github.com/repos/nipy/dipy/forks","keys_url":"https://api.github.com/repos/nipy/dipy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nipy/dipy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nipy/dipy/teams","hooks_url":"https://api.github.com/repos/nipy/dipy/hooks","issue_events_url":"https://api.github.com/repos/nipy/dipy/issues/events{/number}","events_url":"https://api.github.com/repos/nipy/dipy/events","assignees_url":"https://api.github.com/repos/nipy/dipy/assignees{/user}","branches_url":"https://api.github.com/repos/nipy/dipy/branches{/branch}","tags_url":"https://api.github.com/repos/nipy/dipy/tags","blobs_url":"https://api.github.com/repos/nipy/dipy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nipy/dipy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nipy/dipy/git/refs{/sha}","trees_url":"https://api.github.com/repos/nipy/dipy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nipy/dipy/statuses/{sha}","languages_url":"https://api.github.com/repos/nipy/dipy/languages","stargazers_url":"https://api.github.com/repos/nipy/dipy/stargazers","contributors_url":"https://api.github.com/repos/nipy/dipy/contributors","subscribers_url":"https://api.github.com/repos/nipy/dipy/subscribers","subscription_url":"https://api.github.com/repos/nipy/dipy/subscription","commits_url":"https://api.github.com/repos/nipy/dipy/commits{/sha}","git_commits_url":"https://api.github.com/repos/nipy/dipy/git/commits{/sha}","comments_url":"https://api.github.com/repos/nipy/dipy/comments{/number}","issue_comment_url":"https://api.github.com/repos/nipy/dipy/issues/comments{/number}","contents_url":"https://api.github.com/repos/nipy/dipy/contents/{+path}","compare_url":"https://api.github.com/repos/nipy/dipy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nipy/dipy/merges","archive_url":"https://api.github.com/repos/nipy/dipy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nipy/dipy/downloads","issues_url":"https://api.github.com/repos/nipy/dipy/issues{/number}","pulls_url":"https://api.github.com/repos/nipy/dipy/pulls{/number}","milestones_url":"https://api.github.com/repos/nipy/dipy/milestones{/number}","notifications_url":"https://api.github.com/repos/nipy/dipy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nipy/dipy/labels{/name}","releases_url":"https://api.github.com/repos/nipy/dipy/releases{/id}","created_at":"2010-02-06T11:43:08Z","updated_at":"2015-02-27T18:43:31Z","pushed_at":"2015-02-27T18:43:31Z","git_url":"git://github.com/nipy/dipy.git","ssh_url":"git@github.com:nipy/dipy.git","clone_url":"https://github.com/nipy/dipy.git","svn_url":"https://github.com/nipy/dipy","homepage":"http://dipy.org","size":19533,"stargazers_count":54,"watchers_count":54,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":74,"mirror_url":null,"open_issues_count":59,"forks":74,"open_issues":59,"watchers":54,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nipy/dipy/pulls/568"},"html":{"href":"https://github.com/nipy/dipy/pull/568"},"issue":{"href":"https://api.github.com/repos/nipy/dipy/issues/568"},"comments":{"href":"https://api.github.com/repos/nipy/dipy/issues/568/comments"},"review_comments":{"href":"https://api.github.com/repos/nipy/dipy/pulls/568/comments"},"review_comment":{"href":"https://api.github.com/repos/nipy/dipy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nipy/dipy/pulls/568/commits"},"statuses":{"href":"https://api.github.com/repos/nipy/dipy/statuses/caa711a3b9c04b7e53920e899bb54ba0b1659673"}}}}