{"action":"created","comment":{"url":"https://api.github.com/repos/astropy/astropy/pulls/comments/35187989","id":35187989,"diff_hunk":"@@ -2315,6 +2410,159 @@ def _custom_model_wrapper(func, fit_deriv=None):\n     return type(model_name, (FittableModel,), members)\n \n \n+def render_model(model, arr=None, coords=None, copy=True):\n+    \"\"\"\n+    Evaluates a model on an input array. Evaluation is limited to\n+    a bounding box if the `Model.bounding_box` attribute is set.\n+\n+    Parameters\n+    ----------\n+    model : `Model`\n+        Model to be evaluated.\n+    arr : `numpy.ndarray`, optional\n+        Array on which the model is evaluated.\n+    coords : array-like, optional\n+        Coordinate arrays mapping to ``array``, such that \n+        ``arr[coords] == arr``.\n+    copy : bool, optional\n+        Default is ``True``. If ``False``, ``arr`` will be modified in place. \n+\n+    Returns\n+    -------\n+    array : `numpy.ndarray`\n+        The model evaluated on the input ``arr`` or a new array from ``coords``.  \n+        If ``arr`` and ``coords`` are both `None`, the returned array is \n+        limited to the `Model.bounding_box` limits of the model. If \n+        `Model.bounding_box` is `None`, ``arr`` or ``coords`` must be passed.\n+\n+    Examples\n+    --------\n+    .. plot::\n+        :include-source:\n+\n+        import numpy as np\n+        import matplotlib.pyplot as plt\n+\n+        from astropy.modeling.models import Gaussian2D\n+        from astropy.modeling import render_model\n+\n+        imshape = (450, 600)\n+        nsrc = 50\n+\n+        np.random.seed(0)\n+        model_params = [dict(x_mean = np.random.rand() * imshape[1],\n+                        y_mean = np.random.rand() * imshape[0],\n+                        amplitude = np.abs(np.random.randn() + 10.),\n+                        x_stddev = np.abs(np.random.rand() + 5),\n+                        y_stddev = np.abs(np.random.rand() * 8 + 5),\n+                        theta = np.random.rand() * np.pi) for i in range(nsrc)]\n+\n+        model_list = [Gaussian2D(**kwargs) for kwargs in model_params]\n+\n+        bb_array = np.zeros(imshape)\n+        full_array = np.zeros(imshape)\n+\n+        for model in model_list:\n+            render_model(model, bb_array, copy=False)\n+            model.bounding_box = None\n+            render_model(model, full_array, copy=False)\n+\n+        max_err = (full_array-bb_array).max()\n+\n+        plt.figure(figsize=(12,8))\n+        plt.imshow(np.sqrt(bb_array), cmap='cubehelix')\n+        plt.colorbar()\n+        plt.title('Bounded Models', fontsize=16)\n+\n+        plt.figure(figsize=(12,8))\n+        plt.imshow(full_array - bb_array, cmap='RdBu', vmin=-max_err, vmax=max_err)\n+        plt.colorbar(format='%.1e')\n+        plt.title('Total Flux Err = %.0e' % \n+                  ((np.sum(full_array) - np.sum(bb_array))/np.sum(full_array)),\n+                  fontsize=16)\n+        plt.show()\n+    \"\"\"\n+\n+    bbox = model.bounding_box\n+\n+    if (coords is None) & (arr is None) & (bbox is None):\n+        raise AssertionError('If no bounding_box is set, coords or arr must be input.')\n+\n+    # for consistent indexing\n+    if model.n_inputs == 1:\n+        if coords is not None:\n+            coords = [coords]\n+        if bbox is not None:\n+            bbox = [bbox]\n+\n+    if arr is not None:\n+        # Check dimensions match model\n+        assert arr.ndim == model.n_inputs\n+        # Copy\n+        if copy:\n+            arr = arr.copy()\n+\n+    if coords is not None:\n+        # Will fail if coords are inconsisent\n+        coords = np.array(coords)\n+\n+        # Check dimensions match model\n+        assert len(coords) == model.n_inputs\n+\n+        # Check dimensions match arr\n+        if arr is not None:\n+            assert coords[0].shape == arr.shape\n+\n+        # If no input arr, make one\n+        else:\n+            arr = np.zeros(coords[0].shape)\n+\n+    if bbox is not None:\n+\n+        # assure position is at center pixel\n+        pd = pos, delta = np.array([(np.mean(bb), np.ceil((bb[1] - bb[0]) / 2))\n+                                    for bb in bbox]).astype(int).T\n+\n+        if coords is not None:\n+\n+            sub_shape = tuple(delta * 2 + 1)\n+            sub_coords = np.array([extract_array(c, sub_shape, pos) for c in coords])\n+\n+        else:\n+\n+            limits = [slice(p - d, p + d + 1, 1) for p, d in pd.T]\n+\n+            sub_coords = np.mgrid[limits]\n+\n+        if model.n_inputs > 1:\n+            sub_coords[[0, 1]] = sub_coords[[1, 0]]\n+\n+        if arr is None:\n+            arr = model(*sub_coords)\n+\n+        else:\n+            try:\n+                arr = add_array(arr, model(*sub_coords), pos)\n+            except ValueError:\n+                raise ValueError('The `bounding_box` is larger than the input arr in one \\","path":"astropy/modeling/core.py","position":328,"original_position":328,"commit_id":"7142e7d9e8e8ac44b58b68022b9f8285209677be","original_commit_id":"7142e7d9e8e8ac44b58b68022b9f8285209677be","user":{"login":"cdeil","id":852409,"avatar_url":"https://avatars.githubusercontent.com/u/852409?v=3","gravatar_id":"","url":"https://api.github.com/users/cdeil","html_url":"https://github.com/cdeil","followers_url":"https://api.github.com/users/cdeil/followers","following_url":"https://api.github.com/users/cdeil/following{/other_user}","gists_url":"https://api.github.com/users/cdeil/gists{/gist_id}","starred_url":"https://api.github.com/users/cdeil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cdeil/subscriptions","organizations_url":"https://api.github.com/users/cdeil/orgs","repos_url":"https://api.github.com/users/cdeil/repos","events_url":"https://api.github.com/users/cdeil/events{/privacy}","received_events_url":"https://api.github.com/users/cdeil/received_events","type":"User","site_admin":false},"body":"Does this work, adding a `\\` within a multi-line string?\r\nIn any case, I think the common pattern is\r\n```python\r\nraise ValueError('my very '\r\n                 'long string')\r\n```\r\n","created_at":"2015-07-22T07:37:09Z","updated_at":"2015-07-22T07:37:09Z","html_url":"https://github.com/astropy/astropy/pull/3909#discussion_r35187989","pull_request_url":"https://api.github.com/repos/astropy/astropy/pulls/3909","_links":{"self":{"href":"https://api.github.com/repos/astropy/astropy/pulls/comments/35187989"},"html":{"href":"https://github.com/astropy/astropy/pull/3909#discussion_r35187989"},"pull_request":{"href":"https://api.github.com/repos/astropy/astropy/pulls/3909"}}},"pull_request":{"url":"https://api.github.com/repos/astropy/astropy/pulls/3909","id":39056593,"html_url":"https://github.com/astropy/astropy/pull/3909","diff_url":"https://github.com/astropy/astropy/pull/3909.diff","patch_url":"https://github.com/astropy/astropy/pull/3909.patch","issue_url":"https://api.github.com/repos/astropy/astropy/issues/3909","number":3909,"state":"open","locked":false,"title":"WIP: Implement `bounding_box` model property and `render_model` function ","user":{"login":"patti","id":4968596,"avatar_url":"https://avatars.githubusercontent.com/u/4968596?v=3","gravatar_id":"","url":"https://api.github.com/users/patti","html_url":"https://github.com/patti","followers_url":"https://api.github.com/users/patti/followers","following_url":"https://api.github.com/users/patti/following{/other_user}","gists_url":"https://api.github.com/users/patti/gists{/gist_id}","starred_url":"https://api.github.com/users/patti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patti/subscriptions","organizations_url":"https://api.github.com/users/patti/orgs","repos_url":"https://api.github.com/users/patti/repos","events_url":"https://api.github.com/users/patti/events{/privacy}","received_events_url":"https://api.github.com/users/patti/received_events","type":"User","site_admin":false},"body":"Implementation of a `bounding_box` property for the `Model` class, to improve efficiency by constraining model evaluation to relevant limits. Currently defaults are defined for `Gaussian2D`, `Ellipse2D`, and `Gaussian1D`. Tests have been written for both 1D and 2D. \r\n\r\nThe `render_model` function and associated `ellipse_extent` function currently live in `modeling.utils.py`. Tests for `render_model` have been written in `test_functional_models.py` for the 1D, 2D, and 3D cases. This can presumably extend to N-dimensions. \r\n\r\nRemaining tasks and open questions are listed here:\r\n\r\n1. Where should  `render_model` and `ellipse_extent` live? There are logical arguments against the `utils` module. I propose moving `render_model` to `core.py` where it can live as a function like `custom_model`. This seems like a good fit and makes it user-accessible. It can later be integrated into `__call__` if we so desire. I would leave `ellipse_extent` in `utils` because it doesn't really seem to fit anywhere else and I'm not too worried about it being user-accessible. \r\n\r\n2. How does the user access the `_bounding_box_default` docstring? I think the simple solution is that this doesn't need to be a private function. The added plus is that the user can then define it for themselves for any model, custom or otherwise.\r\n\r\nDocs\r\n- [x] Clean up and fill out doc-strings. Only minor edits still needed. \r\n- [ ] Write high level docs for render_model. Suggested adding subpage `astropy/docs/modeling/bounding_boxes.rst`. This will demo the large sky-model use-case. \r\n- [x] Change `array` to `numpy.ndarray` \r\n\r\nTests\r\n- [x] Add test for 1D bounding box in `Fittable1DModelTester` class\r\n- [x] Add test functions for `render_model` (edge cases, floating position, various optional inputs) in `test_functional_models.py`.\r\n- [x] Add test for N-dim bounding box.\r\n- [x] Get rid of need for `bbox_parameters` in tests (Re: Is it ok to add `bbox_parameters` to the `test_parameters`? If not, I need to re-work `render_model` to handle negative indices.)\r\n- [x] Test arrays for `render_model` should be different lengths in each dimension. \r\n\r\n`bounding_box` property\r\n- [x] New `Model` class attribute  `_bounding_box = None` by default. If set to `'auto'` the setter checks if `_bounding_box_default` exists, otherwise returns the default `None` with a warning (Re: Need to handle models without defined defaults. Will add a try/except in the setter and set `_bounding_box = None` for these.)\r\n- [x] See above proposed solution Re: How to document defaults so that they are accessible to the user? Currently these are defined in the private function `_bounding_box_default` in each model class that is called by the property if `_bounding_box == 'auto' `\r\n- [ ] Later: add defaults for other well-defined models (`Box1D`, `Box2D`, `Ring2D`, `Disk2D`, etc.)\r\n\r\n`render_model` function\r\n- [x] Clean up try/except and if/else statements\r\n\r\n","created_at":"2015-07-02T07:54:18Z","updated_at":"2015-07-22T07:37:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"733930bc8e7d77c82dfcb613ac20048af98793b6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/astropy/astropy/pulls/3909/commits","review_comments_url":"https://api.github.com/repos/astropy/astropy/pulls/3909/comments","review_comment_url":"https://api.github.com/repos/astropy/astropy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/astropy/astropy/issues/3909/comments","statuses_url":"https://api.github.com/repos/astropy/astropy/statuses/7142e7d9e8e8ac44b58b68022b9f8285209677be","head":{"label":"patti:boundingboxes","ref":"boundingboxes","sha":"7142e7d9e8e8ac44b58b68022b9f8285209677be","user":{"login":"patti","id":4968596,"avatar_url":"https://avatars.githubusercontent.com/u/4968596?v=3","gravatar_id":"","url":"https://api.github.com/users/patti","html_url":"https://github.com/patti","followers_url":"https://api.github.com/users/patti/followers","following_url":"https://api.github.com/users/patti/following{/other_user}","gists_url":"https://api.github.com/users/patti/gists{/gist_id}","starred_url":"https://api.github.com/users/patti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patti/subscriptions","organizations_url":"https://api.github.com/users/patti/orgs","repos_url":"https://api.github.com/users/patti/repos","events_url":"https://api.github.com/users/patti/events{/privacy}","received_events_url":"https://api.github.com/users/patti/received_events","type":"User","site_admin":false},"repo":{"id":32838725,"name":"astropy","full_name":"patti/astropy","owner":{"login":"patti","id":4968596,"avatar_url":"https://avatars.githubusercontent.com/u/4968596?v=3","gravatar_id":"","url":"https://api.github.com/users/patti","html_url":"https://github.com/patti","followers_url":"https://api.github.com/users/patti/followers","following_url":"https://api.github.com/users/patti/following{/other_user}","gists_url":"https://api.github.com/users/patti/gists{/gist_id}","starred_url":"https://api.github.com/users/patti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patti/subscriptions","organizations_url":"https://api.github.com/users/patti/orgs","repos_url":"https://api.github.com/users/patti/repos","events_url":"https://api.github.com/users/patti/events{/privacy}","received_events_url":"https://api.github.com/users/patti/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/patti/astropy","description":"Repository for the Astropy core package","fork":true,"url":"https://api.github.com/repos/patti/astropy","forks_url":"https://api.github.com/repos/patti/astropy/forks","keys_url":"https://api.github.com/repos/patti/astropy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/patti/astropy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/patti/astropy/teams","hooks_url":"https://api.github.com/repos/patti/astropy/hooks","issue_events_url":"https://api.github.com/repos/patti/astropy/issues/events{/number}","events_url":"https://api.github.com/repos/patti/astropy/events","assignees_url":"https://api.github.com/repos/patti/astropy/assignees{/user}","branches_url":"https://api.github.com/repos/patti/astropy/branches{/branch}","tags_url":"https://api.github.com/repos/patti/astropy/tags","blobs_url":"https://api.github.com/repos/patti/astropy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/patti/astropy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/patti/astropy/git/refs{/sha}","trees_url":"https://api.github.com/repos/patti/astropy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/patti/astropy/statuses/{sha}","languages_url":"https://api.github.com/repos/patti/astropy/languages","stargazers_url":"https://api.github.com/repos/patti/astropy/stargazers","contributors_url":"https://api.github.com/repos/patti/astropy/contributors","subscribers_url":"https://api.github.com/repos/patti/astropy/subscribers","subscription_url":"https://api.github.com/repos/patti/astropy/subscription","commits_url":"https://api.github.com/repos/patti/astropy/commits{/sha}","git_commits_url":"https://api.github.com/repos/patti/astropy/git/commits{/sha}","comments_url":"https://api.github.com/repos/patti/astropy/comments{/number}","issue_comment_url":"https://api.github.com/repos/patti/astropy/issues/comments{/number}","contents_url":"https://api.github.com/repos/patti/astropy/contents/{+path}","compare_url":"https://api.github.com/repos/patti/astropy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/patti/astropy/merges","archive_url":"https://api.github.com/repos/patti/astropy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/patti/astropy/downloads","issues_url":"https://api.github.com/repos/patti/astropy/issues{/number}","pulls_url":"https://api.github.com/repos/patti/astropy/pulls{/number}","milestones_url":"https://api.github.com/repos/patti/astropy/milestones{/number}","notifications_url":"https://api.github.com/repos/patti/astropy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/patti/astropy/labels{/name}","releases_url":"https://api.github.com/repos/patti/astropy/releases{/id}","created_at":"2015-03-25T02:49:38Z","updated_at":"2015-03-25T02:49:43Z","pushed_at":"2015-07-22T01:42:24Z","git_url":"git://github.com/patti/astropy.git","ssh_url":"git@github.com:patti/astropy.git","clone_url":"https://github.com/patti/astropy.git","svn_url":"https://github.com/patti/astropy","homepage":"www.astropy.org","size":38010,"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":"astropy:master","ref":"master","sha":"561a6dd5ba0ade701db0b738bf9032c58c387246","user":{"login":"astropy","id":847984,"avatar_url":"https://avatars.githubusercontent.com/u/847984?v=3","gravatar_id":"","url":"https://api.github.com/users/astropy","html_url":"https://github.com/astropy","followers_url":"https://api.github.com/users/astropy/followers","following_url":"https://api.github.com/users/astropy/following{/other_user}","gists_url":"https://api.github.com/users/astropy/gists{/gist_id}","starred_url":"https://api.github.com/users/astropy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/astropy/subscriptions","organizations_url":"https://api.github.com/users/astropy/orgs","repos_url":"https://api.github.com/users/astropy/repos","events_url":"https://api.github.com/users/astropy/events{/privacy}","received_events_url":"https://api.github.com/users/astropy/received_events","type":"Organization","site_admin":false},"repo":{"id":2081289,"name":"astropy","full_name":"astropy/astropy","owner":{"login":"astropy","id":847984,"avatar_url":"https://avatars.githubusercontent.com/u/847984?v=3","gravatar_id":"","url":"https://api.github.com/users/astropy","html_url":"https://github.com/astropy","followers_url":"https://api.github.com/users/astropy/followers","following_url":"https://api.github.com/users/astropy/following{/other_user}","gists_url":"https://api.github.com/users/astropy/gists{/gist_id}","starred_url":"https://api.github.com/users/astropy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/astropy/subscriptions","organizations_url":"https://api.github.com/users/astropy/orgs","repos_url":"https://api.github.com/users/astropy/repos","events_url":"https://api.github.com/users/astropy/events{/privacy}","received_events_url":"https://api.github.com/users/astropy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/astropy/astropy","description":"Repository for the Astropy core package","fork":false,"url":"https://api.github.com/repos/astropy/astropy","forks_url":"https://api.github.com/repos/astropy/astropy/forks","keys_url":"https://api.github.com/repos/astropy/astropy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/astropy/astropy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/astropy/astropy/teams","hooks_url":"https://api.github.com/repos/astropy/astropy/hooks","issue_events_url":"https://api.github.com/repos/astropy/astropy/issues/events{/number}","events_url":"https://api.github.com/repos/astropy/astropy/events","assignees_url":"https://api.github.com/repos/astropy/astropy/assignees{/user}","branches_url":"https://api.github.com/repos/astropy/astropy/branches{/branch}","tags_url":"https://api.github.com/repos/astropy/astropy/tags","blobs_url":"https://api.github.com/repos/astropy/astropy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/astropy/astropy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/astropy/astropy/git/refs{/sha}","trees_url":"https://api.github.com/repos/astropy/astropy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/astropy/astropy/statuses/{sha}","languages_url":"https://api.github.com/repos/astropy/astropy/languages","stargazers_url":"https://api.github.com/repos/astropy/astropy/stargazers","contributors_url":"https://api.github.com/repos/astropy/astropy/contributors","subscribers_url":"https://api.github.com/repos/astropy/astropy/subscribers","subscription_url":"https://api.github.com/repos/astropy/astropy/subscription","commits_url":"https://api.github.com/repos/astropy/astropy/commits{/sha}","git_commits_url":"https://api.github.com/repos/astropy/astropy/git/commits{/sha}","comments_url":"https://api.github.com/repos/astropy/astropy/comments{/number}","issue_comment_url":"https://api.github.com/repos/astropy/astropy/issues/comments{/number}","contents_url":"https://api.github.com/repos/astropy/astropy/contents/{+path}","compare_url":"https://api.github.com/repos/astropy/astropy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/astropy/astropy/merges","archive_url":"https://api.github.com/repos/astropy/astropy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/astropy/astropy/downloads","issues_url":"https://api.github.com/repos/astropy/astropy/issues{/number}","pulls_url":"https://api.github.com/repos/astropy/astropy/pulls{/number}","milestones_url":"https://api.github.com/repos/astropy/astropy/milestones{/number}","notifications_url":"https://api.github.com/repos/astropy/astropy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/astropy/astropy/labels{/name}","releases_url":"https://api.github.com/repos/astropy/astropy/releases{/id}","created_at":"2011-07-21T01:33:49Z","updated_at":"2015-07-22T00:40:46Z","pushed_at":"2015-07-22T04:49:02Z","git_url":"git://github.com/astropy/astropy.git","ssh_url":"git@github.com:astropy/astropy.git","clone_url":"https://github.com/astropy/astropy.git","svn_url":"https://github.com/astropy/astropy","homepage":"www.astropy.org","size":65185,"stargazers_count":615,"watchers_count":615,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":413,"mirror_url":null,"open_issues_count":577,"forks":413,"open_issues":577,"watchers":615,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/astropy/astropy/pulls/3909"},"html":{"href":"https://github.com/astropy/astropy/pull/3909"},"issue":{"href":"https://api.github.com/repos/astropy/astropy/issues/3909"},"comments":{"href":"https://api.github.com/repos/astropy/astropy/issues/3909/comments"},"review_comments":{"href":"https://api.github.com/repos/astropy/astropy/pulls/3909/comments"},"review_comment":{"href":"https://api.github.com/repos/astropy/astropy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/astropy/astropy/pulls/3909/commits"},"statuses":{"href":"https://api.github.com/repos/astropy/astropy/statuses/7142e7d9e8e8ac44b58b68022b9f8285209677be"}}}}