diff --git a/amor_manual.md b/amor_manual.md deleted file mode 100644 index 335c2d1..0000000 --- a/amor_manual.md +++ /dev/null @@ -1,1283 +0,0 @@ -% User Manual - for the - Neutron Reflectometer Amor -% Jochen Stahn -% 2024-06-06 - ---- - -# Users' guide to NICOS - -## open NICOS - -- *normal* users should be logged on as `amorlnsg` on the instrument control computer `amor.psi.ch`. - That is the one under the desk in the control cabin. - - > computer: `amor.psi.ch` - > username: `amorlnsg` - > password: ask beamline scientist - -- create a local subdirectory for the actual campagne: - - 1. Open a terminal and enter your name. Probably create a new directory with your name. - You will end up in this directory. - 1. Create a sub-directory: `mkdir -` (or the like). - 1. `cd -` - -- open the NICOS gui by typing - - > `nicos-gui` - - in a terminal window as user `amorlnsg`. - The gui will start up. Probably you will have to connect to the NICOS program: - - > in the upper right corner type the wheel and select *connect* - > user: `user` - > password: ask beamline scientist - -## identify yourself - -Upon starting a measurement campagne, you have to enter the following -information: - -- proposal ID -- title -- user name(s) -- affiliation? - -The information is used to create a repository for your data and is -also stored in the meta data section of all your files. - -## brief intro for NICOS commands - -### check and change device parameters - -- everything is a *device* -- each device has a *name*, in most cases a 2 to 3 letter abbreviation - e.g. `som` for the sample tilt (*s*ample *om*ega) -- get the **description** for a device: - - > `` - -- get the **present value** (position, angle, ...): - - > `()` - -- get all the paramters (position, unit, limits, ...): - - > ??? - -- **move** to a new value (and wait until the command is executed): - - > `maw(, [, , ...])` - -All these actions are also realised in the gui: klick on the device name in the -right pannel. A pop-up window opens with all the options available. - -### counting and scanning - - -### batch file creation and execution - - -## define set-up - -... -### devices - -`read()` - -: reads the value of a *device* - -`maw(, [, , ...])` - -: (move and wait) moves a *device* to *value* - - -### left over and does not belong here - -The sample must be positioned with the center of its surface at the -focal point. At the same time it should be in the center of rotation of -the $\omega$ stage. For this reason there are 2 vertical **translation**s which are -close to parallel: - -`soz` -: = *S*ample *O*mega stage *Z* position -: Lift of the $\omega$ stage so that its center is in the FP. - -`stz` -: = *S*ample *T*ranslation *Z* direction -: Lift of the sample on the $\omega$ stage to bring it to the - center of rotation. -: *This stage is not available at the moment!* - -And finally the sample has to be **tilt**ed by using the $\omega$ and $\chi$ stages. - -`mu` -: Tilt of the sample relative to the horizon. -: On amor $\mu$ is used to define and probably to describe the sample - orientation relative to the lab horizon. Since the beam might be convergent - on the sample surface it is not the neutron's angle of incidence! - See also *[coordinate system(s) and nomenclature]*. - -`nu` -: Rotation of the detector center around the sample position -relative to the lab horizon. This is a combined movement of -detector lift, tilt, (*x* translation) and also affects -all other devices behind the sample. - -Diaphragms - -`d` -: = *d*iaphragm number *n*, blade or position *m* -: $n \in \{\mathrm v, 1..4\}$ for $v$irtual source and *n*umber of diaphragm. -: $m \in \{\mathrm{t, b, l, r, h, v, z}\}$ for *t*op, *b*ottem, *l*eft, *r*ight, - *h*orizontal, *v*ertical and *z*-position. -: not all options rea available for all devices. - -| name | description | *m* | range or values / mm | -| :--- | :--- | :--- | :---: | -| `dvv` | virtual source vertical | | 0.5 1 2 3 4 6 8 10 | -| `dvh` | virtual source horizontal | | 2 5 10 12 15 20 25 30 | -| `dmf` | middle focus | | slot 1 .. 5 | -| `d1` | behind Selene guide | `t b l r` | -40 .. +40 | -| `d2` | before sample | `t b l r` | -40 .. +40 | -| `d2Z` | '' lift | | -100 .. +100 | -| `d3` | behind sample | `t b l r` | -40 .. +40 | -| `d3Z` | '' lift | | -100 .. +100 | -| `d4` | before detector | `h v` | +1 .. +140 | - -## perform measurement - -The $q_z$ range of one measurement is defined by the wavelength -range $\lambda \in [3.5, 12.5]$ \AA\ and the spread of the angles -of incidence $\alpha_i$. -For specular reflecivity, $\alpha_i$ is deduced from the position (i.e. angle) -of the detector and the position on the detector where a neutron is detected. - -`count(=)` -: count with *mode* - - - `t` = time for *preset* seconds - - `m` = monitor for *preset* monitor counts - -`scan(, , , , =)` -: scan the *device* from *start* with *np* steps of width *step* with the -counting time defined by *mode* / *preset* - -`run()` -: *run* the script *scriptname*, who's path is either relative to -`Exp.scriptpath` or which has an absolute path. The script language is -python. - - -# How to perform a measurement - -It is assumed here that NICOS is running, the basic data are provided and the -general set-up is realised. - -## identify sample - -Enter a name and a description of the sample. This information will -end up in the meta data section. - - NewSample() - -It is also advised to give a short sample description in orso model format: - - Sample.orsomodel='' - -e.g. - - Sample.orsomodel='air | 20 ( Ni 5 | Ti 7 ) | SiO2' - -## align sample - -There are 2 sets of (virtual) devices: the ones for aligning the smaple and the ones -to select a certain $q$ range or instrument set-up. -The **sample alinment devices** are: - -`mud` -: for *pitch correction*: -: To compensate the misalignment of the sample surface with respect to the -: sample table surface along the beam. - -`sch` -: for *roll correction*: -: To compensate the misalignment of the sample surface with respect to the -: sample table surface normal the beam. - -`sz` -: for *height adjustment*: -: To bring the sample surface to the center of rotation = focal point -: of the neutron beam. - -It is not necessary - nor is it allowed - to rerdefine any device's zero position! - -### height alignment - -In case the sample surface or a mark outside the sample enviromnet indicating -its position are visible, one can use the **surveyor's optical level** mounted -outside the Amor area at the wall next to the experimental cabin. -This ensures that the sample is in the beam with a vertical offset of a few tenths -of a mm. - -If possible, an **absorber** at a defined distance above (or below) the sample surface -can be used: Scanning the sample though the direct beam allows for detecting this absorber -and moving to the right position. - -Using the full divergence and a large virtual source allows for pitch-misalignments -of up to $1^\circ$ and height-offsets of 5 mm to still generate a signal on the detector -for a reflected beam. A good choice for `mu` for searching some signal in this case is -$0.8^\circ$. - -### pitch - -If one of the schemes 'simple' or '???' is used, the detector is positioned in a way -that the refelcted beam footprint is centered. This means that the lower edge of -the footprint - which is much easire to detect then the upper edge - is half the divergence -below the detector center. The center is at channel number $z = 224$. - -If the full divergenc of $1.5^\circ$ is used, the lower footprint boundary is -at channel number $z = 384$. - -### roll - -Once *height* and *pitch* are (roughly) aligned, the *roll* can be corrected by looking at the -$I(y, \theta)$ detector image of the reflected beam. The lower boundary of the -footprint should be exactly horizontal. - -The *roll* is independent of *pitch* and *height*. An iterative alignment process is thus not -necessary. - -An accuracy of about $0.1^\circ$ is sufficient. - -### fine alignment - -The beam cross section at the focal plane is mimimum for the settings - -> `div` = $0.05^\circ$ -> `kad` = $0.70^\circ$ -> `dmf` = 1 - -I.e. *height* and *pitch* alignment with these settings at a moderate $q$ should -give the best results. The iterative procedure is similar to 'conventional' -reflectometers: - -> - scan of `sz` to find the *height* maximum -> - adjust `mud` so that the beam footprint if centered at detector -> channel $z = 224$ - -## measurement: ranges, angles and duration - - - -## reduce data - -The *raw* data at amor are stored in the nexus hdf format. Besides some meta data -bout the experiment and the set-up, it contains the measurement data in form of an event. -I.e. each neutron has one entry with the time and location (better: voxel-ID) of detection. - -There is the python program code **eos** to reduce the event stream to something like a -Intensity vs. wavelength and angle map. This process includes a variety of corrections -(instrument geometry, chopper properties, filtering in wavelength and footprint size, gravity, -etc. If available, a reference measurement (characterising the incident beam, the detector -efficiency and absorption of the sample environment) can be used to create a -$R(\lambda, \theta)$ map for specular reflectometry, .... -and finally an $R(q_z)$ curve. - -The output format follows the *orso* rules. - -### location of the raw data files: - -If not specified differently, **eos* looks for the raw data files in the following directories: - -`./`, `./raw/`, `../raw`, `../../raw`, `/home/amor/data//` - - - -The present approach is to create a subdirectory for the present experiment - - /home/amorlnsg// - -and in there a link to the raw files (if it does not already exist) - - cd /home/amor/data// - ln -s /home/amor/data// raw - -### activate the virtual environment for eos - -in the terminal in which the data reduction is to be performed type: - -```bash -source ~/eos/venv/bin/activate.csh -``` - -### run eos - -**eos** is a command line script. This means that all necessary information is -provided as arguments after the program call on the same line. -At least a file (measurement) number and the name of the output file have to be -provided. This then looks like - -```bash -python ~/eos/eos.py -f -o -``` - -All 'missing' information is guessed or collected by eos, where most default values -are extracted form the raw data file (e.g. beam divergence, instrument settings, -sample and detector angles). Almost all relevant values can be overwritten by -an command line option - in most cases this is not recommended! - -Useful arguments for data reduction are: - -data file(s): - -`-f ` -: number(s) of the raw data files -: the general format is -: `-:[,-:..]` -: !!! - -`-n ` -: number of the reference file for normalisation - -`-Y` -: year of the data collection (in order to create the correct raw file name. -: The default is the actual year. - -filtering / region of interest: - -`-l ` -: wavelength range in angstroms - -`-t ` -: theta range - -`-y ` -: horizontal pixel range on the detector - -`-q ` -: $q_z$ range - -correct angle: - -`-m` -: offset to the `mu` stored in the data file - -`-mu` -: overwrite the data file entry - -scaling: - -`-S ` -: scale the (weighted) average in the given $q_z$ range to 1 - -`-s ` -: multiply *reflectivity* by *value*; executed after `-S` - -formats: - -`-of Rqz | Rlt | ort | orb` -: Defines what is written to the file: -: `Rqz`: $R(q_z)$, -: `Rlt`: $R(\lambda, \theta)$ and much more -: and in which format: -: `ort`: ASCII file -: `orb`: nexus conform binary file - -`-h` -: get help on eos and a description of all arguments. - - - -# Instrument description - -Amor is a neutron reflectometer with a beam focused to the sample position. - -## Parameters and options - -- wavelength range -- angular range -- q range -- polarisation efficiency -- angle of incidence on liquid surfaces - -- sample environment - - 1 T electomagnet (with closed cycle refrigerator) - - 7 T cryomagnet, horizontal field direction - - furnaces - - LB trough - - sheer cell - - potentiostate / galvanostate - - -## From physical source to virtual source - -The *real neutron source* is the spallation target at SINQ. The fast -neutrons created there by proton capture in lead nuclei are moderated -to room temperature using D~2~O, and further down to cold -neutrons by a liquid H~2~ moderator. This is often referred to as -*cold source*. - -Some of these cold neutrons are guided by a 4.5 m long converging -neutron guide (with m = 2.5 coating) to the *virtual source* (VS) position, -just outside the shileding monilith at 6 m from the surface of the -cold moderator. - -virtual source - -: _ -: 2 Boron-Aluminium wheels with horizontal - and vertical slits of various sizes, respectively. - The opening (luminous field diaphragm) is centered at the guide - focal point. - - -Table: Vertical and horizontal slit widths of the virtual source diaphragm. - - | opening | vertical | horizontal | - | ---: | ---: | ---: | - | | / mm | / mm | - | 1 | 0.5 | 2.0 | - | 2 | 1.0 | 5.0 | - | 3 | 2.0 | 10.0 | - | 4 | 3.0 | 12.0 | - | 5 | 4.0 | 15.0 | - | 6 | 6.0 | 20.0 | - | 7 | 8.0 | 25.0 | - | 8 | 10.0 | 30.0 | - -## Selene guide - -The divergent neutron beam emerging from the *virtual source* slits is -collected and focussed to a point (the *mdiddle focus*, MF) -some 15 m away from the VS by a set of -two elliptically bent mirrors (i.e. a Montel optics), where one reflects horizontally towards -the Aare, and the other vertically upwards. - -The image of the VS at the MF is distorted due to come aberatin. To correct for this -to first order, a second Montel optics follows which reflects towards Berg and downwards. -This results in an image of the VS at the final focal point, some 30 m behind the VS. -This image is distorted spatialy due to imperfections of the Montel optics (surface and alignement) -and it is inhomogeneous in intensity due to an only partially corrected divergence distribution and -due to losses during reflection. - -This arrangement of two Montel optics we called **Selene guide**. - -The Selene guide is made up of about 500 mm long L-shaped elemets. These are individually -tiltable in vertical direction (*pitch* movement). 6 of these elements are bundeled on a -support beam which rests on two vertically movable sockets. 3 beams form one Montel optics. -All these movements are highly delicat because they might lead to collissions and thus -demage at places that are not accessable for years - if ever again. Thus no user is -allowd to run these. - -The housings of each Montel optics plus the ones of some beam shaping elements and of -one chopper disk each form a continuous vacuum vessel. - -The Montel optics are each some 9 m long and located symmetrically between the adjunct focal -points. This means there are gaps between VS and guide entry and the guide exit to FP of -3 m, and of 6 m between the Montel optics. These gaps are used for conditioning the -neutron beam. - -### characteristic measures - -- $a = 7'421\,\mathrm{mm}$ -- $b = 129.50\,\mathrm{mm}$ -- $c = \sqrt{a^2 - b^2} = 7'420\,\mathrm{mm}$ -- $4c = 29'680\,\mathrm{mm}$ -- $\Delta \alpha = \arctan \frac{b}{a} \sqrt{\frac{1+\xi}{1-\xi}} - - \arctan \frac{b}{a} \sqrt{\frac{1-\xi}{1+\xi}}$ - $\approx\, 1.5\, b/a \,\approx\, 1.5^\circ$ for $\xi = 0.6$ - -## Optics in the bunker - -The gap between VS and Montel optics is bridged by an evacuated flight tube. - -The large central gap hoists: - -polariser / frame overlap filter - -: \hfill MF - 2700 -: The polariser consists of Fe/Si supermirror coated Si blades which are bent in - the shape of a logarithmic spiral with the MF as the spiral pole. To enhance - the polarisation efficiency, 2 of these sheets are mounted with a distance of a few - cm. The total arrangement is some 2.1 m long. -: The frame overlap filter (FOF) has a similar design, but only one sheet, coated on the - outside (towards the source) with Ni. -: Both are monted one baove the other and switching means a vertical translation of the - assembly. - -first chopper disk - -: (master) \hfill MF - 500 -: The chopper disk has 2 openings of 13.5^o^, each, and rotates with a speed of - 1000 rpm creating pulses ever 33.33 s. -: The beam shaping properties are discussed below in context with the second chopper disk. - -main shutter - -: \hfill MF - 300 - -beam monitor - -: \hfill MF - 250 -: The monitor is a fission chamber with sensitivity 10^-?^. - -neutron camera - -: \hfill MF -: For alignment purposes. By default not in the beam. - -middle focus aperture - -: \hfill MF -: Wheel with 5 freely configurable slots. - Presently unused. - -second chopper disk - -: (slave) \hfill MF + 500 -: Geometrically identical to the first chopper disk, but rotating - in the opposite sense with a phase offset of -13.5^o^. - This way they form a *blind double chopper* with a resolution - $\Delta t/t = \Delta \lambda/\lambda = constant$. [@vanWell] - The center of this set-up, i.e. MF, is the virtual origin in time and space - for the time-of-flight encoding of wavelength $\lambda$. - - -RF spin flipper - -: _ - -and some shielding elements and flight tubes. - -These optical elements produce a neutron beam which - - - is convergent to the FP - - is restricted to a wavelength range of 3 to 9.5 Aa - - might be polarised with selectable polarisation - - has a time focus 15 m before the FP (i.e. the pulse they belong to was - virtually created 15 m upstream for all divergences due to the - equal trajectory lengths in an ellipse). - -## Optical bench - -Behind the exit of the second Montel optics the following -components are / will be / might be installed: - -instrument shutter - -: _ - -laser system for sample alignment - -: (to come) - -diaphragm D1 - -: (to come) -: for defining vertical and horizontal divergences - and for changeing the angle of incidence for a reduced beam within the - full divergence of 1.5 deg. -: individual movement of $\pm40$ mm of all 4 blades relative to center - -deflector - -: (to come, optional) -: to redirect a restricted beam downwards to - liquid surfaces - -diaphragm D2 - -: (to come) -: to reduce background -: individual movement of $\pm40$ mm of all 4 blades relative to center - -sample table - -: _ - -diaphragm D3 - -: (to come) -: to reduce background -: individual movement of $\pm40$ mm of all 4 blades relative to center - -analyser spin flipper - -: (future option) - -analyser - -: (future option) - -diaphragm D4 - -: (to come) -: vertical / horizontal slit with 1 to 160 mm opening, centered - -detector - -: _ -: the active region starts 4'000 mm behind the FP and 18'842 mm - behind the MF in standard position -: The detector was designed and built by the ESS detector group. - It is a prototype for the detector for Estia@ESS with a reduced active area - of $140 \times 160$ mm^2^. The full beam footprint is - $110 \times 120$ mm^2^. - -In addition there might be flight tubes and shileding. - -## Infrastructure - -### Areas - -### Media - -### EDV - - -#### Amor network - -By default all LAN sockets in the experimental areas (below the granite -beam and against the bunker wall on the upper area) and all -LAN sockets in the cabin below the desk are connected to the Amor -network. - -In this network there are also the instrument controll computer -and computers for the detector, the chopper, .... - -#### PSI network - -By default the 2 LAN sockets in the corner opposite the the doors in the -cabin are connected to the PSI web. - -!!! WLAN: EDUROM, CORP - -### PSYS - -# Experiments - -## coordinate system(s) and nomenclature - -The lab coordinate system is ... - -z -: vertical direction - -x -: horizontal, parallel to the projection of the beam center at the -sample position on the horizontal plane -: roghly pointing from *Böttstein* towards *Villigen* - -y -: horizontal, forming a right-handed system with x and z -: roughly pointing from *Berg* towards *Aare* - -The special situation with an incoming beam on the sample with -potentially a divergence of up to 1.5^o^ requires a differentiation -between sample tilt ($\omega$) and angle of incidence ($\alpha_i$). -In the case of specular reflectivity we define $\theta := \alpha_i = -\alpha_f$. - -We define $\omega$ as the tilt angle of the sample with respect -to the horizon of the instrument. I.e. for liquid surfaces it is -allways zero!. - - -*to be checked for consistency:* -The position of the detector $\delta$ (realised by 2 translations in $x$ and -$z$ direction), its tilt towards the sample $-\delta$ and the -detection position of the neutron on / in the detector described by $\Delta\delta$ -result in an angle $\delta + \Delta\delta = \omega + \theta$ relative to the -instrument horizon. Thus -$$ \theta = \delta + \Delta\delta - \omega$$ - -![Geometry and angles used for the focused beam set-up. The blue line -represents the horison, the black line the inclination $\omega$ of the sample surface -relative to the horizon. The gray bar represents the detector located -at an angle $\delta$. The red neutron trajectory hits the detector with an -offset $\Delta\delta$.](angles.eps) - - -## Set-up options - -### polarisation - -Amor is (will be) equipped with a spiral-shaped neutron polariser. -It is located before the first chopper within a vacuum housing in -the neutron guide bunker. - -The polariser is mounted on a vertical translation system which -allows for 3 settings: - -- **polarised**: the polariser with a frame overlap filter coating - is in the beam -- **unpolarised**: the frame overlap filter is in the beam -- **open**: the neutron beam passes in between polariser and - frame overlap filter - -### liquid surfaces - -(not yet installed) - -The neutron beam can be inclined downward onto a horizontal (liquid) -surface by a mirror mounted on the optical bench right after the -first diaphragm. - -The limited acceptance of this mirror means that only a small part of -the beam can be used. I.e. wide-divergence reflectometry is not -possible in this mode. The srrong off-specular scattering from a -liquid surface anyhow asks for a low-divergent beam. - -Within the divergence of the beam before the first diaphragm it is -possible to scan / vary the angle of incidence on the sample -without moving it vertically. - -### beam divergence - -The *natural* divergence of the beam at the sample position -in the scattering plane is about 1.5 deg. It can be reduced by -closing the first slit behind the guide, D1. - -The independent movement of the blades allows to vary or scan -the position of the slit opening across the divergent beam. -I.e. The angle of incidence on the sample can be changed without -tilting the sample. - ---- - -# cold start of the instrument - -Short description of how to start hard- and software after the shut down -or after a power failure. - -## electronic racks - -Starting the **motor control units** (MCU) in rack 1 and the **SPS** in rack 3 -has to be done by the repsective LIN staff. Presently that would be -Marccel Schild (MCU) and Roman Buerge (SPS). - -Once the SPS is running, one has to set the gas flows for the detector and -probably for the flight tube: - -``` -SPS - |- main - |- Detector - |- gas flow 'Detector counting gas' to 7 sccm - |- gas flow 'Flight tube Argon' to 10 sccm -``` - -## chopper - -Starting the chopper is tricky, instable and time-consuming. Be patient. -Mind the order of the operations! - -Situation: *device control unit* DCU (upper box, small display) and supervision -(lower box with large screen) switched off - -1. switch on DCU and boot - key switch to 'PC' -1. switch on supervision (back side), boot and wait for `AMOR GUI` to start -1. on the controll screen: - - 1. klick on the top bar of *Chopper 1* - 1. tag *Chopper*: *DCU Command* to `Callibrate` - *accept-key* - 1. klick on the top bar of *Chopper 2* - 1. tag *Chopper*: *DCU Command* to `Callibrate` - *accept-key* - 1. wait for both choppers to report *positioning* or *ready* - on the DCU screen - 1. klick on the top bar of *Chopper 1* - 1. tag *Chopper*: *DCU Command* to `Async Rotation` - *Speed* to 500 rpm - *accept-key* - 1. klick on the top bar of *Chopper 2* - *DCU Command*: `Sync. rotation` - *Phase*: -17 - *Master*: `Master Chopper 1` - *Gear Ratio*: `1/1` - *accept-key* - 1. should be fine now! - -## amor computer and instrument control - -The instrument control computer `amor.psi.ch` (`172.28.65.60`, `PC14655`) -is located in the cabin under the table on the right side. - -1. switch on and boot -1. log in as user *amorlnsg* (24lns1) -1. start and control processes: - open a terminal window and switch user to *amor*: - - ``` - su - amor - marche-gui - ``` - - in Marche gui: - - - klick `amor.psi.ch` - - start *nicos* - - start *amorIOC* - - start *Histogram Memory* - - start all *Kafka-to-Nexus...* processes - - start *Automatic File Sync* - -1. start NICOS: - - - open a terminal window and enter - `nicos-gui &` - - in the NICOS gui klick the tooth wheel - `Connect to server` - *User name*: `user` - *Password*: `24lns1` - *OK* - -## detector - -see section *MBamor* - - -## data visualisation and reduction - -as user `amorlnsg@amor.psi.ch` (i.e. when opening a new termminal on the amor computer) -perform the following steps: - -``` -source ~/eos/venv/bin/activate.csh -cd -python ~/eos/events2histogram.py -display -update 2 e2h.png & -``` - -Now you can use `events2histogram.py` for fast visualisation and -`eos.py` for data reduction in THIS terminal window. - -## sample environment - -in NICOS gui: - -``` -|- Setup -| |- Instrument -| | |- frappy -> select -| | |- frappy_main -> select -| |- Apply -| -|- Instrument interaction - |- output -> type frappy('') -``` - -e.g. potentiostat: -`frappy('smamor')` -generates `frappy-main` with devices `se_smi` and `se_smv` - -in terminal window: - -``` -sea -``` - ---- - -# Marche - daemon control - -The daemons for NICOS, EPICS, `filewriter` and so on are controlled via -`Marche`. - -1. `marche` gui - -> 1. log in as user `amor@amor.psi.ch` -> 1. call `marche-gui` - -1. `marche` script ? - - - -# the detector **MBamor** - -## parameters - -size: 14 blades, each 32 channels high and 64 channels wide -sample detector distance (to blade tips): 4000 mm -(distance blade tip to housing front: 33 mm -inclination of the blades: 5.1 deg -separation of blades: 10.5 mm or 0.15 deg - - -## launching of electronics and hardware - -1. start server **det-efu02** in rack 1 - (a monitor might be needed during booting to enable 'vnc') -1. switch on **master module** in rack 1 -1. switch on **assistor**s on top of the detector tower - (probably just reconnect the LV cables) - check **Master/ring status**: should be 'not configured' -1. ramp up **HV** - - | channel | name | U / V | I / $\mu$A | - | --- | --- | ---: | ---: | - | 0 | K1 | 1230 | 73 | - | 1 | R1 | 1050 | 41 | - | 2 | K2 | 1230 | 73 | - | 3 | R2 | 1050 | 41 | - - (start from *off* position, not from *kill*) - -## start processes on server **det-efu02** - - -The actions below can either be performed via `ssh` or `vnc`. In the latter case -(more challenging for the server graphic card) one might have to start the -**vncserver** first on the det-efu02: - -> presently, vnc does not work - most likely because of RH8. -> use `ssh` instead! -> -> 1. start `vncserver` (optional, should be launched at booting) -> -> ``` -> cd essproj -> ./startvnc -> cd -> ``` -> 1. change graphic settings for vnc -> -> ``` -> cd detg_git -> ./display_vnc -> cd -> ``` -> connecting via `vnc` -> -> - via web browser from `amor` or `amor-dr`: -> enter 'vnc://det-efu02:5901' in address bar -> - via `boxes` on console -> `vnc://172.28.65.80:5901` mit passwd `essdaq` - -1. log into `det_efu02`: - (e.g. from a terminal window on the computer `amor.psi.ch` as user `amor` or `amorlnsg`) - - ``` - ssh essdaq@172.28.65.80 - ``` - - (password `essdaq`) - -1. start **ring controll**` - - ``` - cd slowctrl_rmm - ./run_all.sh - cd - ``` - - (the `poll` numbers should not exceed 16) - - the **master/ring status** shown on the master module should be green for rings 0, 1 and 2 - -1. start slow controll pannel - - ``` - ./vmmdcs & - ``` - - `VMM3a` opens - - initialise slow control in `VMM3a` window - - 1. load configuration - - - *open folder* (lower left region) - - select 'Amordetector....' and *open* - - *Load* - - *FEN00\** shows up - - 1. initialise and start acquisition - - - *open communication* - - array on the left should show '4' for all hybrids - - > if not all hybrids show '4': - > - > - *ACQ off* - > - select *FEN0/1/2* - > - *Warm init* - > - *Send* - > - *ACQ on* - > - > escalation: - > - > 1. *Hard reset* (lower right corner) - > 1. power cycle LV (NOT front end assistors) - - - *send* - - *ACQ on* - - array on the left should show '5' for all hybrids - - -1. start **event formation unit**s - - ``` - cd Desktop - ./runall.bash - cd - ``` - -## start processes on `amor-dr` - -### daqlite - -start `daqlite` for *real*time images of detector output - -``` -cd Desktop -./daqlite.bash -cd -``` - -## start processes on `amor` - -### graphana - -to monitor the efu activity - -1. on amor: - - - open Firefox tab - - enter `https://172.28.65.80:3000` (or bookmark) - - select 'Freia' - -2. on `amor`: - - - open firefox browser - - address: `vnc://det-efu02:5901` - passwd: essdaq - - - open browser - - select AMORB2 - - -## shut down - -1. **slow controll**: *ACQ off* -2. switch off **front end assistor**s -3. in random order: - - - close slow controll - - close daqlite - - stop **efu** on det-efu02: - - ``` - cd Desktop - ./stopall.bash - cd - ``` - -4. shut down det-efu02 - (can be powerd off without shutting down) - -## configuration options - -### chopper signal - -The default setting is that the chopper trigger signal is used as a timing signal. -I.e. without chopper TTL signal, no data acqusition. -This can be changed on 'det-efu02' in the script `.../run_all.sh` at approximately line -30: -toggle the comments on the entries -`...` -and -`...` - -## maintanance - -### temperature - -check every couple of weeks: -*VMM3a / I2C / measure* - -### uptime - -The **VMM**s have a limited life time (approx 5 years). It is thus recommended to -power them down if not used for more than about a week: -*VMM3a / ACQ off* -disconnect LV power cable - -### trouble shooting - -- **dark lines - *VMM3a / ACQ off* - *VMM3a / FEN\* / Warm init* - *VMM3a / Send* - *VMM3a / ACQ on* - -- **dark area** - on detector image but all hybrids show '5' (green): - restart rings: - `det-efu02> .../run_all.sh` - -- **no data** - - - chopper TTL signal might be missing - - *VMM3a / ... * all on '5' (green): - restart rings: - `det-efu02> .../run_all.sh` - -# trouble shooting - -## (re)start services - -- log in as `amor@amor.psi.ch`: -- open `marche-gui` (unless already open) -- check for status of - - - EPICS - - NICOS - - `chopper.service` - - `histogrammer.service` - - `kafka-to-nexus.target` - - and probably *stop* / *restart* / *start* the service(s) - -- I do not remember what this is for: - - ``` - telnet localhost 20000 - ^x - ^[ - quit - ``` - -# kafka stuff - -## check kafka input from `efu` - -on `amor`: - -- `source /home/software/virtualenvs/kafka-tools/bin/activate.csh` -- `kafka-spy -b linkafka01:9092 -t amor_ev44 -C` - -# NICOS commands - -## start / stop NICOS - -in case the Selene pitches MCUs are active, one can deactivate these with - - amor> caput SQ:AMOR:SEL2.AOUT M714=0 - amor> caput SQ:AMOR:SEL2.AOUT M814=0 - -The **gui** can be launched also by the `amorlnsg` from the console: -and to launch the gui - - /home/amor/bin/nicos-gui - -with - - user: user - password: 24lns1 - -## devices and movements - -In NICOS everything is a device! - -`ListDevices()` - -: shows all available devices - -`read()` - -: reads the value of a *device* - -`maw(, [, , ...])` - -: (move and wait) moves a *device* to *value* - -`move(, )` - -: starts moving a *device* to *value* without waiting - -`wait(, , ...)` - -: waits for the listed *devices* to finish - -`status()` - -: queries the status of a *device* - -`stop()` - -: stops devices, also Ctr-C, Ctrl-C - -NICOS devices can have parameters - -`ListParams()` - -: lists the accessible parameters of a *device* - -`.` - -: queries the value of a *parameter* of *device* - -`.=` - -: sets a new *value* for a *parameter* of a *device* - -`ListMethods()` - -: lists the methods of a *device* - -## measurement - -`count(=)` - -: count with *mode* - - - `t` = time for *preset* seconds - - `m` = monitor for *preset* monitor counts - -`scan(, , , , =)` - -: scan the *device* from *start* with *np* steps of width *step* with the -counting time defined by *mode* / *preset* - -`scan(, [, , , , ... ], =)` - -: scans points *p1, p2, ...* as given in the list - -`cscan(dev, center, step, np, t=2)` - -: center-scan: scans *device* around a *center* with *np* steps of width *step* to -either side. - -## batch files - -Batch files are written in python. -You can use all of python plus the NICOS command set - -`run()` - -: *run* the script *scriptname*, which is either relative to -`Exp.scriptpath` or has an absolute path - -`sim()` - -: *sim*ulate the script *scriptname* - -## raw data - -Location of the raw data files: - - /home/amor/data// - -Location of lof files: - - /home/amor/nicos/log - diff --git a/events2histogram.py b/events2histogram.py index 140285e..9ecc8ee 100755 --- a/events2histogram.py +++ b/events2histogram.py @@ -860,7 +860,7 @@ def process(dataPath, ident, clas): try: lamdaMax except NameError: lamdaMax = lamdaMin + tau * hdm/chopperDetectorDistance * 1e13 - tofOffset = tau * chopperPhase / 180. # mismatch of chopper pulse and time-zero + tofOffset = -tau * chopperPhase / 180. # mismatch of chopper pulse and time-zero tofCut = lamdaCut * chopperDetectorDistance / hdm * 1.e-13 # tof of frame start tof_e = np.array(ev['/entry1/Amor/detector/data/event_time_offset'][:], dtype=np.uint64)/1.e9 + tofOffset # tof @@ -960,7 +960,7 @@ def commandLineArgs(): type=float, help ="value of nu") clas.add_argument("-P", "--chopperPhase", - default=7.5, + default=-7.5, type=float, help ="chopper phase offset") clas.add_argument("-p", "--plot", diff --git a/life_histogrammer.py b/life_histogrammer.py deleted file mode 100755 index 5aaafff..0000000 --- a/life_histogrammer.py +++ /dev/null @@ -1,716 +0,0 @@ -__version__ = '2024-03-30' - -import os -import sys -import subprocess -import h5py -import glob -import numpy as np -import argparse -import matplotlib.pyplot as plt -import matplotlib as mpl -import time -import logging -from datetime import datetime - -#============================================================================== -#============================================================================== -class Detector: - def __init__(self): - self.nBlades = 14 # number of active blades in the detector - angle = np.deg2rad( 5.1 ) # deg angle of incidence of the beam on the blades (def: 5.1) - self.dZ = 4.0 * np.sin(angle) # mm height-distance of neighboring pixels on one blade - self.dX = 4.0 * np.cos(angle) # mm depth-distance of neighboring pixels on one blace - self.bladeZ = 10.7 # mm distance between detector blades (consistent with nu!) - self.zero = 0.5 * self.nBlades * self.bladeZ # mm vertical center of the detector - -#============================================================================== -def pixel2quantity(): - det = Detector() - nPixel = 64 * 32 * det.nBlades - pixelID = np.arange(nPixel) - (bladeNr, bPixel) = np.divmod(pixelID, 64*32) - (bZ, bY) = np.divmod(bPixel, 64) - z = det.zero - bladeNr * det.bladeZ - bZ * det.dZ - x = (31 - bZ) * det.dX - bladeAngle = np.rad2deg( 2. * np.arcsin(0.5*det.bladeZ / detectorDistance) ) - delta = (det.nBlades/2. - bladeNr) * bladeAngle - np.rad2deg( np.arctan(bZ*det.dZ / ( detectorDistance + bZ * det.dX) ) ) - dZ = bladeNr * 32 + bZ - quantity = np.vstack((dZ.T, bY.T, delta.T, x.T)).T - - return quantity - -#============================================================================== -def analyse_ev(event_e, tof_e, yMin, yMax, thetaMin, thetaMax): - - data_e = np.zeros((len(event_e), 9), dtype=float) - - # data_e column description: - # 0: wall time / s - # 1: pixelID - # 2: z on detector - # 3: y on detector - # 4: delta / deg = angle on detector - # 5: path within detector / mm - # 6: lambda / angstrom - # 7: theta / deg - # 8: q_z / angstrom^-1 - - data_e[:,0] = tof_e[:] - data_e[:,1] = event_e[:] - - # filter 'strange' tof times > 2 tau - if True: - filter_e = (data_e[:,0] <= 2*tau) - #print(event_e[~filter_e]) - #print(data_e[~filter_e,0]) - data_e = data_e[filter_e,:] - if np.shape(filter_e)[0]-np.shape(data_e)[0] > 0.5 and verbous: - logging.warning(f'## strange times: {np.shape(filter_e)[0]-np.shape(data_e)[0]}') - - pixelLookUp = pixel2quantity() - data_e[:,2:6] = pixelLookUp[np.int_(data_e[:,1])-1,:] - - #================================ - - # filter y range - filter_e = (yMin <= data_e[:,3]) & (data_e[:,3] <= yMax) - data_e = data_e[filter_e,:] - - # correct tof for beam size effect at chopper - data_e[:,0] -= ( data_e[:,4] / 180. ) * tau - - # effective flight path length - #data_e[:,5] = chopperDetectorDistance + data_e[:,5] - - # calculate lambda - hdm = 6.626176e-34/1.674928e-27 # h / m - data_e[:,6] = 1.e13 * data_e[:,0] * hdm / ( chopperDetectorDistance + data_e[:,5] ) - - # theta - data_e[:,7] = nu - mu + data_e[:,4] - - # gravity compensation - data_e[:,7] += np.rad2deg( np.arctan( 3.07e-10 * ( detectorDistance + data_e[:,5]) * data_e[:,6] * data_e[:,6] ) ) - - # filter theta range - filter_l = (thetaMin <= data_e[:,7]) & (data_e[:,7] <= thetaMax) - data_e = data_e[filter_l,:] - - # q_z - data_e[:,8] = 4*np.pi * np.sin( np.deg2rad( data_e[:,7] ) ) / data_e[:,6] - - # filter q_z range - #filter_e = (qMin < data_e[:,6]) & (data_e[:,6] < qMax) - #data_e = data_e[filter_e,:] - - return data_e - -#============================================================================== -class Meta: - # AMOR hdf dataset with associated properties from metadata - def __init__(self, fileName): - self.fileName = fileName - - fh = h5py.File(fileName, 'r', swmr=True) - - # for processing - - self.chopperDistance = float(np.take(fh['/entry1/Amor/chopper/pair_separation'], 0)) # mm - # the following is the distance from the sample to the detector entry window, not to the center of rotation - self.detectorDistance = float(np.take(fh['/entry1/Amor/detector/transformation/distance'], 0)) # mm - self.chopperDetectorDistance = self.detectorDistance - float(np.take(fh['entry1/Amor/chopper/distance'], 0)) # mm - - self.lamdaCut = 2.5 # Aa - - startDate = str(fh['/entry1/start_time'][0].decode('utf-8')) - self.startDate = datetime.strptime(startDate, '%Y-%m-%d %H:%M:%S') - startDate = datetime.timestamp(self.startDate) - self.countingTime = float(np.take(fh['/entry1/Amor/detector/data/event_time_zero'], -1))/1e9 - startDate - # not exact for low rates - - ka0 = 0.245 # given inclination of the beam after the Selene guide - - year_date = str(datetime.today()).split(' ')[0].replace("-", "/", 1) - - # deside from where to take the control paralemters - try: - self.mu = float(np.take(fh['/entry1/Amor/master_parameters/mu/value'], 0)) - self.nu = float(np.take(fh['/entry1/Amor/master_parameters/nu/value'], 0)) - self.kap = float(np.take(fh['/entry1/Amor/master_parameters/kap/value'], 0)) - self.kad = float(np.take(fh['/entry1/Amor/master_parameters/kad/value'], 0)) - self.div = float(np.take(fh['/entry1/Amor/master_parameters/div/value'], 0)) - chSp = float(np.take(fh['/entry1/Amor/chopper/rotation_speed/value'], 0)) - self.chPh = float(np.take(fh['/entry1/Amor/chopper/phase/value'], 0)) - except (KeyError, IndexError): - logging.warning(f" using parameters from nicos cache") - #cachePath = '/home/amor/nicosdata/amor/cache/' - cachePath = '/home/nicos/amorcache/' - value = str(subprocess.getoutput('/usr/bin/grep "value" '+cachePath+'nicos-mu/'+year_date)).split('\t')[-1] - self.mu = float(value) - value = str(subprocess.getoutput('/usr/bin/grep "value" '+cachePath+'nicos-nu/'+year_date)).split('\t')[-1] - self.nu = float(value) - value = str(subprocess.getoutput('/usr/bin/grep "value" '+cachePath+'nicos-kap/'+year_date)).split('\t')[-1] - self.kap = float(value) - value = str(subprocess.getoutput('/usr/bin/grep "value" '+cachePath+'nicos-kad/'+year_date)).split('\t')[-1] - self.kad = float(value) - value = str(subprocess.getoutput('/usr/bin/grep "value" '+cachePath+'nicos-div/'+year_date)).split('\t')[-1] - self.div = float(value) - value = str(subprocess.getoutput('/usr/bin/grep "value" '+cachePath+'nicos-ch1_speed/'+year_date)).split('\t')[-1] - chSp = float(value) - self.chPh = np.nan - - if chSp: - self.tau = 30. / chSp - else: - self.tau = 0 - - try: # not yet correctly implemented in nexus template - spin = str(fh['/entry1/polarizer/spin_flipper/spin'][0].decode('utf-8')) - if spin == "b'p'": - self.spin = 'p' - elif spin == "b'm'": - self.spin = 'm' - elif spin == "b'up'": - self.spin = 'p' - elif spin == "b'down'": - self.spin = 'm' - elif spin == '?': - self.spin = '?' - else: - self.spin = 'n' - except (KeyError, IndexError): - self.spin = '?' - - fh.close() - -#============================================================================== -def resolveNumber(dataPath, ident): - if ident == '0' or '-' in ident[0]: - try: - nnr = int(ident) - except: - logging.error("ERROR: '{}' is no valid file identifier!".format(ident)) - fileNames = glob.glob(dataPath+'/*.hdf') - fileNames.sort() - fileName = fileNames[nnr-1] - fileName = fileName.split('/')[-1] - fileNumber = fileName.split('n')[1].split('.')[0].lstrip('0') - else: - fileNumber = ident - - return fileNumber - -#============================================================================== -def fileNameCreator(dataPath, ident): - clas = commandLineArgs() - ident=str(ident) - try: - nnr = int(ident) - except: - logging.error("ERROR: '{}' is no valid file identifier!".format(ident)) - - if nnr <= 0 : - fileName = glob.glob(dataPath+'/*.hdf')[nnr-1] - fileName = fileName.split('/')[-1] - else: - fileName = f'amor{clas.year}n{ident:>06s}' - - fileName = fileName.split('.')[0] - fileName = fileName+'.hdf' - fileName = dataPath+fileName - - fileNumber = fileName.split('n')[-1].split('.')[0].lstrip('0') - - return fileName, fileNumber - -#============================================================================== -class PlotSelection: - - def headline(self, fileNumber, totalCounts): - headLine = "#{} \u03bc={:>1.2f} \u03bd={:>1.2f} {:>12,} cts {:>8.1f} s".format(fileNumber, mu+5e-3, nu+5e-3, totalCounts, countingTime) - return headLine - - # grids - - def y_grid(self): - y_grid = np.arange(yMin, yMax+1, 1) - return y_grid - - def lamda_grid(self): - dldl = 0.005 # Delta lambda / lambda - lMin = max(2, lamdaMin) - lamda_grid = lMin*(1+dldl)**np.arange(int(np.log(lamdaMax/lMin)/np.log(1+dldl)+1)) - return lamda_grid - - def theta_grid(self): - det = Detector() - - bladeAngle = np.rad2deg( 2. * np.arcsin(0.5*det.bladeZ / detectorDistance) ) - blade_grid = np.arctan( np.arange(33) * det.dZ / ( detectorDistance + np.arange(33) * det.dX) ) - blade_grid = np.rad2deg(blade_grid) - stepWidth = blade_grid[1] - blade_grid[0] - blade_grid = blade_grid - 0.2 * stepWidth - - delta_grid = [] - for b in np.arange(det.nBlades-1): - delta_grid = np.concatenate((delta_grid, blade_grid), axis=None) - blade_grid = blade_grid + bladeAngle - delta_grid = delta_grid[delta_grid=thetaMin] - theta_grid = theta_grid[theta_grid<=thetaMax] - - return theta_grid - - def q_grid(self): - dqdq = 0.010 # Delta q_z / q_z - q_grid = qMin*(1.+dqdq)**np.arange(int(np.log(qMax/qMin)/np.log(1+dqdq))) - return q_grid - - # create PNG with several plots - - def all(self, fileNumber, arg, data_e): - #cmap='gist_earth' - cmap = mpl.cm.gnuplot(np.arange(256)) - cmap[:1, :] = np.array([256/256, 255/256, 236/256, 1]) - cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0]) - I_yt, bins_y, bins_t = np.histogram2d(data_e[:,3], data_e[:,7], bins = (self.y_grid(), self.theta_grid())) - I_lt, bins_l, bins_t = np.histogram2d(data_e[:,6], data_e[:,7], bins = (self.lamda_grid(), self.theta_grid())) - I_q, bins_q = np.histogram(data_e[:,8], bins = self.q_grid()) - q_lim = 4*np.pi*np.array([ max( np.sin(self.theta_grid()[0]*np.pi/180.)/self.lamda_grid()[-1] , 1e-4 ), - min( np.sin(self.theta_grid()[-1]*np.pi/180.)/self.lamda_grid()[0] , 0.03 )]) - if arg == 'lin': - #vmin = min(np.min(I_lt), np.min(I_yt)) - vmin = 0 - vmax = max(5, np.max(I_lt), np.max(I_yt)) - else: - vmin = 0 - vmax = max(1, np.log(np.max(I_lt)+.1)/np.log(10)*1.05, np.log(np.max(I_yt)+.1)/np.log(10)*1.05) - # I(y, theta) - fig = plt.figure() - axs = fig.add_gridspec(2,3) - myt = fig.add_subplot(axs[0,0]) - myt.set_title('detector area') - myt.set_xlabel('$y ~/~ \\mathrm{bins}$') - myt.set_ylabel('$\\theta ~/~ \\mathrm{deg}$') - if arg == 'lin': - myt.pcolormesh(bins_y, bins_t, I_yt.T, cmap=cmap, vmin=vmin, vmax=vmax) - else: - myt.pcolormesh(bins_y, bins_t, (np.log(I_yt + 5.e-1) / np.log(10.)).T, cmap=cmap, vmin=vmin, vmax=vmax) - # I(lambda, theta) - mlt = fig.add_subplot(axs[0,1:]) - mlt.set_title('angle- and energy disperse') - mlt.set_xlabel('$\\lambda ~/~ \\mathrm{\\AA}$') - mlt.axes.get_yaxis().set_visible(False) - if arg == 'lin': - cb = mlt.pcolormesh(bins_l, bins_t, I_lt.T, cmap=cmap, vmin=vmin, vmax=vmax) - else: - cb = mlt.pcolormesh(bins_l, bins_t, (np.log(I_lt + 5.e-1) / np.log(10.)).T, cmap=cmap, vmin=vmin, vmax=vmax) - # I(q_z) - lqz = fig.add_subplot(axs[1,:]) - lqz.set_title('$I(q_z)$') - lqz.set_ylabel('$\\log_{10}(\\mathrm{cnts})$') - lqz.set_xlabel('$q_z~/~\\mathrm{\\AA}^{-1}$') - lqz.set_xlim(q_lim) - if arg == 'lin': - plt.plot(bins_q[:-1], I_q, color='blue', linewidth=0.5) - else: - err_q = np.sqrt(I_q+1) - low_q = np.where(I_q-err_q>0, I_q-err_q, 0.1) - plt.fill_between(bins_q[:-1], np.log(low_q)/np.log(10), np.log(I_q+err_q/2)/np.log(10), color='lightgrey') - plt.plot(bins_q[:-1], np.log(I_q+5e-1)/np.log(10), color='blue', linewidth=0.5) - lw = I_q[ ((q_lim[0] < bins_q[:-1]) & (bins_q[:-1] < q_lim[1])) ].min() - plt.ylim(max(-0.1, np.log(lw+.1)/np.log(10)-0.1), ) - # - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=2.8, c='r') - fig.colorbar(cb, ax=mlt) - plt.subplots_adjust(hspace=0.6, wspace=0.1) - plt.savefig(output, format='png', dpi=150) - - # create PNG with one plot - - def Iyz(self, fileNumber, arg, data_e): - det = Detector() - cmap = mpl.cm.gnuplot(np.arange(256)) - cmap[:1, :] = np.array([256/256, 255/256, 236/256, 1]) - cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0]) - z_grid = np.arange(det.nBlades*32) - I_yz, bins_y, bins_z = np.histogram2d(data_e[:,3], data_e[:,2], bins = (self.y_grid(), z_grid)) - if arg == 'log': - vmin = 0 - vmax = max(1, np.log(np.max(I_yt)+.1)/np.log(10)*1.05) - plt.pcolormesh(bins_y[:],bins_z[:],(np.log(I_yz+6e-1)/np.log(10)).T, cmap=cmap, vmin=vmin, vmax=vmax) - else: - plt.pcolormesh(bins_y[:],bins_z[:],I_yz.T, cmap=cmap) - plt.xlabel('$y ~/~ \\mathrm{bins}$') - plt.ylabel('$z ~/~ \\mathrm{bins}$') - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.colorbar() - plt.savefig(output, format='png', dpi=150) - - def Ilt(self, fileNumber, arg, data_e) : - cmap = mpl.cm.gnuplot(np.arange(256)) - cmap[:1, :] = np.array([256/256, 255/256, 236/256, 1]) - cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0]) - I_lt, bins_l, bins_t = np.histogram2d(data_e[:,6], data_e[:,7], bins = (self.lamda_grid(), self.theta_grid())) - if arg == 'log': - vmax = max(1, np.log(np.max(I_lt)+.1)/np.log(10)*1.05 ) - plt.pcolormesh(bins_l, bins_t, (np.log(I_lt+I_lt[I_lt>0].min()/2)/np.log(10.)).T, cmap=cmap, vmin=0, vmax=vmax) - else : - vmax = max(np.max(I_lt), 5) - plt.pcolormesh(bins_l, bins_t, I_lt.T, cmap=cmap, vmin=0, vmax=vmax) - plt.xlim(0,) - #if np.min(bins_t) > 0.01 : - # plt.ylim(bottom=0) - #else: - # plt.ylim(bottom=np.min(bins_t)) - #if np.max(bins_t) < -0.01: - # plt.ylim(top=0) - #else: - # plt.ylim(top=np.max(bins_t)) - plt.xlim(lamdaMin, lamdaMax) - plt.ylim(thetaMin, thetaMax) - plt.xlabel('$\\lambda ~/~ \\mathrm{\\AA}$') - plt.ylabel('$\\theta ~/~ \\mathrm{deg}$') - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.colorbar() - plt.savefig(output, format='png', dpi=150) - - def Itz(self, fileNumber, arg, data_e): - det = Detector() - cmap = mpl.cm.gnuplot(np.arange(256)) - cmap[:1, :] = np.array([256/256, 255/256, 236/256, 1]) - cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0]) - time_grid = np.arange(0, tau, 0.0005) - z_grid = np.arange(det.nBlades*32+1) - - I_tz, bins_t, bins_z = np.histogram2d(data_e[:,0], data_e[:,2], bins = (time_grid, z_grid)) - if arg == 'log': - vmax = max(2., np.log(np.max(I_tz)+.1)/np.log(10)*1.05 ) - plt.pcolormesh(bins_t, bins_z, (np.log(I_tz+5.e-1)/np.log(10.)).T, cmap=cmap, vmin=0, vmax=vmax) - else : - vmax = max(np.max(I_tz), 5) - plt.pcolormesh(bins_t, bins_z, I_tz.T, cmap=cmap, vmin=0, vmax=vmax) - if True: - plt.xlim(0,) - plt.ylim(0,) - plt.xlabel('$t ~/~ \\mathrm{s}$') - plt.ylabel('$z$ pixel row') - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.colorbar() - plt.savefig(output, format='png', dpi=150) - - def Iq(self, fileNumber, arg, data_e): - I_q, bins_q = np.histogram(data_e[:,8], bins = self.q_grid()) - err_q = np.sqrt(I_q+1) - q_lim = 4*np.pi*np.array([ max( np.sin(self.theta_grid()[0]*np.pi/180.)/self.lamda_grid()[-1] , 1e-4 ), - min( np.sin(self.theta_grid()[-1]*np.pi/180.)/self.lamda_grid()[0] , 0.03 )]) - if arg == 'log': - low_q = np.where(I_q-err_q>0, I_q-err_q, 0.1) - plt.fill_between(bins_q[:-1], np.log(low_q)/np.log(10), np.log(I_q+err_q/2)/np.log(10), color='lightgrey') - plt.plot(bins_q[:-1], np.log(I_q+5e-1)/np.log(10), color='blue', linewidth=0.5) - lw = I_q[ ((q_lim[0] < bins_q[:-1]) & (bins_q[:-1] < q_lim[1])) ].min() - plt.ylim(max(-0.1, np.log(lw+.1)/np.log(10)-0.1), ) - else: - plt.plot(bins_q[:-1], I_q, color='blue', linewidth=0.5) - plt.ylabel('$\\log_{10}(\\mathrm{cnts})$') - plt.xlabel('$q_z ~/~ \\mathrm{\\AA}^{-1}$') - plt.xlim(q_lim) - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.savefig(output, format='png', dpi=150) - - def Il(self, fileNumber, arg, data_e): - I_l, bins_l = np.histogram(data_e[:,6], bins = self.lamda_grid()) - if arg == 'lin': - plt.plot(bins_l[:-1], I_l) - plt.ylabel('$I ~/~ \\mathrm{cnts}$') - else: - plt.plot(bins_l[:-1], np.log(I_l+5.e-1)/np.log(10.)) - plt.ylabel('$\\log_{10} I ~/~ \\mathrm{cnts}$') - plt.xlabel('$\\lambda ~/~ \\mathrm{\\AA}$') - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.savefig(output, format='png', dpi=150) - - def It(self, fileNumber, arg, data_e): - I_t, bins_t = np.histogram(data_e[:,7], bins = self.theta_grid()) - plt.plot( I_t, bins_t[:-1]) - plt.xlabel('$\\mathrm{cnts}$') - plt.ylabel('$\\theta ~/~ \\mathrm{deg}$') - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.savefig(output, format='png', dpi=150) - - def tof(self, fileNumber, arg, data_e): - time_grid = np.arange(0, 1.3*tau, 0.0005) - I_t, bins_t = np.histogram(data_e[:,0], bins = time_grid) - if arg == 'lin': - plt.plot(bins_t[:-1]+tau, I_t) - plt.plot(bins_t[:-1], I_t) - plt.plot(bins_t[:-1]+2*tau, I_t) - else: - lI_t = np.log(I_t+5.e-1)/np.log(10.) - plt.plot(bins_t[:-1]+tau, lI_t) - plt.plot(bins_t[:-1], lI_t) - plt.plot(bins_t[:-1]+2*tau, lI_t) - plt.ylabel('log(counts)') - plt.xlabel('time / s') - headline = self.headline(fileNumber, np.shape(data_e)[0]) - plt.title(headline, loc='left', y=1.0, c='r') - plt.savefig(output, format='png') - -#============================================================================== -def process(dataPath, ident, clas): - #================================ - # constants - hdm = 6.626176e-34/1.674928e-27 # h / m - #================================ - # instrument specific parameters - #================================ - global lamdaMin, lamdaMax, qMin, qMax, thetaMin, thetaMax, yMin, yMax - # defaults - lamdaCut = 2.5 # Aa used to reshuffle tof - # data filtering and folding - - #================================ - if clas.lambdaRange: - lamdaMin = clas.lambdaRange[0] - lamdaMax = clas.lambdaRange[1] - else: - lamdaMin = lamdaCut - - chopperPhase = clas.chopperPhase - tofOffset = clas.TOFOffset - thetaMin = clas.thetaRange[0] - thetaMax = clas.thetaRange[1] - yMin = clas.yRange[0] - yMax = clas.yRange[1] - qMin = clas.qRange[0] - qMax = clas.qRange[1] - - #================================ - # find and open input file - global ev - - data_eSum = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0]]) - sumTime = 0 - - number = resolveNumber(dataPath, ident) - fileName, fileNumber = fileNameCreator(dataPath, str(number)) - - if verbous: - logging.info('life_histogrammer processing file ->\033[1m {} \033[0m<-'.format(fileNumber)) - - for i in range(6): - ev = h5py.File(fileName, 'r', swmr=True) - try: - ev['/entry1/Amor/detector/data/event_time_zero'][-1] - break - except (KeyError, IndexError): - ev.close() - if i < 5: - if verbous: - print("no data yet, retrying ({}) ".format(10-2*i), end='\r') - time.sleep(2) - continue - else: - if verbous: - print("# time-out: no longer waiting for data!\a") - return - - # get and process data - meta = Meta(fileName) - - global mu, nu, tau - - if clas.mu < 98.: - mu = clas.mu - else: - mu = meta.mu + clas.muOffset - - if clas.nu < 98.: - nu = clas.nu - else: - nu = meta.nu - - if clas.chopperSpeed: - tau = 30./ clas.chopperSpeed - else: - tau = meta.tau - - try: - chPh - except NameError: - chPh = meta.chPh - spin = meta.spin - - global countingTime, detectorDistance, chopperDetectorDistance - detectorDistance = meta.detectorDistance - chopperDetectorDistance = meta.chopperDetectorDistance - countingTime = meta.countingTime - - if verbous: - logging.info(" mu = {:>4.2f} deg, nu = {:>4.2f} deg".format(mu, nu)) - if spin == 'u': - logging.info(' spin <+|') - elif spin == 'd': - logging.info(' spin <-|') - - try: lamdaMax - except NameError: lamdaMax = lamdaMin + tau * hdm/chopperDetectorDistance * 1e13 - - tofOffset = tau * chopperPhase / 180. # mismatch of chopper pulse and time-zero - tofCut = lamdaCut * chopperDetectorDistance / hdm * 1.e-13 # tof of frame start - - tof_e = np.array(ev['/entry1/Amor/detector/data/event_time_offset'][:], dtype=np.uint64)/1.e9 + tofOffset # tof - - detPixelID_e = np.array(ev['/entry1/Amor/detector/data/event_id'][:], dtype=np.uint64) # pixel index - - dataPacket_p = np.array(ev['/entry1/Amor/detector/data/event_index'][:], dtype=np.uint64) # data packet index - - tof_e = np.where(tof_etau+tofCut, tof_e-tau, tof_e) - - data_e = analyse_ev(detPixelID_e, tof_e, yMin, yMax, thetaMin, thetaMax) - - ev.close() - - data_eSum = np.append(data_eSum, data_e, axis=0) - sumTime += countingTime - - if verbous: - logging.info(" total counts = {} in {:6.1f} s".format(np.shape(data_e)[0], sumTime)) - - #================================ - # plotting data - plotType = clas.plot[0] - try: - arg = clas.plot[1] - except IndexError: - arg = 'def' - plott = PlotSelection() - try: - plotFunction = getattr(plott, plotType) - plotFunction(fileNumber, arg, data_e) - plt.close() - except Exception as e: - logging.error(f"ERROR: '{plotType}' is no known output format!") - logging.error(f" original error: {e}") - -#============================================================================== -def commandLineArgs(): - msg = "events2histogram reads the eventstream from an hdf raw file and \ - creates various histogrammed outputs or plots." - clas = argparse.ArgumentParser(description = msg) - - clas.add_argument("-c", "--chopperSpeed", - type=float, - help ="chopper speed in rpm") - clas.add_argument("-d", "--dataPath", - help ="relative path to directory with .hdf files") - clas.add_argument("-f", "--fileIdent", - default='0', - help ="file number or offset (if negative)") - clas.add_argument("-l", "--lambdaRange", - nargs=2, - type=float, - help ="wavelength range to be used") - clas.add_argument("-M", "--muOffset", - default=0., - type=float, - help ="mu offset") - clas.add_argument("-m", "--mu", - default=99., - type=float, - help ="value of mu") - clas.add_argument("-n", "--nu", - default=99., - type=float, - help ="value of nu") - clas.add_argument("-P", "--chopperPhase", - default=-5., - type=float, - help ="chopper phase offset") - clas.add_argument("-p", "--plot", - default=['all', 'def'], - nargs='+', - help ="select what to plot or write") - clas.add_argument("-q", "--qRange", - default=[0.005, 0.30], - nargs=2, - type=float, - help ="q_z range") - clas.add_argument("-T", "--TOFOffset", - default=0.0, - type=float, - help ="TOF zero offset") - clas.add_argument("-t", "--thetaRange", - default=[-12., 12.], - nargs=2, - type=float, - help ="theta range to be used") - clas.add_argument("-Y", "--year", - default = str(datetime.today()).split('-')[0], - help = "year, the measurement was performed") - clas.add_argument("-y", "--yRange", - default=[0, 63], - nargs=2, - type=int, - help ="detector y range to be used") - - return clas.parse_args() - -#============================================================================== -def get_dataPath(clas): - if clas.dataPath: - dataPath = clas.dataPath + '/' - if not os.path.exists(dataPath): - sys.exit('# *** the directory "'+dataPath+'" does not exist ***') - elif os.path.exists('./raw'): - dataPath = "./raw/" - elif os.path.exists('../raw'): - dataPath = "../raw/" - else: - sys.exit('# *** please provide the path to the .hdf data files (-d , default is "./raw")') - - return dataPath - -#============================================================================== -def get_directDataPath(clas): - #dataPath = clas.dataPath + '/' - year = str(datetime.today()).split('-')[0] - year_date = str(datetime.today()).split(' ')[0].replace("-", "/", 1) - pNr = str(subprocess.getoutput('/usr/bin/grep "proposal\t" /home/amor/nicosdata/amor/cache/nicos-exp/'+year_date)[-1]).split('\'')[1] - dataPath = '/home/amor/nicosdata/amor/data/'+year+'/'+pNr+'/' - if not os.path.exists(dataPath): - sys.exit('# *** the directory "'+dataPath+'" does not exist ***') - - return dataPath - -#============================================================================== -def main(): - global verbous, output, dataPath - - clas = commandLineArgs() - - dataPath = get_dataPath(clas) - logging.basicConfig(level=logging.INFO, format='# %(message)s') - verbous = True - output = 'life_plot.png' - process(dataPath, clas.fileIdent, clas) - -#============================================================================== -#============================================================================== -if __name__ == "__main__": - main() - -