Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EnergyOffsetArray #383

Merged
merged 48 commits into from Feb 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
51e2588
implement energy_array_class
Nov 20, 2015
f8e044b
First introdution to documentation
Nov 20, 2015
84de374
ajout de la fonction fillevents qui rempli energy_offset array a aprt…
Dec 10, 2015
e43c167
test
Jan 18, 2016
a268109
ichange from ..obs import Datastore to from ..data
Jan 18, 2016
387aa3e
pass an event_list_table instead of an observation table to the fill_…
Jan 18, 2016
84de738
add the decorator for gammapy-extra
Jan 18, 2016
fde3b4e
defining the testing function
Jan 18, 2016
cd4776f
pass the test tst_energy_offset_array
Jan 19, 2016
5edf576
import requires_data from utils.testting
Jan 19, 2016
f3a8ade
remote from ..data import Datastrore and from ..data import eventlist
Jan 19, 2016
3b6ea1c
fill_events take now a lis of EventList
Jan 19, 2016
7edb715
pass now a list of Object eventList to the fill_events method
Jan 19, 2016
5905a85
add the _fill_one_event_method
Jan 19, 2016
afe6685
save the plot for the test energyoffsetarray in the background/tests …
Jan 20, 2016
a0436b0
remote the comments
Jan 20, 2016
74af34f
remove the _
Jan 20, 2016
f77ad2a
deleted a file
Jan 20, 2016
4d6ae05
delete the table in the function fill_events
Jan 20, 2016
92ccd44
remove the figure
Jan 20, 2016
6c4b677
add the method offset in the eventLis class and cal this method and t…
Jan 20, 2016
557b132
fille_one_event list is again private
Jan 20, 2016
a82b686
separate imshow and colorbar in the plot function
Jan 20, 2016
758309e
in the constructor, energy and offset arenot none and if data is none…
Jan 20, 2016
892e479
reformat with pycharm
Jan 20, 2016
2658eeb
add the docu for the energy offset array
Jan 20, 2016
2a5ef0e
fix the identation pb
Jan 21, 2016
2c80988
add energy_offset_array
Jan 21, 2016
108632c
fix the shinx warning
Jan 21, 2016
4b26f69
better code format and sphinx docu
Jan 21, 2016
e6c1c85
add a test for the offset
Jan 21, 2016
bec4795
new example files for the nergy offset array
Jan 21, 2016
e3fb853
add center.separation for calculating the offset but htat dosn'st giv…
Jan 21, 2016
93d806a
add the file example_energyoffsetarray for the example script
Jan 21, 2016
c88902e
return to detX detY for the offset calculation
Jan 22, 2016
753fded
add the function in datastore that return a list of filetype
Jan 22, 2016
9027d1e
add ebound.equal_log_spacing
Jan 22, 2016
27d6492
modif the test
Jan 22, 2016
1ae0549
change name event_list
Jan 22, 2016
55bd62c
add assert_allclose
Jan 22, 2016
37c50eb
offset with radec + new test value
Jan 25, 2016
0fb5a7d
from .energy_offset_array import *
Jan 25, 2016
becc616
ssertallclose
Feb 1, 2016
13bb8bd
remove the pytest.mark.xfail
Feb 1, 2016
114c27d
rtol=1e-5
Feb 1, 2016
5a90ffa
new variable name
Feb 1, 2016
0eb4005
remove eventlist import
Feb 1, 2016
915b48a
fix the too short issue
Feb 1, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/background/energy_offset_array.rst
@@ -0,0 +1,24 @@
.. _energyoffset_array:

EnergyOffset Array
==================

The `~gammapy.background.EnergyOffsetArray` class represents a 2D array *(energy,offset)* that is filled with an eventlist. For a set of observations, by giving an energy binning and an offset binning, you fill the events in this histogram.


Four Crab observations are located in the ``gammapy-extra`` repository as
examples:
`hess_events_simulated_023523.fits`_ , `hess_events_simulated_023526.fits`_ , `hess_events_simulated_023559.fits`_ and `hess_events_simulated_023592.fits`_

An example script of how to fill this Array from these four observations and plots the result is given in the ``examples`` directory:
:download:`example_energy_offset_array.py <../../examples/example_energy_offset_array.py>`

.. plot:: ../examples/example_energy_offset_array.py
:include-source:



.. _hess_events_simulated_023523.fits: https://github.com/gammapy/gammapy-extra/tree/master/datasets/hess-crab4/hess_events_simulated_023523.fits
.. _hess_events_simulated_023526.fits: https://github.com/gammapy/gammapy-extra/tree/master/datasets/hess-crab4/hess_events_simulated_023526.fits
.. _hess_events_simulated_023559.fits: https://github.com/gammapy/gammapy-extra/tree/master/datasets/hess-crab4/hess_events_simulated_023559.fits
.. _hess_events_simulated_023592.fits: https://github.com/gammapy/gammapy-extra/tree/master/datasets/hess-crab4/hess_events_simulated_023592.fits
3 changes: 2 additions & 1 deletion docs/background/index.rst
Expand Up @@ -39,7 +39,8 @@ If you'd like to learn more about using `gammapy.background`, read the following

models
make_models

energy_offset_array

Reference/API
=============

Expand Down
26 changes: 26 additions & 0 deletions examples/example_energy_offset_array.py
@@ -0,0 +1,26 @@
import numpy as np
import matplotlib.pyplot as plt

from gammapy.data import DataStore
from gammapy.datasets import gammapy_extra
from gammapy.background import EnergyOffsetArray
from gammapy.utils.energy import EnergyBounds


def make_counts_array():
"""Make an example counts array with energy and offset axes."""
data_store = DataStore.from_dir(gammapy_extra.dir / 'datasets/hess-crab4')

event_lists = data_store.load_all('events')
ebounds = EnergyBounds.equal_log_spacing(0.1, 100, 100, 'TeV')
offset = np.linspace(0, 2.5, 100)
array = EnergyOffsetArray(ebounds, offset)
array.fill_events(event_lists)

return array


if __name__ == '__main__':
array = make_counts_array()
array.plot_image()
plt.show()
1 change: 1 addition & 0 deletions gammapy/background/__init__.py
Expand Up @@ -2,6 +2,7 @@
"""
Background estimation and modeling methods.
"""
from .energy_offset_array import *
from .fov import *
from .maps import *
from .cube import *
Expand Down
106 changes: 106 additions & 0 deletions gammapy/background/energy_offset_array.py
@@ -0,0 +1,106 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this boilerplate line:

from __future__ import absolute_import, division, print_function, unicode_literals

This is to make simultaneous Python 2 / 3 codebase easier and we have it in every file.

from __future__ import absolute_import, division, print_function, unicode_literals
import numpy as np
from astropy.coordinates import Angle
from astropy.units import Quantity

__all__ = [
'EnergyOffsetArray',
]


class EnergyOffsetArray(object):
"""
Energy offset dependent array.

Parameters
----------
energy : `~astropy.units.Quantity`
energy bin vector
offset : `~astropy.coordinates.Angle`
offset bin vector
data : `~numpy.ndarray`
data array (2D)

"""

def __init__(self, energy, offset, data=None):
self.energy = Quantity(energy, 'TeV')
self.offset = Angle(offset, 'deg')
if data is None:
self.data = np.zeros((len(energy) - 1, len(offset) - 1))
else:
self.data = data

def fill_events(self, event_lists):
"""Fill events histogram.

This add the counts to the existing value array.

Parameters
-------------
event_lists : list of `~gammapy.data.EventList`
Python list of event list objects.


"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function could just be this, no?

for event_list in events:
    counts = self._fill_one_event_list(event_list)
    self.data += counts

# loop over the Lost of object EventList
for event_list in event_lists:
# Fill the events
counts = self._fill_one_event_list(event_list)
self.data += counts

def _fill_one_event_list(self, events):
"""
histogram the counts of an EventList object in 2D (energy,offset)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I don't understand why it doesn't appear on the documentation since I remote the _ for the methods...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try running:

python setup.py build_sphinx -l

This does a "clean" before running the docs build.

I don't know all of the details about the Sphinx build, but I think it's common that you need to clean to make sure the API docs are re-generated. (you can also run "make clean").
Also try python setup.py build_sphinx -h and make html to see the available options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it useful to expose this method in the public API?
It can't do anything that can't be done via fill_events, so best to have a small API and keep it private?
I.e. add back the underscore.

You can add docstrings for private functions / methods, but it's not as important as for the public methods.


Parameters
-------------
events :`~gammapy.data.EventList`
Event list objects.

"""

offset = events.offset
ev_energy = events.energy

# stack the offset and energy array
ev_cube_array = np.vstack([ev_energy, offset]).T

# fill data cube into histogramdd
ev_cube_hist, ev_cube_edges = np.histogramdd(ev_cube_array,
[self.energy.value,
self.offset.value])
return ev_cube_hist

def plot_image(self, ax=None, offset=None, energy=None, **kwargs):
"""
Plot Energy_offset Array image (x=offset, y=energy).
"""
import matplotlib.pyplot as plt

kwargs.setdefault('cmap', 'afmhot')
kwargs.setdefault('origin', 'bottom')
kwargs.setdefault('interpolation', 'nearest')

ax = plt.gca() if ax is None else ax

if offset is None:
offset = self.offset

if energy is None:
energy = self.energy

extent = [
offset.value.min(), offset.value.max(),
energy.value.min(), energy.value.max(),
]
ax.imshow(self.data, extent=extent, **kwargs)
ax.semilogy()
ax.set_xlabel('Offset ({0})'.format(offset.unit))
ax.set_ylabel('Energy ({0})'.format(energy.unit))
ax.set_title('Energy_offset Array')
ax.legend()
image = ax.imshow(self.data, extent=extent, **kwargs)
plt.colorbar(image)
return ax
21 changes: 21 additions & 0 deletions gammapy/background/tests/test_energy_offset_array.py
@@ -0,0 +1,21 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import absolute_import, division, print_function, unicode_literals
import numpy as np
from ..energy_offset_array import EnergyOffsetArray
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this boilerplate lines at the top of the new test file:

# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import absolute_import, division, print_function, unicode_literals

from ...data import DataStore
from ...datasets import gammapy_extra
from ...utils.testing import requires_dependency, requires_data
from ...utils.energy import EnergyBounds


@requires_dependency('matplotlib')
@requires_data('gammapy-extra')
def test_energy_offset_array():
dir = str(gammapy_extra.dir) + '/datasets/hess-crab4'
data_store = DataStore.from_dir(dir)
ev_list = data_store.load_all('events')
ebounds = EnergyBounds.equal_log_spacing(0.1, 100, 100, 'TeV')
offset = np.linspace(0, 2.5, 100)
array = EnergyOffsetArray(ebounds, offset)
array.fill_events(ev_list)
array.plot_image()
19 changes: 19 additions & 0 deletions gammapy/data/data_store.py
Expand Up @@ -209,6 +209,25 @@ def load(self, obs_id, filetype):
else:
raise ValueError('Invalid filetype.')

def load_all(self, filetype):
"""Load a given file type for all observations

Parameters
----------
filetype : {'events', 'aeff', 'edisp', 'psf', 'background'}
Type of file.

Returns
-------
list : python list of object
Object depends on type, e.g. for `events` it is a list of `~gammapy.data.EventList`.
"""
data_lists = []
for obs_id in self.obs_table['OBS_ID']:
data_list = self.load(obs_id, filetype)
data_lists.append(data_list)
return data_lists

def check_integrity(self, logger):
"""Check integrity, i.e. whether index table and files match.
"""
Expand Down
24 changes: 16 additions & 8 deletions gammapy/data/event_list.py
Expand Up @@ -133,6 +133,14 @@ def altaz(self):
lon, lat = self['AZ'], self['ALT']
return SkyCoord(lon, lat, unit='deg', frame=altaz_frame)

@property
def offset(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding EventList.offset!
Please add a test for this new property at the end of the test_EventList function here:
https://github.com/gammapy/gammapy/blob/master/gammapy/data/tests/test_event_list.py#L12

Compute the offset, and assert on the value for one or two items.
The way I implement such asserts where the reference value isn't known is to put an arbitrary number for the expected value, run the test, get the actual value from the console output of the failed test, and put that in.

This is the best one can do for things where no externally verified expected values are available.
It's of course not perfect to do this, but it's useful to add that assert nonetheless, as a regression test so that we notice when results change on different machines / when refactoring code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better if you replace the implementation of the offset computation with this:
https://github.com/gammapy/gammapy/blob/master/gammapy/data/event_list.py#L265

I.e. use the RADEC pointing and event position and angular separation on the sky.
I prefer this for two reasons:

  • The DETX, DETY field of view coordinates aren't well-defined yet, see here
  • The FOV coordinates are optional, whereas RA / DEC are required and thus always present (see here).

Angular separation on the sky will be slower, but I don't think performance is an issue here.
OK?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so position will be self.radec and the center will be pointing_radec?
Le 21 janv. 2016 à 14:41, Christoph Deil notifications@github.com a écrit :

In gammapy/data/event_list.py:

@@ -132,7 +132,14 @@ def altaz(self):

     lon, lat = self['AZ'], self['ALT']
     return SkyCoord(lon, lat, unit='deg', frame=altaz_frame)

I.e. use the RADEC pointing and event position and angular separation on the sky.
I prefer this for two reasons:

The DETX, DETY field of view coordinates aren't well-defined yet, see here
The FOV coordinates are optional, whereas RA / DEC are required and thus always present (see here).
Angular separation on the sky will be slower, but I don't think performance is an issue here.
OK?


Reply to this email directly or view it on GitHub.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so position will be self.radec and the center will be pointing_radec?

Yes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to understand why DETX, DETY field of view coordinates aren't well-defined yet ? This is the coordinates relative to the pointing direction so exactly what we want? I spoke with bruno and at least is HAP-Fr this is well defined.

If I want to use the radec of the events I need the pointing position for each event not the pointing position of the run given by self.target_radec and I don't think we have this pointing position for each event?

  1. With DetXDetY I got for an event an offset of 1.8996292352676392 deg
  2. With the separation:
    position = self.radec
    center=self.target_radec
    offset=center.separation(position)
    I got 2.3601627349853516 deg .....

Le 21 janv. 2016 à 17:15, Christoph Deil notifications@github.com a écrit :

In gammapy/data/event_list.py:

@@ -132,7 +132,14 @@ def altaz(self):

     lon, lat = self['AZ'], self['ALT']
     return SkyCoord(lon, lat, unit='deg', frame=altaz_frame)

  • @Property
  • def offset(self):
    ok so position will be self.radec and the center will be pointing_radec?

Yes.


Reply to this email directly or view it on GitHub.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The orientation of DETX / DETY isn't well-defined yet (axes aligned with ALT/AZ or RA/DEC).

For HESS the pointing position in RADEC is fixed during the whole run.
The separation of this pointing RADEC wrt. the event RADEC should be the same as the offset computed with DETX / DETY.
If this is not the case, there's either a bug in Gammapy or the event list.
Can you try to find out what's going on here or should I investigate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to find out

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see roughly the same offset (differences at the 0.01 deg level), no matter if it's computed for RA/DEC or DETX/DETY:

def print_offset(filename):
    import numpy as np
    from gammapy.datasets import gammapy_extra
    from gammapy.data import EventList

    filename = gammapy_extra.filename(filename)
    event_list = EventList.read(filename, hdu='EVENTS')
    theta1 = event_list.pointing_radec.separation(event_list.radec).deg
    theta2 = np.sqrt(event_list['DETX'].data ** 2 + event_list['DETY'].data ** 2)
    print(theta1[:5])
    print(theta2[:5])
>>> print_offset('test_datasets/unbundled/hess/run_0023037_hard_eventlist.fits.gz')
[ 1.90449774  0.49800608  1.44632578  1.19342053  1.37034893]
[ 1.89962924  0.49339229  1.45202422  1.19860065  1.37333238]
>>> print_offset('datasets/hess-crab4/hess_events_simulated_023523.fits')
[ 0.54917634  0.50370294  0.55434477  0.52719957  0.62944549]
[ 0.54917902  0.50370306  0.55434608  0.52720046  0.62944645]

I think it's OK if you just put the sky separation for this pull request to finish it up,
no need to trace back the difference you saw.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it was my mistake I used the target_position instead of the pointing_radec...
Le 25 janv. 2016 à 08:58, Christoph Deil notifications@github.com a écrit :

In gammapy/data/event_list.py:

@@ -132,7 +132,14 @@ def altaz(self):

     lon, lat = self['AZ'], self['ALT']
     return SkyCoord(lon, lat, unit='deg', frame=altaz_frame)

  • @Property
  • def offset(self):
    I see roughly the same offset (differences at the 0.01 deg level), no matter if it's computed for RA/DEC or DETX/DETY:

def print_offset(filename):
import numpy as np
from gammapy.datasets import gammapy_extra
from gammapy.data import EventList

filename = gammapy_extra.filename(filename)
event_list = EventList.read(filename, hdu='EVENTS')
theta1 = event_list.pointing_radec.separation(event_list.radec).deg
theta2 = np.sqrt(event_list['DETX'].data ** 2 + event_list['DETY'].data ** 2)
print(theta1[:5])
print(theta2[:5])

print_offset('test_datasets/unbundled/hess/run_0023037_hard_eventlist.fits.gz')
[ 1.90449774 0.49800608 1.44632578 1.19342053 1.37034893]
[ 1.89962924 0.49339229 1.45202422 1.19860065 1.37333238]
print_offset('datasets/hess-crab4/hess_events_simulated_023523.fits')
[ 0.54917634 0.50370294 0.55434477 0.52719957 0.62944549]
[ 0.54917902 0.50370306 0.55434608 0.52720046 0.62944645]
I think it's OK if you just put the sky separation for this pull request to finish it up,
no need to trace back the difference you saw.


Reply to this email directly or view it on GitHub.

"""Event offset (`~astropy.coordinates.Angle`)"""
position = self.radec
center = self.pointing_radec
offset = center.separation(position)
return Angle(offset, unit='deg')

@property
def energy(self):
"""Event energies (`~astropy.units.Quantity`)"""
Expand Down Expand Up @@ -620,14 +628,14 @@ class EventListDatasetChecker(object):
Logger to use (use module-level Gammapy logger by default)
"""
_AVAILABLE_CHECKS = OrderedDict(
misc='check_misc',
times='check_times',
coordinates='check_coordinates',
misc='check_misc',
times='check_times',
coordinates='check_coordinates',
)

accuracy = OrderedDict(
angle=Angle('1 arcsec'),
time=Quantity(1, 'microsecond'),
angle=Angle('1 arcsec'),
time=Quantity(1, 'microsecond'),

)

Expand Down Expand Up @@ -712,9 +720,9 @@ def check_times(self):

# http://fermi.gsfc.nasa.gov/ssc/data/analysis/documentation/Cicerone/Cicerone_Data/Time_in_ScienceTools.html
telescope_met_refs = OrderedDict(
FERMI=Time('2001-01-01T00:00:00'),
HESS=Time('2000-01-01T12:00:00.000'),
# TODO: Once CTA has specified their MET reference add check here
FERMI=Time('2001-01-01T00:00:00'),
HESS=Time('2000-01-01T12:00:00.000'),
# TODO: Once CTA has specified their MET reference add check here
)

telescope = self.dset.event_list.meta['TELESCOP']
Expand Down
12 changes: 11 additions & 1 deletion gammapy/data/tests/test_data_store.py
Expand Up @@ -4,7 +4,8 @@
from ...data import EventList
from ...data import DataStore
from ...utils.testing import requires_data, data_manager

from ...datasets import gammapy_extra
from numpy.testing import assert_allclose

@pytest.mark.xfail
@requires_data('hess')
Expand Down Expand Up @@ -59,6 +60,15 @@ def test_DataStore_load(data_manager):
assert isinstance(events, EventList)


@requires_data('hess')
def test_DataStore_load_all():
"""Test loading data and IRF files via the DataStore"""
dir = str(gammapy_extra.dir) + '/datasets/hess-crab4'
data_store = DataStore.from_dir(dir)
event_lists = data_store.load_all(filetype='events')
assert_allclose(event_lists[0]['ENERGY'][0], 1.1156039)
assert_allclose(event_lists[-1]['ENERGY'][0], 1.0204216)

@pytest.mark.xfail
@requires_data('hess')
def test_DataStore_other(data_manager):
Expand Down
1 change: 1 addition & 0 deletions gammapy/data/tests/test_event_list.py
Expand Up @@ -19,6 +19,7 @@ def test_EventList():
assert event_list.radec[0].to_string() == '82.7068 19.8186'
assert event_list.galactic[0].to_string() == '185.956 -7.69277'
assert event_list.altaz[0].to_string() == '46.2059 31.2001'
assert_allclose(event_list.offset[0].value, 1.904497742652893, rtol=1e-5)
assert '{:1.5f}'.format(event_list.energy[0]) == '11.64355 TeV'

lon, lat, height = event_list.observatory_earth_location.to_geodetic()
Expand Down