{"action":"created","comment":{"url":"https://api.github.com/repos/soft-matter/pims/pulls/comments/33363606","id":33363606,"diff_hunk":"@@ -0,0 +1,269 @@\n+\"\"\"Reader for Norpix .seq files\n+\n+Author: Nathan C. Keim\n+Based heavily on cine.py by Kleckner and Caswell\n+\"\"\"\n+from __future__ import (absolute_import, division, print_function,\n+                        unicode_literals)\n+\n+import six\n+from six.moves import range\n+\n+from pims.frame import Frame\n+from pims.base_frames import FramesSequenceMappable\n+import os, struct, itertools\n+from warnings import warn\n+import datetime\n+import numpy as np\n+from threading import Lock\n+\n+__all__ = ['NorpixSeq',]\n+\n+DWORD = 'L'\n+LONG = 'l'\n+DOUBLE = 'd'\n+\n+HEADER_FIELDS = [\n+    ('magic', DWORD),\n+    ('name', '24s'),\n+    ('version', LONG),\n+    ('header_size', LONG),\n+    ('description', '512s'),\n+    ('width', DWORD),\n+    ('height', DWORD),\n+    ('bit_depth', DWORD),\n+    ('bit_depth_real', DWORD),\n+    ('image_size_bytes', DWORD),\n+    ('image_format', DWORD),\n+    ('allocated_frames', DWORD),\n+    ('origin', DWORD),\n+    ('true_image_size', DWORD),\n+    ('suggested_frame_rate', DOUBLE),\n+    ('description_format', LONG),\n+]\n+\n+\n+class NorpixSeq(FramesSequenceMappable):\n+    \"\"\"Read Norpix sequence (.seq) files\n+\n+    This is the native format of StreamPix software, owned by NorPix Inc.\n+    The format is described in the StreamPix documentation.\n+\n+    Currently supports uncompressed 8-bit monochrome files only.\n+\n+    Nominally thread-safe.\n+\n+    Parameters\n+    ----------\n+    filename : string\n+        Path to the .seq file\n+    process_func : function, optional\n+        callable with signature `proc_img = process_func(img)`,\n+        which will be applied to the data from each frame\n+    dtype : numpy datatype, optional\n+        Image arrays will be converted to this datatype.\n+    as_grey : boolean, optional\n+        Ignored.\n+    \"\"\"\n+    @classmethod\n+    def class_exts(cls):\n+        return {'seq'} | super(NorpixSeq, cls).class_exts()\n+\n+    def __init__(self, filename, process_func=None, dtype=None, as_grey=False):\n+        super(NorpixSeq, self).__init__()\n+        self._file = open(filename, 'rb')\n+        self._filename = filename\n+\n+        self.header_dict = self._read_header(HEADER_FIELDS)\n+\n+        if self.header_dict['magic'] != 0xFEED:\n+            raise IOError('The format of this .seq file is unrecognized')\n+        if self.header_dict['image_format'] != 100:\n+            raise IOError('Only uncompressed mono images are supported in .seq files')\n+\n+        # File-level metadata\n+        if self.header_dict['version'] >= 5:  # StreamPix version 6\n+            self._image_offset = 8192\n+            # Timestamp = 4-byte unsigned long + 2-byte unsigned short (ms)\n+            #   + 2-byte unsigned short (us)\n+            self._timestamp_struct = struct.Struct('<LHH')\n+            self._timestamp_micro = True\n+        else:  # Older versions\n+            self._image_offset = 1024\n+            self._timestamp_struct = struct.Struct('<LH')\n+            self._timestamp_micro = False\n+        self._image_block_size = self.header_dict['true_image_size']\n+        self._filesize = os.stat(self._filename).st_size\n+        self._image_count = int((self._filesize - self._image_offset) /\n+                                self._image_block_size)\n+\n+        # Image metadata\n+        self._width = self.header_dict['width']\n+        self._height = self.header_dict['height']\n+        self._pixel_count = self._width * self._height\n+        self._image_bytes = self.header_dict['image_size_bytes']\n+        self._dtype_native = np.dtype('uint%i' % self.header_dict['bit_depth'])\n+\n+        # Public metadata\n+        self.metadata = {k: self.header_dict[k] for k in\n+                         ('description', 'bit_depth_real', 'origin',\n+                          'suggested_frame_rate', 'width', 'height')}\n+        self.metadata['gamut'] = 2**self.metadata['bit_depth_real'] - 1\n+\n+        # Handle optional parameters\n+        if dtype is None:\n+            self._dtype = self._dtype_native\n+        else:\n+            self._dtype = dtype\n+\n+        self._validate_process_func(process_func)\n+\n+        # TODO How to handle timestamps in a way that survives slicing?\n+        # Do we need to be able to read timestamps independent of their images?\n+        # Plan: return timestamp in the Frame object.\n+        # Also offer a function to retrieve an individual timestamp,\n+        # plus a dump of all timestamps.\n+        #\n+        # One thing that would really help this kind of functionality is\n+        # a user-facing way to look up the raw frame number that is being\n+        # served by a child slice.\n+\n+        self._file_lock = Lock()\n+\n+    def _read_header(self, fields, offset=0):\n+        self._file.seek(offset)\n+        tmp = dict()\n+        for name, format in fields:\n+            val = self._unpack(format)\n+            tmp[name] = val\n+\n+        return tmp\n+\n+    def _unpack(self, fs, offset=None):\n+        if offset is not None:\n+            self._file.seek(offset)\n+        s = struct.Struct('<' + fs)\n+        vals = s.unpack(self._file.read(s.size))\n+        if len(vals) == 1:\n+            return vals[0]\n+        else:\n+            return vals\n+\n+    def _verify_frame_no(self, i):\n+        if int(i) != i:\n+            raise ValueError(\"Frame numbers can only be integers\")\n+        if i >= self._image_count or i < 0:\n+            raise ValueError(\"Frame number is out of range: \" + str(i))\n+\n+    def get_frame(self, i):\n+        self._verify_frame_no(i)\n+        with FileLocker(self._file_lock):\n+            self._file.seek(self._image_offset + self._image_block_size * i)\n+            imdata = np.fromfile(self._file, self._dtype_native, self._pixel_count\n+                                 ).reshape((self.height, self.width))\n+            # Timestamp immediately follows\n+            tfloat, ts = self._read_timestamp()\n+            md = {'time': ts, 'time_float': tfloat,\n+                  'gamut': self.metadata['gamut']}\n+            return Frame(self.process_func(imdata.astype(self._dtype)),\n+                         frame_no=i, metadata=md)\n+\n+    def _read_timestamp(self):\n+        \"\"\"Read a 6-byte timestamp at the current position in the file.\n+\n+        Returns a floating-point representation in seconds, and a datetime instance.\n+        \"\"\"\n+        if self._timestamp_micro:\n+            tsecs, tms, tus = self._timestamp_struct.unpack(self._file.read(8))\n+            tfloat = tsecs + float(tms) / 1000. + float(tus) / 1.0e6\n+        else:\n+            tsecs, tms = self._timestamp_struct.unpack(self._file.read(6))\n+            tfloat = tsecs + float(tms) / 1000.\n+        return tfloat, datetime.datetime.fromtimestamp(tfloat)\n+\n+    def _get_time(self, i):\n+        \"\"\"Call _read_timestamp() for a given frame.\"\"\"\n+        self._verify_frame_no(i)\n+        with FileLocker(self._file_lock):\n+            self._file.seek(self._image_offset + self._image_block_size * i\n+                            + self._image_bytes)\n+            return self._read_timestamp()\n+\n+    def get_time(self, i):\n+        \"\"\"Return the time of frame i as a datetime instance.\n+\n+        Times are stored with millisecond precision.\n+\n+        Calling this function in a different timezone than where the movie\n+        was recorded will result in an offset. The .seq format does not\n+        store UTC or timezone information.\n+        \"\"\"\n+        return self._get_time(self._map_index(i))[1]\n+\n+    def get_time_float(self, i):\n+        \"\"\"Return the time of frame i as a floating-point number of seconds.\"\"\"\n+        return self._get_time(self._map_index(i))[0]\n+\n+    def dump_times_float(self):\n+        \"\"\"Return all frame times in file, as an array of floating-point numbers.\"\"\"\n+        return np.array([self._get_time(i)[0] for i in self._all_indices()])\n+\n+    @property\n+    def filename(self):\n+        return self._filename\n+\n+    @property\n+    def pixel_type(self):\n+        return self._dtype\n+\n+    @property\n+    def width(self):\n+        return self.metadata['width']\n+\n+    @property\n+    def height(self):\n+        return self.metadata['height']\n+\n+    @property\n+    def frame_shape(self):\n+        return (self.metadata['height'], self.metadata['width'])\n+\n+    @property\n+    def frame_rate(self):\n+        return self.metadata['suggested_frame_rate']\n+\n+    def __len__(self):\n+        return self._image_count\n+\n+    def close(self):\n+        self._file.close()\n+\n+    def __repr__(self):\n+        return \"\"\"<Frames>\n+Source: {filename}\n+Length: {count} frames\n+Frame Shape: {w}w x {h}h\n+Pixel Datatype: {dtype}\"\"\".format(filename=self.filename,\n+                                  count=len(self),\n+                                  w=self.frame_shape[0],\n+                                  h=self.frame_shape[1],\n+                                  dtype=self.pixel_type)\n+\n+\n+class FileLocker(object):","path":"pims/norpix_reader.py","position":253,"original_position":253,"commit_id":"331dfded97fa7d71a126f5d7af6a92f6b671fdf2","original_commit_id":"331dfded97fa7d71a126f5d7af6a92f6b671fdf2","user":{"login":"tacaswell","id":199813,"avatar_url":"https://avatars.githubusercontent.com/u/199813?v=3","gravatar_id":"","url":"https://api.github.com/users/tacaswell","html_url":"https://github.com/tacaswell","followers_url":"https://api.github.com/users/tacaswell/followers","following_url":"https://api.github.com/users/tacaswell/following{/other_user}","gists_url":"https://api.github.com/users/tacaswell/gists{/gist_id}","starred_url":"https://api.github.com/users/tacaswell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tacaswell/subscriptions","organizations_url":"https://api.github.com/users/tacaswell/orgs","repos_url":"https://api.github.com/users/tacaswell/repos","events_url":"https://api.github.com/users/tacaswell/events{/privacy}","received_events_url":"https://api.github.com/users/tacaswell/received_events","type":"User","site_admin":false},"body":"Shouldn't this be shared with the cine code? (or did I take it out when I ported it over to pims?)","created_at":"2015-06-26T15:01:30Z","updated_at":"2015-06-26T15:01:30Z","html_url":"https://github.com/soft-matter/pims/pull/160#discussion_r33363606","pull_request_url":"https://api.github.com/repos/soft-matter/pims/pulls/160","_links":{"self":{"href":"https://api.github.com/repos/soft-matter/pims/pulls/comments/33363606"},"html":{"href":"https://github.com/soft-matter/pims/pull/160#discussion_r33363606"},"pull_request":{"href":"https://api.github.com/repos/soft-matter/pims/pulls/160"}}},"pull_request":{"url":"https://api.github.com/repos/soft-matter/pims/pulls/160","id":38602520,"html_url":"https://github.com/soft-matter/pims/pull/160","diff_url":"https://github.com/soft-matter/pims/pull/160.diff","patch_url":"https://github.com/soft-matter/pims/pull/160.patch","issue_url":"https://api.github.com/repos/soft-matter/pims/issues/160","number":160,"state":"open","locked":false,"title":"ENH: Slicing-aware reader methods","user":{"login":"nkeim","id":2985325,"avatar_url":"https://avatars.githubusercontent.com/u/2985325?v=3","gravatar_id":"","url":"https://api.github.com/users/nkeim","html_url":"https://github.com/nkeim","followers_url":"https://api.github.com/users/nkeim/followers","following_url":"https://api.github.com/users/nkeim/following{/other_user}","gists_url":"https://api.github.com/users/nkeim/gists{/gist_id}","starred_url":"https://api.github.com/users/nkeim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nkeim/subscriptions","organizations_url":"https://api.github.com/users/nkeim/orgs","repos_url":"https://api.github.com/users/nkeim/repos","events_url":"https://api.github.com/users/nkeim/events{/privacy}","received_events_url":"https://api.github.com/users/nkeim/received_events","type":"User","site_admin":false},"body":"This addresses #159 using the general approach outlined there. With this PR, methods like `get_time(frameno)` behave the same way on slices of movies as they do on entire movies. This brings us closer to a world in which `FrameSequence` and `SliceableIterable` are interchangeable.\r\n\r\nNorPix (see #158) and Cine classes have been updated to use this functionality. It would probably be best to avoid merging this PR before #158 has been approved and merged.","created_at":"2015-06-25T22:57:52Z","updated_at":"2015-06-26T15:01:30Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8b179446cbe8319ae6e53517bb6860f4b6f84ff4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/soft-matter/pims/pulls/160/commits","review_comments_url":"https://api.github.com/repos/soft-matter/pims/pulls/160/comments","review_comment_url":"https://api.github.com/repos/soft-matter/pims/pulls/comments{/number}","comments_url":"https://api.github.com/repos/soft-matter/pims/issues/160/comments","statuses_url":"https://api.github.com/repos/soft-matter/pims/statuses/331dfded97fa7d71a126f5d7af6a92f6b671fdf2","head":{"label":"nkeim:sliced_methods","ref":"sliced_methods","sha":"331dfded97fa7d71a126f5d7af6a92f6b671fdf2","user":{"login":"nkeim","id":2985325,"avatar_url":"https://avatars.githubusercontent.com/u/2985325?v=3","gravatar_id":"","url":"https://api.github.com/users/nkeim","html_url":"https://github.com/nkeim","followers_url":"https://api.github.com/users/nkeim/followers","following_url":"https://api.github.com/users/nkeim/following{/other_user}","gists_url":"https://api.github.com/users/nkeim/gists{/gist_id}","starred_url":"https://api.github.com/users/nkeim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nkeim/subscriptions","organizations_url":"https://api.github.com/users/nkeim/orgs","repos_url":"https://api.github.com/users/nkeim/repos","events_url":"https://api.github.com/users/nkeim/events{/privacy}","received_events_url":"https://api.github.com/users/nkeim/received_events","type":"User","site_admin":false},"repo":{"id":36041233,"name":"pims","full_name":"nkeim/pims","owner":{"login":"nkeim","id":2985325,"avatar_url":"https://avatars.githubusercontent.com/u/2985325?v=3","gravatar_id":"","url":"https://api.github.com/users/nkeim","html_url":"https://github.com/nkeim","followers_url":"https://api.github.com/users/nkeim/followers","following_url":"https://api.github.com/users/nkeim/following{/other_user}","gists_url":"https://api.github.com/users/nkeim/gists{/gist_id}","starred_url":"https://api.github.com/users/nkeim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nkeim/subscriptions","organizations_url":"https://api.github.com/users/nkeim/orgs","repos_url":"https://api.github.com/users/nkeim/repos","events_url":"https://api.github.com/users/nkeim/events{/privacy}","received_events_url":"https://api.github.com/users/nkeim/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/nkeim/pims","description":"Python Image Sequence: Load video and sequential images in many formats with a simple, consistent interface.","fork":true,"url":"https://api.github.com/repos/nkeim/pims","forks_url":"https://api.github.com/repos/nkeim/pims/forks","keys_url":"https://api.github.com/repos/nkeim/pims/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nkeim/pims/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nkeim/pims/teams","hooks_url":"https://api.github.com/repos/nkeim/pims/hooks","issue_events_url":"https://api.github.com/repos/nkeim/pims/issues/events{/number}","events_url":"https://api.github.com/repos/nkeim/pims/events","assignees_url":"https://api.github.com/repos/nkeim/pims/assignees{/user}","branches_url":"https://api.github.com/repos/nkeim/pims/branches{/branch}","tags_url":"https://api.github.com/repos/nkeim/pims/tags","blobs_url":"https://api.github.com/repos/nkeim/pims/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nkeim/pims/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nkeim/pims/git/refs{/sha}","trees_url":"https://api.github.com/repos/nkeim/pims/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nkeim/pims/statuses/{sha}","languages_url":"https://api.github.com/repos/nkeim/pims/languages","stargazers_url":"https://api.github.com/repos/nkeim/pims/stargazers","contributors_url":"https://api.github.com/repos/nkeim/pims/contributors","subscribers_url":"https://api.github.com/repos/nkeim/pims/subscribers","subscription_url":"https://api.github.com/repos/nkeim/pims/subscription","commits_url":"https://api.github.com/repos/nkeim/pims/commits{/sha}","git_commits_url":"https://api.github.com/repos/nkeim/pims/git/commits{/sha}","comments_url":"https://api.github.com/repos/nkeim/pims/comments{/number}","issue_comment_url":"https://api.github.com/repos/nkeim/pims/issues/comments{/number}","contents_url":"https://api.github.com/repos/nkeim/pims/contents/{+path}","compare_url":"https://api.github.com/repos/nkeim/pims/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nkeim/pims/merges","archive_url":"https://api.github.com/repos/nkeim/pims/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nkeim/pims/downloads","issues_url":"https://api.github.com/repos/nkeim/pims/issues{/number}","pulls_url":"https://api.github.com/repos/nkeim/pims/pulls{/number}","milestones_url":"https://api.github.com/repos/nkeim/pims/milestones{/number}","notifications_url":"https://api.github.com/repos/nkeim/pims/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nkeim/pims/labels{/name}","releases_url":"https://api.github.com/repos/nkeim/pims/releases{/id}","created_at":"2015-05-21T22:54:24Z","updated_at":"2015-05-21T22:54:25Z","pushed_at":"2015-06-25T22:50:02Z","git_url":"git://github.com/nkeim/pims.git","ssh_url":"git@github.com:nkeim/pims.git","clone_url":"https://github.com/nkeim/pims.git","svn_url":"https://github.com/nkeim/pims","homepage":"","size":5611,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"soft-matter:master","ref":"master","sha":"af887ec55b21ed447f8042f10d239c5fc4abdebe","user":{"login":"soft-matter","id":5857177,"avatar_url":"https://avatars.githubusercontent.com/u/5857177?v=3","gravatar_id":"","url":"https://api.github.com/users/soft-matter","html_url":"https://github.com/soft-matter","followers_url":"https://api.github.com/users/soft-matter/followers","following_url":"https://api.github.com/users/soft-matter/following{/other_user}","gists_url":"https://api.github.com/users/soft-matter/gists{/gist_id}","starred_url":"https://api.github.com/users/soft-matter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soft-matter/subscriptions","organizations_url":"https://api.github.com/users/soft-matter/orgs","repos_url":"https://api.github.com/users/soft-matter/repos","events_url":"https://api.github.com/users/soft-matter/events{/privacy}","received_events_url":"https://api.github.com/users/soft-matter/received_events","type":"Organization","site_admin":false},"repo":{"id":14318232,"name":"pims","full_name":"soft-matter/pims","owner":{"login":"soft-matter","id":5857177,"avatar_url":"https://avatars.githubusercontent.com/u/5857177?v=3","gravatar_id":"","url":"https://api.github.com/users/soft-matter","html_url":"https://github.com/soft-matter","followers_url":"https://api.github.com/users/soft-matter/followers","following_url":"https://api.github.com/users/soft-matter/following{/other_user}","gists_url":"https://api.github.com/users/soft-matter/gists{/gist_id}","starred_url":"https://api.github.com/users/soft-matter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soft-matter/subscriptions","organizations_url":"https://api.github.com/users/soft-matter/orgs","repos_url":"https://api.github.com/users/soft-matter/repos","events_url":"https://api.github.com/users/soft-matter/events{/privacy}","received_events_url":"https://api.github.com/users/soft-matter/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/soft-matter/pims","description":"Python Image Sequence: Load video and sequential images in many formats with a simple, consistent interface.","fork":false,"url":"https://api.github.com/repos/soft-matter/pims","forks_url":"https://api.github.com/repos/soft-matter/pims/forks","keys_url":"https://api.github.com/repos/soft-matter/pims/keys{/key_id}","collaborators_url":"https://api.github.com/repos/soft-matter/pims/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/soft-matter/pims/teams","hooks_url":"https://api.github.com/repos/soft-matter/pims/hooks","issue_events_url":"https://api.github.com/repos/soft-matter/pims/issues/events{/number}","events_url":"https://api.github.com/repos/soft-matter/pims/events","assignees_url":"https://api.github.com/repos/soft-matter/pims/assignees{/user}","branches_url":"https://api.github.com/repos/soft-matter/pims/branches{/branch}","tags_url":"https://api.github.com/repos/soft-matter/pims/tags","blobs_url":"https://api.github.com/repos/soft-matter/pims/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/soft-matter/pims/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/soft-matter/pims/git/refs{/sha}","trees_url":"https://api.github.com/repos/soft-matter/pims/git/trees{/sha}","statuses_url":"https://api.github.com/repos/soft-matter/pims/statuses/{sha}","languages_url":"https://api.github.com/repos/soft-matter/pims/languages","stargazers_url":"https://api.github.com/repos/soft-matter/pims/stargazers","contributors_url":"https://api.github.com/repos/soft-matter/pims/contributors","subscribers_url":"https://api.github.com/repos/soft-matter/pims/subscribers","subscription_url":"https://api.github.com/repos/soft-matter/pims/subscription","commits_url":"https://api.github.com/repos/soft-matter/pims/commits{/sha}","git_commits_url":"https://api.github.com/repos/soft-matter/pims/git/commits{/sha}","comments_url":"https://api.github.com/repos/soft-matter/pims/comments{/number}","issue_comment_url":"https://api.github.com/repos/soft-matter/pims/issues/comments{/number}","contents_url":"https://api.github.com/repos/soft-matter/pims/contents/{+path}","compare_url":"https://api.github.com/repos/soft-matter/pims/compare/{base}...{head}","merges_url":"https://api.github.com/repos/soft-matter/pims/merges","archive_url":"https://api.github.com/repos/soft-matter/pims/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/soft-matter/pims/downloads","issues_url":"https://api.github.com/repos/soft-matter/pims/issues{/number}","pulls_url":"https://api.github.com/repos/soft-matter/pims/pulls{/number}","milestones_url":"https://api.github.com/repos/soft-matter/pims/milestones{/number}","notifications_url":"https://api.github.com/repos/soft-matter/pims/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/soft-matter/pims/labels{/name}","releases_url":"https://api.github.com/repos/soft-matter/pims/releases{/id}","created_at":"2013-11-12T00:55:12Z","updated_at":"2015-06-20T10:08:34Z","pushed_at":"2015-06-26T13:44:35Z","git_url":"git://github.com/soft-matter/pims.git","ssh_url":"git@github.com:soft-matter/pims.git","clone_url":"https://github.com/soft-matter/pims.git","svn_url":"https://github.com/soft-matter/pims","homepage":"","size":6521,"stargazers_count":15,"watchers_count":15,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":14,"mirror_url":null,"open_issues_count":21,"forks":14,"open_issues":21,"watchers":15,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/soft-matter/pims/pulls/160"},"html":{"href":"https://github.com/soft-matter/pims/pull/160"},"issue":{"href":"https://api.github.com/repos/soft-matter/pims/issues/160"},"comments":{"href":"https://api.github.com/repos/soft-matter/pims/issues/160/comments"},"review_comments":{"href":"https://api.github.com/repos/soft-matter/pims/pulls/160/comments"},"review_comment":{"href":"https://api.github.com/repos/soft-matter/pims/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/soft-matter/pims/pulls/160/commits"},"statuses":{"href":"https://api.github.com/repos/soft-matter/pims/statuses/331dfded97fa7d71a126f5d7af6a92f6b671fdf2"}}}}