From 7e8d8da92fa48b284320a141503047990299f831 Mon Sep 17 00:00:00 2001 From: Zineb Aly <zaly@km3net.de> Date: Mon, 3 Feb 2020 15:00:56 +0100 Subject: [PATCH] outsource reconstruction data in separate arrays --- CHANGELOG.rst | 4 +- README.rst | 84 +++++++++++++ examples/pictures/reco.png | Bin 0 -> 15443 bytes km3io/offline.py | 156 ++++++++++++++++++++++++- notebooks/OfflineReader_tutorial.ipynb | 92 +++++++++++---- tests/test_offline.py | 52 +++++++++ 6 files changed, 363 insertions(+), 25 deletions(-) create mode 100644 examples/pictures/reco.png diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5a77c7a..c881c2e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,6 @@ Unreleased changes ------------------ - +* update of reco data from offline files Version 0 --------- @@ -44,7 +44,7 @@ Version 0 0.3.0 / 2019-11-19 ~~~~~~~~~~~~~~~~~~~ * Preliminary Jpp timeslice reader prototype -* Updated ``AaetReader`` +* Updated ``AanetReader`` * Updated docs 0.2.1 / 2019-11-15 diff --git a/README.rst b/README.rst index 1653a02..5c74862 100644 --- a/README.rst +++ b/README.rst @@ -645,6 +645,90 @@ to get a specific value from track 0 in event 0, let's say for example the likli >>>r[0].tracks[0].lik 294.6407542676734 +to get the reconstruction parameters, first take a look at the available reconstruction keys: + +.. code-block:: python3 + + >>>r.best_reco.dtype.names + ['JGANDALF_BETA0_RAD', + 'JGANDALF_BETA1_RAD', + 'JGANDALF_CHI2', + 'JGANDALF_NUMBER_OF_HITS', + 'JENERGY_ENERGY', + 'JENERGY_CHI2', + 'JGANDALF_LAMBDA', + 'JGANDALF_NUMBER_OF_ITERATIONS', + 'JSTART_NPE_MIP', + 'JSTART_NPE_MIP_TOTAL', + 'JSTART_LENGTH_METRES', + 'JVETO_NPE', + 'JVETO_NUMBER_OF_HITS', + 'JENERGY_MUON_RANGE_METRES', + 'JENERGY_NOISE_LIKELIHOOD', + 'JENERGY_NDF', + 'JENERGY_NUMBER_OF_HITS'] + +the keys above can also be accessed with a tab completion: + +.. image:: https://git.km3net.de/km3py/km3io/raw/master/examples/pictures/reco.png + +to get a numpy `recarray <https://docs.scipy.org/doc/numpy/reference/generated/numpy.recarray.html>`__ of all fit data of the best reconstructed track: + +.. code-block:: python3 + + >>>r.best_reco + +to get an array of a parameter of interest, let's say `'JENERGY_ENERGY'`: + +.. code-block:: python3 + + >>>r.best_reco['JENERGY_ENERGY'] + array([1141.87137899, 4708.16378575, 499.7243005 , 103.54680875, + 208.6103912 , 1336.52338666, 998.87632267, 1206.54345674, + 16.28973662]) + +**Note**: In km3io, the best fit is defined as the track fit with the maximum reconstruction stages. When "nan" is returned, it means that the reconstruction parameter of interest is not found. for example, in the case of muon simulations: if `[1, 2]` are the reconstruction stages, then only the fit parameters corresponding to the stages `[1, 2]` are found in the Offline files, the remaining fit parameters corresponding to the stages `[3, 4, 5]` are all filled with nan. + +to get a numpy recarray of the fit data of tracks with specific reconstruction stages, let's say `[1, 2, 3, 4, 5]` in the case of a muon track reconstruction: + +.. code-block:: python3 + + >>>r.get_reco_fit([1, 2, 3, 4, 5]) + +again, to get the reconstruction parameters names: + +.. code-block:: python3 + + >>>r.get_reco_fit([1, 2, 3, 4, 5]).dtype.names + ('JGANDALF_BETA0_RAD', + 'JGANDALF_BETA1_RAD', + 'JGANDALF_CHI2', + 'JGANDALF_NUMBER_OF_HITS', + 'JENERGY_ENERGY', + 'JENERGY_CHI2', + 'JGANDALF_LAMBDA', + 'JGANDALF_NUMBER_OF_ITERATIONS', + 'JSTART_NPE_MIP', + 'JSTART_NPE_MIP_TOTAL', + 'JSTART_LENGTH_METRES', + 'JVETO_NPE', + 'JVETO_NUMBER_OF_HITS', + 'JENERGY_MUON_RANGE_METRES', + 'JENERGY_NOISE_LIKELIHOOD', + 'JENERGY_NDF', + 'JENERGY_NUMBER_OF_HITS') + +to get the reconstruction data of interest, for example ['JENERGY_ENERGY']: + +.. code-block:: python3 + + >>>r.get_reco_fit([1, 2, 3, 4, 5])['JENERGY_ENERGY'] + array([1141.87137899, 4708.16378575, 499.7243005 , 103.54680875, + 208.6103912 , 1336.52338666, 998.87632267, 1206.54345674, + 16.28973662]) + +**Note**: When the reconstruction stages of interest are not found in all your data file, an error is raised. + reading mc hits data """""""""""""""""""" diff --git a/examples/pictures/reco.png b/examples/pictures/reco.png new file mode 100644 index 0000000000000000000000000000000000000000..ce5fa3c6ebef1d541d93ef5a8cf563e024c9d9b7 GIT binary patch literal 15443 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 literal 0 HcmV?d00001 diff --git a/km3io/offline.py b/km3io/offline.py index 8902bc9..335342a 100644 --- a/km3io/offline.py +++ b/km3io/offline.py @@ -1,4 +1,5 @@ import uproot +import numpy as np import warnings # 110 MB based on the size of the largest basket found so far in km3net @@ -179,8 +180,7 @@ class OfflineKeys: 'JSTART_NPE_MIP', 'JSTART_NPE_MIP_TOTAL', 'JSTART_LENGTH_METRES', 'JVETO_NPE', 'JVETO_NUMBER_OF_HITS', 'JENERGY_MUON_RANGE_METRES', 'JENERGY_NOISE_LIKELIHOOD', - 'JENERGY_NDF', 'JENERGY_NUMBER_OF_HITS', 'JCOPY_Z_M' - ] + 'JENERGY_NDF', 'JENERGY_NUMBER_OF_HITS', 'JCOPY_Z_M'] return self._fit_keys @property @@ -320,6 +320,7 @@ class OfflineReader: self._mc_hits = None self._mc_tracks = None self._keys = None + self._best_reco = None self._header = None def __getitem__(self, item): @@ -431,6 +432,157 @@ class OfflineReader: [self._data[key] for key in self.keys.mc_tracks_keys]) return self._mc_tracks + @property + def best_reco(self): + """returns the best reconstructed track fit data. The best fit is defined + as the track fit with the maximum reconstruction stages. When "nan" is + returned, it means that the reconstruction parameter of interest is not + found. for example, in the case of muon simulations: if [1, 2] are the + reconstruction stages, then only the fit parameters corresponding to the + stages [1, 2] are found in the Offline files, the remaining fit parameters + corresponding to the stages 3, 4, 5 are all filled with nan. + + Returns + ------- + numpy recarray + a recarray of the best track fit data (reconstruction data). + """ + if self._best_reco is None: + keys = ", ".join(self.keys.fit_keys[:-1]) + empty_fit_info = np.array([match for match in + self._find_empty(self.tracks.fitinf)]) + fit_info = [i for i,j in zip(self.tracks.fitinf, + empty_fit_info[:,1]) if j is not None] + stages = self._get_max_reco_stages(self.tracks.rec_stages) + fit_data = np.array([i[j] for i,j in zip(fit_info, stages[:,2])]) + rows_size = len(max(fit_data, key=len)) + equal_size_data = np.vstack([np.hstack([i, np.zeros(rows_size-len(i)) + + np.nan]) for i in fit_data]) + self._best_reco = np.core.records.fromarrays(equal_size_data.transpose(), + names=keys) + return self._best_reco + + def _get_max_reco_stages(self, reco_stages): + """find the longest reconstructed track based on the maximum size of + reconstructed stages. + + Parameters + ---------- + reco_stages : chunked array + chunked array of all the reconstruction stages of all tracks. + In km3io, it is accessed with + km3io.OfflineReader(my_file).tracks.rec_stages . + + Returns + ------- + numpy array + array with 3 columns: *list of the maximum reco_stages + *lentgh of the maximum reco_stages + *position of the maximum reco_stages + """ + empty_reco_stages = np.array([match for match in + self._find_empty(reco_stages)]) + max_reco_stages = np.array([[max(i, key=len), len(max(i, key=len)), + i.index(max(i, key=len))] for i,j in + zip(reco_stages, empty_reco_stages[:,1]) + if j is not None]) + return max_reco_stages + + def get_reco_fit(self, stages): + """construct a numpy recarray of the fit information (reconstruction + data) of the tracks reconstructed following the reconstruction stages + of interest. + + Parameters + ---------- + stages : list + list of reconstruction stages of interest. for example + [1, 2, 3, 4, 5]. + + Returns + ------- + numpy recarray + a recarray of the fit information (reconstruction data) of + the tracks of interest. + + Raises + ------ + ValueError + ValueError raised when the reconstruction stages of interest + are not found in the file. + """ + keys = ", ".join(self.keys.fit_keys[:-1]) + fit_info = self.tracks.fitinf + rec_stages = np.array([match for match in + self._find_rec_stages(stages)]) + if np.all(rec_stages[:,1]==None): + raise ValueError("The stages {} are not found in your file." + .format(str(stages))) + else: + fit_data = np.array([i[k] for i,j,k in zip(fit_info, + rec_stages[:,0], rec_stages[:,1]) + if k is not None]) + rec_array = np.core.records.fromarrays(fit_data.transpose(), + names=keys) + return rec_array + + def _find_rec_stages(self, stages): + """find the index of reconstruction stages of interest in a + list of multiple reconstruction stages. + + Parameters + ---------- + stages : list + list of reconstruction stages of interest. for example + [1, 2, 3, 4, 5]. + + Yieldsma + ------ + generator + the track id and the index of the reconstruction stages of + interest if found. If the reconstruction stages of interest + are not found, None is returned as the stages index. + """ + for trk_index, rec_stages in enumerate(self.tracks.rec_stages): + try: + stages_index = rec_stages.index(stages) + except ValueError: + stages_index = None + yield trk_index, stages_index + continue + + yield trk_index, stages_index + + def _find_empty(self, array): + """finds empty lists/arrays in an awkward array + + Parameters + ---------- + array : awkward array + Awkward array of data of interest. For example: + km3io.OfflineReader(my_file).tracks.fitinf . + + Yields + ------ + generator + the empty list id and the index of the empty list. When + data structure (list) is simply empty, None is written in the + corresponding index. However, when data structure (list) is not + empty and does not contain an empty list, then False is written in the + corresponding index. + """ + for i, rs in enumerate(array): + try: + if len(rs)==0: + j = None + if len(rs)!=0: + j = rs.index([]) + except ValueError: + j = False # rs not empty but [] not found + yield i, j + continue + yield i, j + class OfflineEvents: """wrapper for offline events""" diff --git a/notebooks/OfflineReader_tutorial.ipynb b/notebooks/OfflineReader_tutorial.ipynb index c1df4f2..2469cd0 100644 --- a/notebooks/OfflineReader_tutorial.ipynb +++ b/notebooks/OfflineReader_tutorial.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -32,7 +32,7 @@ { "data": { "text/plain": [ - "<km3io.aanet.OfflineReader at 0x7f87bd4ce310>" + "<km3io.offline.OfflineReader at 0x7fb60c2dc590>" ] }, "execution_count": 2, @@ -245,7 +245,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [1 2 3 ... 8 9 10] at 0x7f87906e86d0>" + "<ChunkedArray [1 2 3 ... 8 9 10] at 0x7fb5da350b50>" ] }, "execution_count": 6, @@ -265,7 +265,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [44 44 44 ... 44 44 44] at 0x7f87906e87d0>" + "<ChunkedArray [44 44 44 ... 44 44 44] at 0x7fb5da3506d0>" ] }, "execution_count": 7, @@ -285,7 +285,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [182 183 202 ... 185 185 204] at 0x7f87906e8c10>" + "<ChunkedArray [182 183 202 ... 185 185 204] at 0x7fb5da350950>" ] }, "execution_count": 8, @@ -305,7 +305,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [176 125 318 ... 84 255 105] at 0x7f87906f9210>" + "<ChunkedArray [176 125 318 ... 84 255 105] at 0x7fb5da350350>" ] }, "execution_count": 9, @@ -332,7 +332,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [5.170483995038151 4.8283137373023015 5.762051382780177 ... 4.430816798843313 5.541263545158426 4.653960350157523] at 0x7f879052d4d0>" + "<ChunkedArray [5.170483995038151 4.8283137373023015 5.762051382780177 ... 4.430816798843313 5.541263545158426 4.653960350157523] at 0x7fb60fcdd2d0>" ] }, "execution_count": 10, @@ -449,7 +449,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [[806451572 806451572 806451572 ... 809544061 809544061 809544061] [806451572 806451572 806451572 ... 809524432 809526097 809544061] [806451572 806451572 806451572 ... 809544061 809544061 809544061] ... [806451572 806455814 806465101 ... 809526097 809544058 809544061] [806455814 806455814 806455814 ... 809544061 809544061 809544061] [806455814 806455814 806455814 ... 809544058 809544058 809544061]] at 0x7f87906f9450>" + "<ChunkedArray [[806451572 806451572 806451572 ... 809544061 809544061 809544061] [806451572 806451572 806451572 ... 809524432 809526097 809544061] [806451572 806451572 806451572 ... 809544061 809544061 809544061] ... [806451572 806455814 806465101 ... 809526097 809544058 809544061] [806455814 806455814 806455814 ... 809544061 809544061 809544061] [806455814 806455814 806455814 ... 809544058 809544058 809544061]] at 0x7fb5da350fd0>" ] }, "execution_count": 14, @@ -469,7 +469,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [[24 30 22 ... 38 26 23] [29 26 22 ... 26 28 24] [27 19 13 ... 27 24 16] ... [22 22 9 ... 27 32 27] [30 32 17 ... 30 24 29] [27 41 36 ... 29 24 28]] at 0x7f87906f9810>" + "<ChunkedArray [[24 30 22 ... 38 26 23] [29 26 22 ... 26 28 24] [27 19 13 ... 27 24 16] ... [22 22 9 ... 27 32 27] [30 32 17 ... 30 24 29] [27 41 36 ... 29 24 28]] at 0x7fb5da3a3310>" ] }, "execution_count": 15, @@ -645,7 +645,7 @@ { "data": { "text/plain": [ - "<ChunkedArray [[294.6407542676734 294.6407542676734 294.6407542676734 ... 67.81221253265059 67.7756405143316 67.77250505700384] [96.75133289411137 96.75133289411137 96.75133289411137 ... 39.21916536442286 39.184645826013806 38.870325146341884] [560.2775306614813 560.2775306614813 560.2775306614813 ... 118.88577278801066 118.72271313687405 117.80785995187605] ... [71.03251451148226 71.03251451148226 71.03251451148226 ... 16.714140573909347 16.444395245214945 16.34639241716669] [326.440133294878 326.440133294878 326.440133294878 ... 87.79818671079849 87.75488082571873 87.74839444768625] [159.77779654216795 159.77779654216795 159.77779654216795 ... 33.8669134999348 33.821631538334984 33.77240735670646]] at 0x7f8790683d50>" + "<ChunkedArray [[294.6407542676734 294.6407542676734 294.6407542676734 ... 67.81221253265059 67.7756405143316 67.77250505700384] [96.75133289411137 96.75133289411137 96.75133289411137 ... 39.21916536442286 39.184645826013806 38.870325146341884] [560.2775306614813 560.2775306614813 560.2775306614813 ... 118.88577278801066 118.72271313687405 117.80785995187605] ... [71.03251451148226 71.03251451148226 71.03251451148226 ... 16.714140573909347 16.444395245214945 16.34639241716669] [326.440133294878 326.440133294878 326.440133294878 ... 87.79818671079849 87.75488082571873 87.74839444768625] [159.77779654216795 159.77779654216795 159.77779654216795 ... 33.8669134999348 33.821631538334984 33.77240735670646]] at 0x7fb5da302b50>" ] }, "execution_count": 21, @@ -788,6 +788,63 @@ "r[0].tracks[0].lik" ] }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('JGANDALF_BETA0_RAD',\n", + " 'JGANDALF_BETA1_RAD',\n", + " 'JGANDALF_CHI2',\n", + " 'JGANDALF_NUMBER_OF_HITS',\n", + " 'JENERGY_ENERGY',\n", + " 'JENERGY_CHI2',\n", + " 'JGANDALF_LAMBDA',\n", + " 'JGANDALF_NUMBER_OF_ITERATIONS',\n", + " 'JSTART_NPE_MIP',\n", + " 'JSTART_NPE_MIP_TOTAL',\n", + " 'JSTART_LENGTH_METRES',\n", + " 'JVETO_NPE',\n", + " 'JVETO_NUMBER_OF_HITS',\n", + " 'JENERGY_MUON_RANGE_METRES',\n", + " 'JENERGY_NOISE_LIKELIHOOD',\n", + " 'JENERGY_NDF',\n", + " 'JENERGY_NUMBER_OF_HITS')" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r.tracks.reco.dtype.names" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([99.10458562, 99.10458562, 99.10458562, 37.85515249, 99.10458562,\n", + " 7.16916787, 99.10458562, 99.10458562, 49.13672986, 20.35137468])" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r.tracks.reco[\"JENERGY_ENERGY\"]" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -797,7 +854,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -806,7 +863,7 @@ "<OfflineHits: 10 parsed elements>" ] }, - "execution_count": 26, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -824,7 +881,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -833,7 +890,7 @@ "<OfflineTracks: 10 parsed elements>" ] }, - "execution_count": 27, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -842,13 +899,6 @@ "r.mc_tracks" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, diff --git a/tests/test_offline.py b/tests/test_offline.py index 185008b..fa708e2 100644 --- a/tests/test_offline.py +++ b/tests/test_offline.py @@ -1,4 +1,5 @@ import unittest +import numpy as np from pathlib import Path from km3io.offline import Reader, OfflineEvents, OfflineHits, OfflineTracks @@ -115,6 +116,7 @@ class TestReader(unittest.TestCase): class TestOfflineReader(unittest.TestCase): def setUp(self): self.r = OfflineReader(OFFLINE_FILE) + self.nu = OfflineReader(OFFLINE_NUMUCC) self.Nevents = 10 self.selected_data = OfflineReader(OFFLINE_FILE, data=self.r._data[0])._data @@ -132,6 +134,56 @@ class TestOfflineReader(unittest.TestCase): # check that there are 10 events self.assertEqual(Nevents, self.Nevents) + def test_find_empty(self): + fitinf = self.nu.tracks.fitinf + rec_stages = self.nu.tracks.rec_stages + + empty_fitinf = np.array([match for match in + self.nu._find_empty(fitinf)]) + empty_stages = np.array([match for match in + self.nu._find_empty(rec_stages)]) + + self.assertListEqual(empty_fitinf[:5, 1].tolist(), [23, 14, + 14, 4, None]) + self.assertListEqual(empty_stages[:5, 1].tolist(), [False, False, + False, False, + None]) + + def test_find_rec_stages(self): + stages = np.array([match for match in + self.nu._find_rec_stages([1, 2, 3, 4, 5])]) + + self.assertListEqual(stages[:5, 1].tolist(), [0, 0, 0, 0, None]) + + def test_get_reco_fit(self): + JGANDALF_BETA0_RAD = [0.0020367251782607574, + 0.003306725805622178, + 0.0057877124222254885, + 0.015581698352185896] + reco_fit = self.nu.get_reco_fit([1, 2, 3, 4, 5])['JGANDALF_BETA0_RAD'] + + self.assertListEqual(JGANDALF_BETA0_RAD, reco_fit[:4].tolist()) + with self.assertRaises(ValueError): + self.nu.get_reco_fit([1000, 4512, 5625]) + + def test_get_max_reco_stages(self): + rec_stages = self.nu.tracks.rec_stages + max_reco = self.nu._get_max_reco_stages(rec_stages) + + self.assertEqual(len(max_reco.tolist()), 9) + self.assertListEqual(max_reco[0].tolist(), [[1, 2, 3, 4, 5], + 5, 0]) + + def test_best_reco(self): + JGANDALF_BETA1_RAD = [0.0014177681261476852, + 0.002094094517471032, + 0.003923368624980349, + 0.009491461076780453] + best = self.nu.best_reco + + self.assertEqual(best.size, 9) + self.assertEqual(best['JGANDALF_BETA1_RAD'][:4].tolist(), JGANDALF_BETA1_RAD) + def test_reading_header(self): # head is the supported format head = OfflineReader(OFFLINE_NUMUCC).header -- GitLab