diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b92d51f..621f2e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ # Version 0.2.0 +- Add a text-mode browser for the IMO [PR#103](https://github.com/litebird/litebird_sim/pull/103) + - Implement the tools to build a Singularity container [PR#96](https://github.com/litebird/litebird_sim/pull/96/) - Implement an interface to the TOAST mapmaker [PR#86](https://github.com/litebird/litebird_sim/pull/86/) diff --git a/docs/source/imo.rst b/docs/source/imo.rst index bfd6440a..aba376a1 100644 --- a/docs/source/imo.rst +++ b/docs/source/imo.rst @@ -177,6 +177,38 @@ encode this information from a string:: The advantage of the latter method is that you can access data files that have not been formally included in a versioned IMO. +Browsing the IMO database +------------------------- + +The LiteBIRD Simulation Framework provides a text-mode program to +navigate the contents of the IMO. You can start it using the following +command: + +.. code-block:: text + + python3 -m litebird_sim.imobrowser + +Here is a short demo of its capabilities: + +.. asciinema:: imobrowser.cast + :preload: 1 + +When Β«openingΒ» a data file, you can copy either the full path of the +data file or its UUID (the hexadecimal string uniquely identifying it) +in the clipboard: this can be handy when you are developing codes that +need to access specific objects. On Ubuntu, clipboard copying only +works if you have ``xclip`` or ``xsel`` installed; on +Ubuntu/Mint/Debian Linux, you can install ``xclip`` with the following +command: + +.. code-block:: text + + sudo apt-get install xclip + +If ``xclip`` is not installed, clipboard functions are automatically +disabled. + + IMO and reports --------------- diff --git a/docs/source/imobrowser.cast b/docs/source/imobrowser.cast new file mode 100644 index 00000000..3c91298d --- /dev/null +++ b/docs/source/imobrowser.cast @@ -0,0 +1,109 @@ +{"version": 2, "width": 76, "height": 27, "timestamp": 1612871705, "env": {"SHELL": "/bin/bash", "TERM": "xterm-kitty"}} +[0.32021, "o", "\r\n\u001b[1;36mlitebird_sim\u001b[0m on \u001b[1;35mξ‚  \u001b[0m\u001b[1;35mimobrowser\u001b[0m \u001b[1;31m[\u001b[0m\u001b[1;31m$\u001b[0m\u001b[1;31m!\u001b[0m\u001b[1;31m?\u001b[0m\u001b[1;31m] \u001b[0mis \u001b[1;38;5;208mπŸ“¦ \u001b[0m\u001b[1;38;5;208mv0.2.0\u001b[0m via \u001b[1;33m🐍 \u001b[0m\u001b[1;33mv3.8.3\u001b[0m\u001b[1;33m (litebird-sim-mu69iLl4-py3.8)\u001b[0m via \u001b[1;32mC \u001b[0m\u001b[1;32mbase\u001b[0m \r\n\u001b[1;32m❯\u001b[0m "] +[1.534286, "o", "p"] +[1.695683, "o", "y"] +[2.051176, "o", "t"] +[2.132007, "o", "h"] +[2.272697, "o", "o"] +[2.548885, "o", "n"] +[3.026173, "o", "3"] +[3.328087, "o", " "] +[3.469472, "o", "-"] +[3.76411, "o", "m"] +[3.874921, "o", " "] +[4.150859, "o", "l"] +[4.282585, "o", "i"] +[4.382277, "o", "t"] +[4.487098, "o", "e"] +[4.600242, "o", "b"] +[4.728245, "o", "i"] +[4.818434, "o", "r"] +[5.002782, "o", "d"] +[5.204386, "o", "_"] +[5.456223, "o", "s"] +[5.54354, "o", "i"] +[5.660464, "o", "m"] +[5.902323, "o", "."] +[6.14752, "o", "i"] +[6.258794, "o", "m"] +[6.429684, "o", "o"] +[6.563826, "o", "b"] +[6.856097, "o", "r"] +[6.987074, "o", "o"] +[7.182196, "o", "w"] +[7.443909, "o", "s"] +[7.598198, "o", "e"] +[7.829718, "o", "r"] +[8.255982, "o", "\r\n"] +[10.491963, "o", "\u001b[?1049h\u001b[1;27r\u001b(B\u001b[m\u001b[4l\u001b[?7h\u001b[?1h"] +[10.49211, "o", "\u001b[39;49m\u001b[?1h\u001b[?25l"] +[10.492253, "o", "\u001b[?1000h"] +[10.492308, "o", "\u001b[39;49m\u001b[37m\u001b[40m\u001b[1;1H \u001b[2;1H \u001b[3;1H \u001b[4;1H \u001b[5;1H \u001b[6;1H \u001b[7;1H \u001b[8;1H \u001b[9;1H \u001b[10;1H \u001b[11;1H \u001b[12;1H \u001b[13;1H"] +[10.492404, "o", " \u001b[14;1H \u001b[15;1H \u001b[16;1H \u001b[17;1H \u001b[18;1H \u001b[19;1H \u001b[20;1H \u001b[21;1H \u001b[22;1H \u001b[23;1H \u001b[24;1H \u001b[25;1H "] +[10.492468, "o", " \u001b[26;1H \u001b[27;1H \u001b[?7l \u001b[?7h\u001b[H"] +[10.495567, "o", "\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[H\u001b[2J"] +[10.501379, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[1;1Hβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m IMO entity tree: / \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m───────────────────────────┐\u001b[2;1Hβ”‚\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47msatellite /satellite \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40mβ”‚\u001b[3;1Hβ”‚ β”‚\u001b[4;1Hβ”‚ β”‚\u001b[5;1Hβ”‚ β”‚\u001b[6;1Hβ”‚ β”‚\u001b[7;1Hβ”‚ β”‚\u001b[8;1Hβ”‚ β”‚\u001b[9;1Hβ”‚ β”‚\u001b[10;1Hβ”‚ "] +[10.501498, "o", " β”‚\u001b[11;1Hβ”‚ β”‚\u001b[12;1Hβ”‚ β”‚\u001b[13;1Hβ”‚ β”‚\u001b[14;1H│──────────────────────────────────────────────────────────────────────────│\u001b[15;1Hβ”‚ β”‚\u001b[16;1Hβ”‚ β”‚\u001b[17;1Hβ”‚ β”‚\u001b[18;1Hβ”‚ β”‚\u001b[19;1Hβ”‚ β”‚\u001b[20;1Hβ”‚ "] +[10.501557, "o", " β”‚\u001b[21;1Hβ”‚ β”‚\u001b[22;1Hβ”‚ β”‚\u001b[23;1Hβ”‚ β”‚\u001b[24;1Hβ”‚ β”‚\u001b[25;1H│──────────────────────────────────────────────────────────────────────────│\u001b[26;1Hβ”‚ < Quit > β”‚\u001b[27;1Hβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"] +[12.851246, "o", "\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m\u001b[1;24H IMO entity \u001b[1;37Hree: /satellite \u001b[30m\u001b[47m\u001b[2;2H.. \u001b[2;26H \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2HLFT\u001b[3;26H/satellite/LFT\u001b[4;2HMFT\u001b[4;26H/satellite/MFT\u001b[5;2HHFT\u001b[5;26H/satellite/HFT\u001b[15;2Hscanning_parameters\u001b[15;26H/satellite/scanning_parameters"] +[14.648307, "o", "\u001b[2;2H.. \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[3;2HLFT /satellite/LFT "] +[14.919933, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2HLFT /satellite/LFT \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[4;2HMFT /satellite/MFT "] +[15.608856, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[4;2HMFT /satellite/MFT \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[15;2Hscanning_parameters /satellite/scanning_parameters "] +[17.088209, "o", "\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[H\u001b[2J"] +[17.15951, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[1;1Hβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m Quantity /satellite/scanning_parameters \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m─────────────────┐\u001b[2;1Hβ”‚\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m5e3bf49f-53f3-4f80-a65c-6191a4820b86 v1.0 \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40mβ”‚\u001b[3;1Hβ”‚87d9a77e-7ea1-409b-af48-363e21efa219 v0.0 β”‚\u001b[4;1Hβ”‚ β”‚\u001b[5;1Hβ”‚ β”‚\u001b[6;1Hβ”‚ β”‚\u001b[7;1Hβ”‚ β”‚\u001b[8;1Hβ”‚ β”‚\u001b[9;1Hβ”‚ β”‚\u001b[10;1Hβ”‚ "] +[17.159587, "o", " β”‚\u001b[11;1Hβ”‚ β”‚\u001b[12;1Hβ”‚ β”‚\u001b[13;1Hβ”‚ β”‚\u001b[14;1Hβ”‚ β”‚\u001b[15;1Hβ”‚ β”‚\u001b[16;1Hβ”‚ β”‚\u001b[17;1Hβ”‚ β”‚\u001b[18;1Hβ”‚ β”‚\u001b[19;1Hβ”‚ β”‚\u001b[20;1Hβ”‚ β”‚\u001b[21;1Hβ”‚ β”‚\u001b[22;1Hβ”‚ "] +[17.159775, "o", " β”‚\u001b[23;1Hβ”‚ β”‚\u001b[24;1Hβ”‚ β”‚\u001b[25;1H│──────────────────────────────────────────────────────────────────────────│\u001b[26;1Hβ”‚ < Copy path > < Copy UUID > < Close > β”‚\u001b[27;1Hβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"] +[18.60926, "o", "\u001b[2;2H5e3bf49f-53f3-4f80-a65c-6191a4820b86 v1.0 \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[3;2H87d9a77e-7ea1-409b-af48-363e21efa219 v0.0 "] +[19.19934, "o", "\u001b[2;2H5e3bf49f-53f3-4f80-a65c-6191a4820b86 v1.0 \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2H87d9a77e-7ea1-409b-af48-363e21efa219 v0.0 "] +[19.645781, "o", "\u001b[2;2H5e3bf49f-53f3-4f80-a65c-6191a4820b86 v1.0 \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;4H< Copy path >"] +[21.722515, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[26;4H< Copy path >\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;22H< Copy UUID >"] +[23.240611, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[26;22H< Copy UUID >\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;60H< Close >"] +[24.889185, "o", "\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[H\u001b[2J"] +[24.956922, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[1;1Hβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m IMO entity tree: /satellite \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m───────────────────────┐\u001b[2;1Hβ”‚\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m.. \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40mβ”‚\u001b[3;1Hβ”‚LFT /satellite/LFT β”‚\u001b[4;1Hβ”‚MFT /satellite/MFT β”‚\u001b[5;1Hβ”‚HFT /satellite/HFT β”‚\u001b[6;1Hβ”‚ β”‚\u001b[7;1Hβ”‚ β”‚\u001b[8;1Hβ”‚ β”‚\u001b[9;1Hβ”‚ β”‚\u001b[10;1Hβ”‚ "] +[24.957168, "o", " β”‚\u001b[11;1Hβ”‚ β”‚\u001b[12;1Hβ”‚ β”‚\u001b[13;1Hβ”‚ β”‚\u001b[14;1H│──────────────────────────────────────────────────────────────────────────│\u001b[15;1Hβ”‚scanning_parameters /satellite/scanning_parameters β”‚\u001b[16;1Hβ”‚ β”‚\u001b[17;1Hβ”‚ β”‚\u001b[18;1Hβ”‚ β”‚\u001b[19;1Hβ”‚ β”‚\u001b[20;1Hβ”‚ "] +[24.957338, "o", " β”‚\u001b[21;1Hβ”‚ β”‚\u001b[22;1Hβ”‚ β”‚\u001b[23;1Hβ”‚ β”‚\u001b[24;1Hβ”‚ β”‚\u001b[25;1H│──────────────────────────────────────────────────────────────────────────│\u001b[26;1Hβ”‚ < Quit > β”‚\u001b[27;1Hβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"] +[25.513084, "o", "\u001b[2;2H.. \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[3;2HLFT /satellite/LFT "] +[26.050663, "o", "\u001b[37m\u001b[40m\u001b[1;22H IMO enti\u001b[1;32Hy tree: /satellite/LFT \u001b[30m\u001b[47m\u001b[2;2H.. \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2HL1-040 /satellite/LFT/L1-040 \u001b[4;2HL2-050\u001b[4;37HL\u001b[4;40H/L2-050\u001b[5;2HL1-060\u001b[5;37HL\u001b[5;40H/L1-060\u001b[6;2HL3-068\u001b[6;26H/satellite/LFT/L3-068\u001b[7;2HL2-068\u001b[7;26H/satellite/LFT/L2-068\u001b[8;2HL4-078\u001b[8;26H/satellite/LFT/L4-078\u001b[9;2HL1-078\u001b[9;26H/satellite/LFT/L1-078\u001b[10;2HL3-089\u001b[10;26H/satellite/LFT/L3-089\u001b[11;2HL2-089\u001b[11;26H/satellite/LFT/L2-089\u001b[12;2HL4-100\u001b[12;26H/satellite/LFT/L4-100\u001b[13;2HL3-119\u001b[13;26H/satellite/LFT/L3-119\u001b[15;2Hinstrument_info \u001b[15;37HLFT/instrument_info"] +[27.640849, "o", "\u001b[2;2H.. \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[3;2HL1-040 /satellite/LFT/L1-040 "] +[28.88907, "o", "\u001b[37m\u001b[40m\u001b[1;19H IMO entity \u001b[1;32Hree: /satellite/LFT/L1-040 \u001b[30m\u001b[47m\u001b[2;2H.. \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2HL00_008_QA_040T /satellite/LFT/L1-040/L00_008_QA_040T \u001b[4;3H00_0\u001b[4;8H8_QA_040B\u001b[4;42H1\u001b[4;45H4\u001b[4;47H/L00_008_QA_040B\u001b[5;3H00_0\u001b[5;8H9_QB_040T\u001b[5;45H4\u001b[5;47H/L00_009_QB_040T\u001b[6;3H00_009_QB_040B\u001b[6;42H1\u001b[6;45H40/L00_009_QB_040B\u001b[7;3H00_010_UA_040T\u001b[7;42H1\u001b[7;45H40/L00_010_UA_040T\u001b[8;3H00_010_UA_040B\u001b[8;42H1\u001b[8;45H40/L00_010_UA_040B\u001b[9;3H00_011_UB_040T\u001b[9;45H40/L00_011_UB_040T\u001b[10;3H00_011_UB_040B\u001b[10;42H1\u001b[10;45H40/L00_011_UB_040B\u001b[11;3H00_012_UA_040T\u001b[11;42H1\u001b[11;45H40/L00_012_UA_040T\u001b[12;3H00_\u001b[12;7H12_UA_040B\u001b[12;42H1\u001b[12;44H04\u001b[12;47H/L00_012_UA_040B\u001b[13;3H00_013_UB_040T\u001b[13;42H1\u001b[13;44H040/L00_013_UB_040T\u001b[15;2Hchannel_info \u001b[15;41HL1-040/channel_info"] +[30.034125, "o", "\u001b[2;2H.. \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[3;2HL00_008_QA_040T /satellite/LFT/L1-040/L00_008_QA_040T "] +[31.409875, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2HL00_008_QA_040T /satellite/LFT/L1-040/L00_008_QA_040T \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[4;2HL00_008_QA_040B /satellite/LFT/L1-040/L00_008_QA_040B "] +[32.262167, "o", "\u001b[3;2HL00_008_QA_040T /satellite/LFT/L1-040/L00_008_QA_040T \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[4;2HL00_008_QA_040B /satellite/LFT/L1-040/L00_008_QA_040B "] +[32.747222, "o", "\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m\u001b[1;11H IMO entity tree: /sat\u001b[1;34Hllite/LFT/L1-040/L00_\u001b[1;56H08_QA_040T \u001b[30m\u001b[47m\u001b[2;2H.. \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[3;2H \u001b[4;2H \u001b[4;26H \u001b[5;2H \u001b[5;26H \u001b[6;2H \u001b[6;26H \u001b[7;2H \u001b[7;26H \u001b[8;2H \u001b[8;26H \u001b[9;2H \u001b[9;26H \u001b[10;2H \u001b[10;26H \u001b[11;2H \u001b[11;26H \u001b[12;2H \u001b[12;26H \u001b[13;2H \u001b[13;26H \u001b[15;2Hdetector_info\u001b[15;48HL00_008\u001b[15;56HQA_040T/detector_inf"] +[34.264707, "o", "\u001b[2;2H.. \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[15;2Hdetector_info /satellite/LFT/L1-040/L00_008_QA_040T/detector_inf"] +[34.745614, "o", "\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[H\u001b[2J"] +[34.817888, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[1;1Hβ”Œβ”€β”€β”€β”€β”€β”€\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m Quantity /satellite/LFT/L1-040/L00_008_QA_040T/detector_info \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m──────┐\u001b[2;1Hβ”‚\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47me1547df6-0ef6-408e-a0a6-9aab14325c53 v0.0 \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40mβ”‚\u001b[3;1Hβ”‚ β”‚\u001b[4;1Hβ”‚ β”‚\u001b[5;1Hβ”‚ β”‚\u001b[6;1Hβ”‚ β”‚\u001b[7;1Hβ”‚ β”‚\u001b[8;1Hβ”‚ β”‚\u001b[9;1Hβ”‚ β”‚\u001b[10;1Hβ”‚ β”‚\u001b[11;1Hβ”‚ "] +[34.818242, "o", " β”‚\u001b[12;1Hβ”‚ β”‚\u001b[13;1Hβ”‚ β”‚\u001b[14;1Hβ”‚ β”‚\u001b[15;1Hβ”‚ β”‚\u001b[16;1Hβ”‚ β”‚\u001b[17;1Hβ”‚ β”‚\u001b[18;1Hβ”‚ β”‚\u001b[19;1Hβ”‚ β”‚\u001b[20;1Hβ”‚ β”‚\u001b[21;1Hβ”‚ β”‚\u001b[22;1Hβ”‚ β”‚\u001b[23;1Hβ”‚ "] +[34.818566, "o", " β”‚\u001b[24;1Hβ”‚ β”‚\u001b[25;1H│──────────────────────────────────────────────────────────────────────────│\u001b[26;1Hβ”‚ < Copy path > < Copy UUID > < Close > β”‚\u001b[27;1Hβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"] +[36.273852, "o", "\u001b[2;2He1547df6-0ef6-408e-a0a6-9aab14325c53 v0.0 \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;4H< Copy path >"] +[38.554747, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[26;4H< Copy path >\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;22H< Copy UUID >"] +[38.766964, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[26;22H< Copy UUID >\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;60H< Close >"] +[40.608043, "o", "\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[H\u001b[2J"] +[40.673907, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[1;1Hβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€\u001b(B\u001b[m\u001b[1m\u001b[37m\u001b[40m IMO entity tree: /satellite/LFT/L1-040/L00_008_QA_040T \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m─────────┐\u001b[2;1Hβ”‚\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m.. \u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40mβ”‚\u001b[3;1Hβ”‚ β”‚\u001b[4;1Hβ”‚ β”‚\u001b[5;1Hβ”‚ β”‚\u001b[6;1Hβ”‚ β”‚\u001b[7;1Hβ”‚ β”‚\u001b[8;1Hβ”‚ β”‚\u001b[9;1Hβ”‚ β”‚\u001b[10;1Hβ”‚ β”‚\u001b[11;1Hβ”‚ "] +[40.674111, "o", " β”‚\u001b[12;1Hβ”‚ β”‚\u001b[13;1Hβ”‚ β”‚\u001b[14;1H│──────────────────────────────────────────────────────────────────────────│\u001b[15;1Hβ”‚detector_info /satellite/LFT/L1-040/L00_008_QA_040T/detector_infβ”‚\u001b[16;1Hβ”‚ β”‚\u001b[17;1Hβ”‚ β”‚\u001b[18;1Hβ”‚ β”‚\u001b[19;1Hβ”‚ β”‚\u001b[20;1Hβ”‚ β”‚\u001b[21;1Hβ”‚ "] +[40.674167, "o", " β”‚\u001b[22;1Hβ”‚ β”‚\u001b[23;1Hβ”‚ β”‚\u001b[24;1Hβ”‚ β”‚\u001b[25;1H│──────────────────────────────────────────────────────────────────────────│\u001b[26;1Hβ”‚ < Quit > β”‚\u001b[27;1Hβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"] +[41.760206, "o", "\u001b[2;2H.. \u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[15;2Hdetector_info /satellite/LFT/L1-040/L00_008_QA_040T/detector_inf"] +[42.193968, "o", "\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m\u001b[40m\u001b[15;2Hdetector_info /satellite/LFT/L1-040/L00_008_QA_040T/detector_inf\u001b(B\u001b[m\u001b[1m\u001b[30m\u001b[47m\u001b[26;61H< Quit >"] +[42.617052, "o", "\u001b[?1l\u001b[?1000l\u001b[39;49m\u001b[27d\u001b[K\u001b[27;1H\u001b[?12l\u001b[?25h\u001b[?1049l\r\u001b[?1l"] +[42.622052, "o", "\u001b[0m"] +[43.129689, "o", "\r\n\u001b[1;36mlitebird_sim\u001b[0m on \u001b[1;35mξ‚  \u001b[0m\u001b[1;35mimobrowser\u001b[0m \u001b[1;31m[\u001b[0m\u001b[1;31m$\u001b[0m\u001b[1;31m!\u001b[0m\u001b[1;31m?\u001b[0m\u001b[1;31m] \u001b[0mis \u001b[1;38;5;208mπŸ“¦ \u001b[0m\u001b[1;38;5;208mv0.2.0\u001b[0m via \u001b[1;33m🐍 \u001b[0m\u001b[1;33mv3.8.3\u001b[0m\u001b[1;33m (litebird-sim-mu69iLl4-py3.8)\u001b[0m via \u001b[1;32mC \u001b[0m\u001b[1;32mbase\u001b[0m took \u001b[1;33m34s\u001b[0m \r\n\u001b[1;32m❯\u001b[0m "] +[44.270898, "o", "p"] +[44.422483, "o", "a"] +[44.603244, "o", "s"] +[44.818376, "o", "t"] +[44.928425, "o", "i"] +[45.029096, "o", "n"] +[45.119422, "o", "g"] +[45.230352, "o", " "] +[45.43627, "o", "t"] +[45.577182, "o", "e"] +[45.748207, "o", "x"] +[46.003056, "o", "t"] +[46.224627, "o", ":"] +[46.369626, "o", " "] +[47.506502, "o", "/releases/v0.0/satellite/LFT/L1-040/L00_008_QA_040T/detector_info/detector_info"] +[51.81306, "o", "\u0007"] +[53.022683, "o", "\r\n"] +[53.119797, "o", "pasting: command not found\r\n"] +[53.142987, "o", "\r\n\u001b[1;36mlitebird_sim\u001b[0m on \u001b[1;35mξ‚  \u001b[0m\u001b[1;35mimobrowser\u001b[0m \u001b[1;31m[\u001b[0m\u001b[1;31m$\u001b[0m\u001b[1;31m!\u001b[0m\u001b[1;31m?\u001b[0m\u001b[1;31m] \u001b[0mis \u001b[1;38;5;208mπŸ“¦ \u001b[0m\u001b[1;38;5;208mv0.2.0\u001b[0m via \u001b[1;33m🐍 \u001b[0m\u001b[1;33mv3.8.3\u001b[0m\u001b[1;33m (litebird-sim-mu69iLl4-py3.8)\u001b[0m via \u001b[1;32mC \u001b[0m\u001b[1;32mbase\u001b[0m \r\n\u001b[1;31m❯\u001b[0m "] +[53.690266, "o", "exit\r\n"] diff --git a/litebird_sim/imobrowser.py b/litebird_sim/imobrowser.py new file mode 100644 index 00000000..0a37ec74 --- /dev/null +++ b/litebird_sim/imobrowser.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8 -*- + +from asciimatics.widgets import ( + Frame, + MultiColumnListBox, + ListBox, + Layout, + Divider, + Text, + Button, + TextBox, + Widget, +) +from asciimatics.scene import Scene +from asciimatics.screen import Screen +from asciimatics.exceptions import ResizeScreenError, NextScene, StopApplication +import asciimatics.widgets + +import pyperclip + +from litebird_sim import Imo + +from collections import defaultdict +import sys + +# On Linux, pyperclip only works if you have installed either "xclip" +# or "xsel" +try: + pyperclip.copy("test") + USE_PYPERCLIP = True +except pyperclip.PyperclipException: + USE_PYPERCLIP = False + +# Implement a custom theme, mostly similar to the default "monochrome" +# theme provided by Asciimatics, but with better contrast for selected +# buttons and list items +asciimatics.widgets.THEMES["custom"] = defaultdict( + lambda: (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK), + { + "invalid": (Screen.COLOUR_BLACK, Screen.A_NORMAL, Screen.COLOUR_RED), + "label": (Screen.COLOUR_WHITE, Screen.A_BOLD, Screen.COLOUR_BLACK), + "title": (Screen.COLOUR_WHITE, Screen.A_BOLD, Screen.COLOUR_BLACK), + "selected_focus_field": ( + Screen.COLOUR_BLACK, + Screen.A_BOLD, + Screen.COLOUR_WHITE, + ), + "focus_edit_text": (Screen.COLOUR_WHITE, Screen.A_BOLD, Screen.COLOUR_BLACK), + "focus_button": (Screen.COLOUR_BLACK, Screen.A_BOLD, Screen.COLOUR_WHITE), + "selected_focus_control": ( + Screen.COLOUR_BLACK, + Screen.A_BOLD, + Screen.COLOUR_WHITE, + ), + "disabled": (Screen.COLOUR_BLACK, Screen.A_BOLD, Screen.COLOUR_BLACK), + }, +) + + +class EntityBrowser(object): + def __init__(self): + self._imo = Imo() + self._base = None + self._quantity = None + + # Check that the IMO points to a local file + assert "imoobject" in dir(self._imo) + + def get_children(self): + "Return the UUIDs and paths of the children of the current node" + + if self._base: + try: + parent = [(("..", ""), self._base.parent.uuid)] + except AttributeError: + parent = [(("..", ""), None)] + else: + parent = [] + + return parent + [ + ((x.name, x.full_path), x.uuid) + for x in self._imo.imoobject.entities.values() + if x.parent == self._base + ] + + def get_quantities(self): + if not self._base: + return [] + + result = [] + for cur_uuid in self._base.quantities: + cur_quantity = self._imo.imoobject.quantities[cur_uuid] + result.append( + ( + ( + cur_quantity.name, + f"{self.get_entity_name()}/{cur_quantity.name}", + ), + cur_quantity.uuid, + ) + ) + + return result + + def get_entity_name(self): + if self._base: + return self._base.full_path + else: + return "/" + + def enter_child(self, uuid): + if uuid: + self._base = self._imo.imoobject.entities[uuid] + else: + self._base = None + + def set_current_quantity(self, uuid): + if uuid: + self._quantity = self._imo.imoobject.quantities[uuid] + else: + self._quantity = None + + def get_quantity_name(self): + try: + return self._quantity.name + except AttributeError: + return "" + + def get_quantity_path(self): + try: + return f"{self.get_entity_name()}/{self._quantity.name}" + except AttributeError: + return "" + + def get_data_files(self): + if not self._quantity: + return [] + + result = [] + for cur_file_uuid in self._quantity.data_files: + cur_file = self._imo.imoobject.data_files[cur_file_uuid] + release_tag_string = ", ".join( + [ + x.tag + for x in self._imo.imoobject.releases.values() + if cur_file_uuid in x.data_files + ] + ) + result.append(((str(cur_file.uuid), release_tag_string), cur_file.uuid)) + + # Sort the file names according to their tag names (in reverse order) + return sorted(result, key=lambda x: x[0][1], reverse=True) + + def get_data_file_path(self, uuid): + if not self._quantity: + return "" + + quantity_path = self.get_quantity_path() + data_file_name = self._imo.imoobject.data_files[uuid].name + # Get a list of releases, sorted from the most recent to the oldest + releases = sorted( + [x for x in self._imo.imoobject.releases.values() if uuid in x.data_files], + key=lambda x: x.rel_date, + reverse=True, + ) + return f"/releases/{releases[0].tag}{quantity_path}/{data_file_name}" + + def go_up(self): + if self._base: + self._base = self._base.parent + + def has_parent(self): + return self._base is not None + + +class EntityListView(Frame): + def __init__(self, screen, model): + super(EntityListView, self).__init__( + screen, + screen.height, + screen.width, + on_load=self._reload_list, + hover_focus=False, + can_scroll=False, + title="", + ) + + self.set_theme("custom") + + self._model = model + + self._entity_list_view = MultiColumnListBox( + Widget.FILL_FRAME, + ["<24", "<64"], + model.get_children(), + name="entities", + on_change=self._on_pick, + on_select=self._enter_child, + ) + self._quantities_list_view = MultiColumnListBox( + 10, + ["<24", "<64"], + model.get_quantities(), + name="quantities", + on_change=self._on_pick, + on_select=self._enter_quantity, + ) + + entity_list_layout = Layout([100], fill_frame=True) + self.add_layout(entity_list_layout) + entity_list_layout.add_widget(self._entity_list_view) + entity_list_layout.add_widget(Divider()) + + details_layout = Layout([100]) + self.add_layout(details_layout) + details_layout.add_widget(self._quantities_list_view) + details_layout.add_widget(Divider()) + + button_row_layout = Layout([1, 1, 1, 1]) + self.add_layout(button_row_layout) + button_row_layout.add_widget(Button("Quit", self._quit), 3) + + self._refresh_status() + self.fix() + self._on_pick() + + def _refresh_status(self): + self.title = "IMO entity tree: " + self._model.get_entity_name() + + def _details(self): + pass + + def _on_pick(self): + self._quantities_list_view.options = self._model.get_quantities() + + def _enter_child(self, new_value=None): + self.save() # Fill self.data + + self._model.enter_child(self.data["entities"]) + self._entity_list_view.options = self._model.get_children() + self._entity_list_view.value = new_value + self._refresh_status() + + def _enter_quantity(self, new_value=None): + self.save() # Fill self.data + + self._model.set_current_quantity(self.data["quantities"]) + raise NextScene("Quantity details") + + def _reload_list(self, new_value=None): + self.save() # Fill self.data + + self._entity_list_view.options = self._model.get_children() + self._entity_list_view.value = new_value + self._refresh_status() + + @staticmethod + def _quit(): + raise StopApplication("Quitting the IMO browser…") + + +class QuantityDetailsView(Frame): + def __init__(self, screen, model): + super(QuantityDetailsView, self).__init__( + screen, + screen.height, + screen.width, + on_load=self._reload_list, + hover_focus=False, + can_scroll=False, + title="", + ) + + self.set_theme("custom") + + self._model = model + + self._data_files_view = MultiColumnListBox( + Widget.FILL_FRAME, + ["<40", "<64"], + model.get_data_files(), + name="data_files", + ) + self._copy_path_button = Button("Copy path", self._copy_path) + self._copy_uuid_button = Button("Copy UUID", self._copy_uuid) + + self._copy_path_button.disabled = not USE_PYPERCLIP + self._copy_uuid_button.disabled = not USE_PYPERCLIP + + info_layout = Layout([100], fill_frame=True) + self.add_layout(info_layout) + info_layout.add_widget(self._data_files_view) + info_layout.add_widget(Divider()) + + button_row_layout = Layout([1, 1, 1, 1]) + self.add_layout(button_row_layout) + button_row_layout.add_widget(self._copy_path_button, 0) + button_row_layout.add_widget(self._copy_uuid_button, 1) + button_row_layout.add_widget(Button("Close", self._close), 3) + + self.fix() + self._reload_list() + + def _reload_list(self): + self.save() # Fill self.data + + self.title = f"Quantity {self._model.get_quantity_path()}" + self._data_files_view.options = self._model.get_data_files() + + def _copy_path(self): + self.save() # Fill self.data + + try: + pyperclip.copy(self._model.get_data_file_path(self.data["data_files"])) + except pyperclip.PyperclipException: + # No xclip or xsel installed + pass + + def _copy_uuid(self): + self.save() # Fill self.data + + try: + pyperclip.copy(str(self.data["data_files"])) + except pyperclip.PyperclipException: + # No xclip or xsel installed + pass + + def _close(self): + raise NextScene("Main") + + +def browser(screen, scene): + scenes = [ + Scene([EntityListView(screen, imo)], name="Main"), + Scene([QuantityDetailsView(screen, imo)], name="Quantity details"), + ] + + screen.play(scenes, stop_on_resize=True, start_scene=scene, allow_int=True) + + +if __name__ == "__main__": + imo = EntityBrowser() + last_scene = None + while True: + try: + Screen.wrapper(browser, catch_interrupt=True, arguments=[last_scene]) + sys.exit(0) + except ResizeScreenError as e: + last_scene = e.scene diff --git a/poetry.lock b/poetry.lock index 64c79422..b8f92d09 100644 --- a/poetry.lock +++ b/poetry.lock @@ -39,6 +39,21 @@ dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "wheel", "p docs = ["sphinx"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] +[[package]] +name = "asciimatics" +version = "1.12.0" +description = "A cross-platform package to replace curses (mouse/keyboard input & text colours/positioning) and create ASCII animations" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +future = "*" +Pillow = ">=2.7.0" +pyfiglet = ">=0.7.2" +pywin32 = {version = "*", markers = "sys_platform == \"win32\""} +wcwidth = "*" + [[package]] name = "astropy" version = "4.1" @@ -321,6 +336,14 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" +[[package]] +name = "future" +version = "0.18.2" +description = "Clean single-source support for Python 3 and 2" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "h5py" version = "3.1.0" @@ -1095,6 +1118,14 @@ category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pyfiglet" +version = "0.8.post1" +description = "Pure-python FIGlet implementation" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyflakes" version = "2.2.0" @@ -1148,6 +1179,14 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "pyperclip" +version = "1.8.1" +description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyrsistent" version = "0.17.3" @@ -1224,7 +1263,7 @@ name = "pywin32" version = "300" description = "Python for Window Extensions" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -1664,7 +1703,7 @@ mpi = ["mpi4py"] [metadata] lock-version = "1.1" python-versions = ">=3.6,<3.9" # <3.9 because of Numba 0.52 -content-hash = "8e78aa2933b3dab67f2d1a217ada34b5a514507d481e4112278162b3b125b90a" +content-hash = "85d417928fbe18e51af85c4c2c7c92cdf948af37bafbd73c7574160159500640" [metadata.files] alabaster = [ @@ -1697,6 +1736,10 @@ argon2-cffi = [ {file = "argon2_cffi-20.1.0-cp38-cp38-win32.whl", hash = "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78"}, {file = "argon2_cffi-20.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2"}, ] +asciimatics = [ + {file = "asciimatics-1.12.0-py2.py3-none-any.whl", hash = "sha256:83c8ead386cdcc5fd4cebb4289b0d6da61164e44dfdfff3776b66e6bcfef2e3d"}, + {file = "asciimatics-1.12.0.tar.gz", hash = "sha256:4120461a3fb345638dee4fe0f8a3d3f9b6d2d2e003f95c5f914523f94463158d"}, +] astropy = [ {file = "astropy-4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0804f601dddfd6b5b72e11eb6e58133ff7db25bb8c26851449352ba1f414b74c"}, {file = "astropy-4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:85f199c41d28da78c0a09076904fe3c3034c0f4917d251a23ad364ed0e9ff2d5"}, @@ -1889,6 +1932,9 @@ flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] h5py = [ {file = "h5py-3.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1cd367f89a5441236bdbb795e9fb9a9e3424929c00b4a54254ca760437f83d69"}, {file = "h5py-3.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fea05349f63625a8fb808e57e42bb4c76930cf5d50ac58b678c52f913a48a89b"}, @@ -2329,6 +2375,10 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] +pyfiglet = [ + {file = "pyfiglet-0.8.post1-py2.py3-none-any.whl", hash = "sha256:d555bcea17fbeaf70eaefa48bb119352487e629c9b56f30f383e2c62dd67a01c"}, + {file = "pyfiglet-0.8.post1.tar.gz", hash = "sha256:c6c2321755d09267b438ec7b936825a4910fec696292139e664ca8670e103639"}, +] pyflakes = [ {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, @@ -2349,6 +2399,9 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] +pyperclip = [ + {file = "pyperclip-1.8.1.tar.gz", hash = "sha256:9abef1e79ce635eb62309ecae02dfb5a3eb952fa7d6dce09c1aef063f81424d3"}, +] pyrsistent = [ {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] diff --git a/pyproject.toml b/pyproject.toml index c560b2a8..bd5a1736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,8 @@ toast-cmb = "^2.3.12" ducc0 = "^0.8.0" pysm3 = "^3.3.0" "sphinxcontrib.asciinema" = "^0.2.1" +asciimatics = "^1.12.0" +pyperclip = "^1.8.1" [tool.poetry.extras] mpi = ["mpi4py"]