├── Analysis ├── .gitignore ├── README.md ├── UnpackFromArchive.py ├── DisplayExperimentID.py ├── Comparator.py ├── functions.py ├── DetectWhichImageIsFocusedBest.py └── TarToArchive.py ├── Demonstrator ├── .gitignore ├── detect_lines_in_checkerboard.py ├── darkimages.py ├── lineprofiler.py └── undistort_images.py ├── DetectorMockup ├── .gitignore ├── README.md ├── ScintillatorBackplates.scad └── OmmatiDiag.scad ├── aptina ├── data │ ├── board_data │ │ ├── MI_1002.cfg │ │ ├── MI_100B.cfg │ │ ├── Demo3_5551.cdat │ │ ├── HDMI_DemoLC.cdat │ │ ├── HDMI_DemoLCX.cdat │ │ ├── Demo2_FPGA_A3.cdat │ │ ├── Demo2_FPGA_B0.cdat │ │ ├── Demo2_FPGA_B2.cdat │ │ ├── Demo2_FPGA_B3.cdat │ │ ├── Demo2_FPGA_B4.cdat │ │ ├── Demo2_FPGA_B5.cdat │ │ ├── Demo2x_FPGA_C0.cdat │ │ ├── Demo2x_FPGA_C1.cdat │ │ ├── HDMI_Demo_00CE.cdat │ │ ├── HDMI_Demo_C0CE.cdat │ │ ├── HSSA_Deser_CC.cdat │ │ ├── HSSA_Deser_CD.cdat │ │ ├── MIDES_FB_FPGA_A0.cdat │ │ ├── MIDES_FB_FPGA_A1.cdat │ │ ├── CameraLinkDemo_5551.cdat │ │ ├── MI_1007.cfg │ │ ├── MI_100D.cfg │ │ ├── MI_100E.cfg │ │ ├── BigDog_SMFPGA_A.cdat │ │ └── DualMipiFPGA.cdat │ └── sensor_data │ │ └── sensor_desc.xsl ├── README.md └── NoiseVsExposure.py ├── SyncToAFS.cmd ├── Info ├── triggering.png ├── OTF.md └── triggering.md ├── elphel ├── ftp_files_to_camera.sh ├── elphel.py ├── gamma.php ├── GPIO_input.py ├── setexposure.php ├── GPIO_output.py └── globaldiagnostix.php ├── .gitignore ├── .gitconfig ├── GOTTHARD ├── atte_si2.dat ├── Si_Attenuation.dat └── Photon-Calculation.py ├── PlotBallLenses.py ├── NumericalAperture.py ├── Spectra ├── Xray-Spectrum_040kV.txt ├── Xray-Spectrum_046kV.txt ├── Xray-Spectrum_053kV.txt ├── Xray-Spectrum_060kV.txt ├── Xray-Spectrum_070kV.txt ├── Xray-Spectrum_080kV.txt ├── Xray-Spectrum_090kV.txt ├── Xray-Spectrum_100kV.txt └── Xray-Spectrum_120kV.txt ├── RunCalculate.sh ├── AbsorptionCoefficients.py ├── README.md ├── LICENSE ├── readAptinaRAW.py ├── nist-attenuation-scraper.py ├── nist ├── water.dat ├── a150.dat ├── adipose.dat ├── lung.dat ├── muscle.dat ├── bone.dat ├── tissue.dat ├── blood.dat ├── gadolinium.dat └── cesium.dat ├── randomMTF.py ├── DepthOfFocus.py ├── tiscamera └── setup.md ├── PlotXraySpectra.py ├── Switch.py ├── SurfaceEntranceDoseCalculator.py ├── .screenrc ├── SurfaceEntranceDose.py ├── GenerateTestImage.py ├── FocusFOVViewer.py ├── FocusPlotLine.py ├── MTF.py ├── SetupPi.md ├── MTF_reader_and_plotter.py └── AngularOpening.py /Analysis/.gitignore: -------------------------------------------------------------------------------- 1 | *.mp4 2 | -------------------------------------------------------------------------------- /Demonstrator/.gitignore: -------------------------------------------------------------------------------- 1 | *.jpg 2 | -------------------------------------------------------------------------------- /DetectorMockup/.gitignore: -------------------------------------------------------------------------------- 1 | dimlines.scad 2 | TextGenerator.scad 3 | -------------------------------------------------------------------------------- /aptina/data/board_data/MI_1002.cfg: -------------------------------------------------------------------------------- 1 | CHIP_FILE = "BigDog_SMFPGA_*.cdat" 2 | -------------------------------------------------------------------------------- /aptina/data/board_data/MI_100B.cfg: -------------------------------------------------------------------------------- 1 | CHIP_FILE = "MIDES_FB_FPGA_*.cdat" 2 | -------------------------------------------------------------------------------- /SyncToAFS.cmd: -------------------------------------------------------------------------------- 1 | rsync -avr * haberthuer@slslc:/afs/psi.ch/project/EssentialMed/Dev/ 2 | -------------------------------------------------------------------------------- /Info/triggering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/Info/triggering.png -------------------------------------------------------------------------------- /aptina/data/board_data/Demo3_5551.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo3_5551.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/HDMI_DemoLC.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/HDMI_DemoLC.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/HDMI_DemoLCX.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/HDMI_DemoLCX.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2_FPGA_A3.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2_FPGA_A3.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2_FPGA_B0.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2_FPGA_B0.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2_FPGA_B2.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2_FPGA_B2.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2_FPGA_B3.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2_FPGA_B3.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2_FPGA_B4.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2_FPGA_B4.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2_FPGA_B5.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2_FPGA_B5.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2x_FPGA_C0.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2x_FPGA_C0.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/Demo2x_FPGA_C1.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/Demo2x_FPGA_C1.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/HDMI_Demo_00CE.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/HDMI_Demo_00CE.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/HDMI_Demo_C0CE.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/HDMI_Demo_C0CE.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/HSSA_Deser_CC.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/HSSA_Deser_CC.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/HSSA_Deser_CD.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/HSSA_Deser_CD.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/MIDES_FB_FPGA_A0.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/MIDES_FB_FPGA_A0.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/MIDES_FB_FPGA_A1.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/MIDES_FB_FPGA_A1.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/CameraLinkDemo_5551.cdat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/habi/GlobalDiagnostiX/HEAD/aptina/data/board_data/CameraLinkDemo_5551.cdat -------------------------------------------------------------------------------- /aptina/data/board_data/MI_1007.cfg: -------------------------------------------------------------------------------- 1 | CHIP_FILE = "Demo2_FPGA_*.cdat" 2 | CHIP_FILE = "DualMipiFPGA*.cdat" 3 | CHIP_FILE = "HSSA_Deser_*.cdat" 4 | CHIP_FILE = "HDMI_Demo*.cdat" 5 | CHIP_FILE = "SOC1040_3D_BASEBOARD_*.cdat" 6 | -------------------------------------------------------------------------------- /aptina/data/board_data/MI_100D.cfg: -------------------------------------------------------------------------------- 1 | CHIP_FILE = "Demo2x_FPGA_*.cdat" 2 | CHIP_FILE = "HSSA_Deser_*.cdat" 3 | CHIP_FILE = "DualMipiFPGA*.cdat" 4 | CHIP_FILE = "HDMI_Demo*.cdat" 5 | CHIP_FILE = "SOC1040_3D_BASEBOARD_*.cdat" 6 | -------------------------------------------------------------------------------- /elphel/ftp_files_to_camera.sh: -------------------------------------------------------------------------------- 1 | # look for all PHP files in current directory 2 | # for each of those, generate an FTP command to transfer them to the camera 3 | for i in `ls *.php` 4 | do curl -T $i ftp://192.168.0.9/var/html/ --user root:pass 5 | done -------------------------------------------------------------------------------- /aptina/data/board_data/MI_100E.cfg: -------------------------------------------------------------------------------- 1 | CHIP_FILE = "Demo3*.cdat" 2 | CHIP_FILE = "HSSA_Deser_*.cdat" 3 | CHIP_FILE = "DualMipiFPGA*.cdat" 4 | CHIP_FILE = "HDMI_Demo*.cdat" 5 | CHIP_FILE = "CameraLinkDemo*.cdat" 6 | CHIP_FILE = "SOC1040_3D_BASEBOARD_*.cdat" 7 | CHIP_FILE = "AB1800_FPGA*.cdat" 8 | -------------------------------------------------------------------------------- /DetectorMockup/README.md: -------------------------------------------------------------------------------- 1 | This folder contains the [OpenSCAD](http://www.openscad.org/) files for the GlobalDiagnostiX detector. 2 | 3 | All *.scad files are the "originals", any *.stl files are exported from those and can be viewed on GitHub. 4 | 5 | Use [this configuration](http://www.thingiverse.com/thing:263620) for easily editing the .scad files in [Geany](http://geany.org/). 6 | -------------------------------------------------------------------------------- /Analysis/README.md: -------------------------------------------------------------------------------- 1 | This folder of the GlobalDiagnostiX repository contains Python scripts to work with the data acquired during the Master Thesis of a student of the Bern University of Applied Sciences (MAS MedTec). 2 | 3 | He was taking radiographies (in total more than 4000) of different combinations of Scintillators, CMOS sensors and lenses. 4 | The scripts deal with processing, archiving and visualizing the data. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ImageJ-Elphel/ 2 | *.pdf 3 | *.png 4 | *.pyc 5 | matplotlib2tikz* 6 | libtiff* 7 | Images 8 | ImageJ-Elphel 9 | elphel/snapfull.php 10 | elphel/parsedit.php 11 | elphel/setparameters*.php 12 | tiscamera/gige/ 13 | tiscamera/gst/ 14 | tiscamera/tools/ 15 | tiscamera/v4l2/ 16 | snapshot_???.jpg 17 | .__a* 18 | ._* 19 | Desktop/* 20 | ImageJ/* 21 | Config 22 | .DS_Store 23 | geany_run_script.sh 24 | test.py 25 | .idea 26 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | name = David Haberthür 3 | email = email@davidhaberthuer.ch 4 | [core] 5 | editor = nano 6 | [difftool] 7 | prompt = false 8 | [alias] 9 | dt = difftool 10 | l = log --graph --pretty=format:'%C(red)%h%Creset%C(blue)%d%Creset %C(white)%s%Creset %C(white dim)(by %an %ar)%Creset' 11 | ll = !git l --all 12 | [color] 13 | ui=auto 14 | [credential] 15 | helper = cache --timeout=3600 16 | [branch "master"] 17 | mergeoptions = --no-ff 18 | -------------------------------------------------------------------------------- /GOTTHARD/atte_si2.dat: -------------------------------------------------------------------------------- 1 | 1.00000E-03 1.570E+03 2 | 1.50000E-03 5.355E+02 3 | 1.83890E-03 3.092E+02 4 | 1.83890E-03 3.192E+03 5 | 2.00000E-03 2.777E+03 6 | 3.00000E-03 9.784E+02 7 | 4.00000E-03 4.529E+02 8 | 5.00000E-03 2.450E+02 9 | 6.00000E-03 1.470E+02 10 | 8.00000E-03 6.468E+01 11 | 1.00000E-02 3.389E+01 12 | 1.50000E-02 1.034E+01 13 | 2.00000E-02 4.464E+00 14 | 3.00000E-02 1.436E+00 15 | 4.00000E-02 7.012E-01 16 | 5.00000E-02 4.385E-01 17 | 6.00000E-02 3.207E-01 18 | 8.00000E-02 2.228E-01 19 | 1.00000E-01 1.835E-01 20 | -------------------------------------------------------------------------------- /PlotBallLenses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Plot ball lenses 5 | """ 6 | 7 | import matplotlib.pylab as plt 8 | import numpy 9 | 10 | Dia = numpy.arange(0, 15, 0.2) 11 | NA = (0.918919 * (-1.0 + Dia)) / Dia 12 | FNo = (0.544118 * Dia) / (-1.0 + Dia) 13 | 14 | plt.plot(Dia, NA, 'r', label='NA') 15 | plt.plot(Dia, FNo, 'g', label='FNo') 16 | plt.legend(loc='best') 17 | plt.xlim([1.5, 10]) 18 | plt.ylim([0.3, 1.2]) 19 | 20 | for i in (2, 8): 21 | plt.axvline(i, color='k') 22 | if i > 3: 23 | plt.axhline(NA[numpy.where(Dia == i)], color='k') 24 | plt.axhline(FNo[numpy.where(Dia == i)], color='k') 25 | 26 | plt.show() 27 | -------------------------------------------------------------------------------- /NumericalAperture.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import matplotlib.pylab as plt 4 | import numpy 5 | from scipy import integrate 6 | 7 | lpa = 400.0 8 | hpa = 300.0 9 | pxs = 0.194 10 | maxres = 1.3 11 | epx = 1 / (2 * maxres) 12 | 13 | tsl = 1.0 14 | nxp = 1.0 15 | NAc = pxs / (tsl / 2) 16 | NAa = integrate.quad(lambda x: numpy.arctan(pxs / (2 * x)), 0.01, 1)[0] 17 | print 'middle NA:', NAc 18 | print 'average NA:', NAa 19 | 20 | pcs = 0.006944 21 | mag = 36 / lpa 22 | b = 50.0 23 | g = b / mag 24 | 25 | FStop = 1.4 26 | NAdet = b / (FStop * 2 * g) 27 | 28 | mag = numpy.arange(0, 1.01, 0.01) 29 | 30 | legend = [] 31 | plt.figure() 32 | for FStop in [0.5, 0.8, 1, 1.25, 1.4, 2]: 33 | plt.plot(mag, mag / (2 * FStop * (1 + mag))) 34 | legend.append(FStop) 35 | plt.legend(legend) 36 | plt.hlines(NAa, 0, 1) 37 | plt.xlabel('Magnification') 38 | plt.ylabel('NA') 39 | plt.show() 40 | -------------------------------------------------------------------------------- /Info/OTF.md: -------------------------------------------------------------------------------- 1 | # OTF/MTF calculation for the Elphel Camera 2 | 3 | This does not work on the Raspberry Pi (yet), since I wasn't able to compile the ImageJ plugins from Elphel. 4 | 5 | # On a powerful machine 6 | * get the Elphel ImageJ plugins by issuing `git clone git://elphel.git.sourceforge.net/gitroot/elphel/ImageJ-Elphel ImageJ-Elphel` (maybe add it later to your `.gitignore`) 7 | * Follow the directions on the [Elphel Wiki](http://wiki.elphel.com/index.php?title=Measure_OTF_of_the_lens-sensor_system#B_-_generate) to generate a test pattern. 8 | * Take an image with the stated camera settings (Set them with [this link here](http://192.168.0.9/parsedit.php?embed=0.1&title=OTF+Settings>AB_R>AB_G>AB_GB>AB_B&QUALITY) (use gamma.php (also in the repo to set Gamma to 1.0 and black to 0.0). 9 | * Analyze the image according to the [guidelines given by Elphel]http://wiki.elphel.com/index.php?title=Measure_OTF_of_the_lens-sensor_system#ImageJ) 10 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_040kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 40 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 30.209266 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | 8 0 12 | 9 2.23512e-015 13 | 10 2.18948e-009 14 | 11 4.31922e-006 15 | 12 0.00232188 16 | 13 0.300628 17 | 14 14.5561 18 | 15 298.809 19 | 16 1906.89 20 | 17 8932.95 21 | 18 30532.1 22 | 19 88800 23 | 20 205627 24 | 21 353152 25 | 22 539114 26 | 23 791482 27 | 24 1.06265e+006 28 | 25 1.35991e+006 29 | 26 1.64083e+006 30 | 27 1.9089e+006 31 | 28 2.21045e+006 32 | 29 2.49449e+006 33 | 30 2.78835e+006 34 | 31 2.78741e+006 35 | 32 2.67985e+006 36 | 33 2.50978e+006 37 | 34 2.246e+006 38 | 35 1.88899e+006 39 | 36 1.43309e+006 40 | 37 878742 41 | 38 519053 42 | 39 197429 43 | 40 59656.5 44 | 41 0 45 | 46 | -------------------------------------------------------------------------------- /RunCalculate.sh: -------------------------------------------------------------------------------- 1 | # kill all runnig fiji jobs 2 | killall fiji-linux; 3 | rm -r Config 4 | # remove all previously calculated images and logfiles 5 | rm /afs/psi.ch/project/EssentialMed/Dev/DetectorConfiguration/*.png 6 | rm /afs/psi.ch/project/EssentialMed/Dev/DetectorConfiguration/*.txt 7 | # calculate some stuff 8 | for s in {3..43..5}; # Field of View/ScreenSize 9 | do echo FOV $s; 10 | for c in {1..10..2}; # Sensor Size 11 | do echo SensorSize $c; 12 | for l in {2..5..1}; # lp/mm 13 | do echo lp_mm $l; 14 | for o in 90; # Opening Angle 15 | do echo OpeningAngle $o; 16 | for n in 1; # NA 17 | do echo NA $n 18 | for f in 1; # FStop 19 | do echo FStop $f 20 | for e in 50; # Energy 21 | do echo Energy $e; 22 | python CalculateDetector.py -s $s -o $o -n $n -f $f -c $c -e $e -l $l -p > /dev/null; 23 | done; 24 | done; 25 | done; 26 | done; 27 | done; 28 | done; 29 | done 30 | # open fiji 31 | /home/scratch/Apps/Fiji.app/fiji-linux -eval 'run("Image Sequence...", "open=/afs/psi.ch/project/EssentialMed/Dev/Config starting=1 increment=1 scale=100 file=png or=[] sort");' & # start fiji 32 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_046kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 46 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 33.338509 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | 8 0 12 | 9 1.98718e-015 13 | 10 1.56485e-009 14 | 11 2.66078e-006 15 | 12 0.00130587 16 | 13 0.16097 17 | 14 8.00576 18 | 15 164.484 19 | 16 1090.91 20 | 17 5210.25 21 | 18 17936.2 22 | 19 52353.3 23 | 20 123040 24 | 21 214001 25 | 22 335073 26 | 23 504194 27 | 24 700618 28 | 25 931312 29 | 26 1.17712e+006 30 | 27 1.44421e+006 31 | 28 1.70858e+006 32 | 29 1.97753e+006 33 | 30 2.24324e+006 34 | 31 2.34505e+006 35 | 32 2.38215e+006 36 | 33 2.39185e+006 37 | 34 2.36321e+006 38 | 35 2.29126e+006 39 | 36 2.16771e+006 40 | 37 1.99989e+006 41 | 38 1.88095e+006 42 | 39 1.73532e+006 43 | 40 1.64187e+006 44 | 41 1.38155e+006 45 | 42 1.07637e+006 46 | 43 777432 47 | 44 430085 48 | 45 103190 49 | 46 0 50 | 47 0 51 | 52 | -------------------------------------------------------------------------------- /AbsorptionCoefficients.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | """ 4 | Script to plot some absorption coefficients from NIST. 5 | The absorption coefficient data was downloaded as ASCII format table from 6 | http://physics.nist.gov/PhysRefData/XrayMassCoef/tab4.html as "material.dat" 7 | for some materials. 8 | """ 9 | 10 | import os 11 | import glob 12 | import matplotlib.pylab as plt 13 | import numpy as np 14 | 15 | BaseDir = os.path.join(os.getcwd(), 'nist', '*') 16 | print 'Found', len(glob.glob(BaseDir)), 'files with data from NIST' 17 | for item in glob.glob(BaseDir): 18 | print 'loading', os.path.basename(item) 19 | # Skip lines in which there's more than the info we need (K, L, M, etc) 20 | # http://stackoverflow.com/a/17151323 21 | with open(item) as f: 22 | lines = (line for line in f if len(line.split()) < 4) 23 | Data = np.loadtxt(lines) 24 | plt.loglog(Data[:, 0], Data[:, 1], 25 | label=os.path.splitext(os.path.basename(item))[0]) 26 | 27 | plt.rc('text', usetex=True) 28 | plt.title(r"$\mu/\rho$ [$\textrm{cm}^{2}$/g]") 29 | plt.legend() 30 | plt.show() 31 | -------------------------------------------------------------------------------- /Info/triggering.md: -------------------------------------------------------------------------------- 1 | Triggering the Elphel Camera 2 | ============================ 3 | 4 | Details are on the [Elphel wiki page on triggering](http://wiki.elphel.com/index.php?title=Trigger), nonetheless here are some notes. 5 | 6 | The TRIG_PERIOD is in 32 bit or 96 kHz, meaning that if you set the value to 96000000, this equals to one trigger pulse per second, 48000000 to two pulses per second, obviously. 7 | 8 | To see something on the attached LED (or detect a pulse on an attached cable), you have to se the TRIG_OUT to 800000, and set the TRIG_BITLENGTH sufficiently hight, e.g above 128. If you set TRIG_BITLENGTH to low values, you barely see something on the LED, while setting it to 255 gives a satisfying blink on the LED. 9 | 10 | You'll get the settings below (external trigger, two per second, with a blinking LED) if you click on the [link here](http://192.168.0.9/parsedit.php?embed=0.1&title=External+trigger+controls&TRIG=4&TRIG_PERIOD=96000000&&TRIG_BITLENGTH=255&TRIG_OUT=0x800000). This calls all necessary parameters. Set them with pressing "Apply". 11 | 12 | ![Trigger Settings](triggering.png) 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Development Repository for OmmatiDiag 2 | 3 | [![Code Health](https://landscape.io/github/habi/GlobalDiagnostiX/master/landscape.svg)](https://landscape.io/github/habi/GlobalDiagnostiX/master) 4 | 5 | This repository tracks (mostly Python) code written while working on OmmatiDiag, the affordable, reliable and standard-compliant detector for the [GlobalDiagnostiX][GDX]-system. 6 | 7 | [GDX]: http://globaldiagnostix.org 8 | 9 | This repository follow the [PEP 8 -- Style Guide for Python Code][pep8] and the [Git branching model described by Vincent Driessen][branching] as closely as possible. 10 | 11 | [pep8]: http://www.python.org/dev/peps/pep-0008/ 12 | [branching]: http://nvie.com/posts/a-successful-git-branching-model/ 13 | 14 | Since this repository is part of a scientific project and is depending on a particular set of hardware components, it seems silly to make it closed source, the whole thing is covered by [an (un)license](LICENSE). 15 | 16 | [I, the author][I] of those files am very grateful if you notify me of any [issues](issues), submit pull requests and tell me of (better) ideas on how to implement things and any tips! 17 | 18 | [I]: http://davidhaberthuer.ch/ 19 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_053kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 53 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 36.445385 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 1.71595e-015 14 | 10 1.1049e-009 15 | 11 1.71807e-006 16 | 12 0.000809718 17 | 13 0.09614 18 | 14 4.90176 19 | 15 100.745 20 | 16 695.258 21 | 17 3383.67 22 | 18 11694.6 23 | 19 34180.5 24 | 20 81393 25 | 21 143090 26 | 22 228396 27 | 23 349846 28 | 24 497879 29 | 25 678418 30 | 26 881593 31 | 27 1.11381e+006 32 | 28 1.33682e+006 33 | 29 1.57228e+006 34 | 30 1.80097e+006 35 | 31 1.92491e+006 36 | 32 2.00847e+006 37 | 33 2.07976e+006 38 | 34 2.13157e+006 39 | 35 2.16079e+006 40 | 36 2.16104e+006 41 | 37 2.13919e+006 42 | 38 2.11895e+006 43 | 39 2.0783e+006 44 | 40 2.05376e+006 45 | 41 1.94523e+006 46 | 42 1.77942e+006 47 | 43 1.61255e+006 48 | 44 1.47703e+006 49 | 45 1.32117e+006 50 | 46 1.11423e+006 51 | 47 895933 52 | 48 742433 53 | 49 575168 54 | 50 464937 55 | 51 269209 56 | 52 101830 57 | 53 2665.74 58 | 54 0 59 | 60 | -------------------------------------------------------------------------------- /elphel/elphel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Quickly grab images from the Elphel camera we have on loan 5 | """ 6 | 7 | from optparse import OptionParser 8 | import os 9 | import urllib 10 | import time 11 | 12 | parser = OptionParser() 13 | usage = 'usage: % prog [options] arg' 14 | 15 | parser.add_option('-i', dest='Images', help='how many images should I save?', 16 | metavar='1234', type=int) 17 | (options, args) = parser.parse_args() 18 | 19 | # Make a subdirectory to the current directory we're in 20 | try: 21 | os.mkdir(os.path.join(os.getcwd(), 'Elphel')) 22 | except OSError: 23 | print 'Elphel-directory already exists' 24 | SaveDir = os.path.join(os.getcwd(), 'Elphel', str(time.time())) 25 | os.mkdir(SaveDir) 26 | 27 | # get options.Images number of images as fast as possible from the camera 28 | for i in range(options.Images): 29 | print 'writing image', i, '/', len(range(options.Images)) 30 | # get the url of the camera which spit out an image 31 | # save the image to 'SaveDir', with an unique name based on the current 32 | # time 33 | urllib.urlretrieve("http://192.168.0.9:8081/wait/img", 34 | os.path.join(SaveDir, str(time.time()) + '.jpg')) 35 | 36 | print 'saved to', SaveDir 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 4 | software, either in source code form or as a compiled binary, for any purpose, 5 | commercial or non-commercial, and by any means. 6 | 7 | In jurisdictions that recognize copyright laws, the author or authors of this 8 | software dedicate any and all copyright interest in the software to the public 9 | domain. We make this dedication for the benefit of the public at large and to 10 | the detriment of our heirs and successors. We intend this dedication to be an 11 | overt act of relinquishment in perpetuity of all present and future rights to 12 | this software under copyright law. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | For more information, please refer to 22 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_060kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 60 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 39.416324 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 1.50707e-015 14 | 10 8.10424e-010 15 | 11 1.19912e-006 16 | 12 0.000563206 17 | 13 0.0653169 18 | 14 3.39192 19 | 15 69.6778 20 | 16 497.807 21 | 17 2460.48 22 | 18 8513.24 23 | 19 24869.9 24 | 20 59820.6 25 | 21 105995 26 | 22 171370 27 | 23 265504 28 | 24 383507 29 | 25 530369 30 | 26 699915 31 | 27 898154 32 | 28 1.08904e+006 33 | 29 1.29486e+006 34 | 30 1.49358e+006 35 | 31 1.61521e+006 36 | 32 1.71024e+006 37 | 33 1.79963e+006 38 | 34 1.8754e+006 39 | 35 1.93733e+006 40 | 36 1.98119e+006 41 | 37 2.01207e+006 42 | 38 2.03167e+006 43 | 39 2.03595e+006 44 | 40 2.04195e+006 45 | 41 1.99578e+006 46 | 42 1.89892e+006 47 | 43 1.79921e+006 48 | 44 1.74227e+006 49 | 45 1.6747e+006 50 | 46 1.55661e+006 51 | 47 1.43114e+006 52 | 48 1.35262e+006 53 | 49 1.26459e+006 54 | 50 1.18109e+006 55 | 51 1.05286e+006 56 | 52 932065 57 | 53 805356 58 | 54 674131 59 | 55 546897 60 | 56 380495 61 | 57 243354 62 | 58 126191 63 | 59 31397.8 64 | 60 2498.36 65 | 61 0 66 | 67 | -------------------------------------------------------------------------------- /aptina/data/board_data/BigDog_SMFPGA_A.cdat: -------------------------------------------------------------------------------- 1 | [CHIP_DESCRIPTOR] 2 | CHIPNAME = "BigDog FPGA" 3 | SERIAL_BASE_ADDRESS = 0x6a 4 | SERIAL_DATA_SIZE = 16 5 | [END] 6 | 7 | //REGDEF = {ADDR, TYPE, MASK, RW, DEFAULT, DESC, DETAIL} 8 | // {BITDEF, MASK, RW, DESC, DETAIL} 9 | // {BITDEF, MASK, RW, DESC, DETAIL} 10 | // ... 11 | // {BITDEF, MASK, RW, DESC, DETAIL} 12 | 13 | [REGISTERS] 14 | CHIP_VERSION_REG = {0x0000, CHIP, 0xFFFF, RO, 0xCAFE, "Frame Buffer_ID", ""} 15 | Column_Count = {0x0001, CHIP, 0xFFFF, RO, 0x0000, "Column Count", ""} 16 | Row_Count = {0x0002, CHIP, 0xFFFF, RO, 0x0000, "Row Count", ""} 17 | Row_Time = {0x0003, CHIP, 0xFFFF, RO, 0x0000, "Row Time", ""} 18 | Frame_Time_Lower = {0x0004, CHIP, 0xFFFF, RO, 0x0000, "LSBs of Frame Time", ""} 19 | Frame_Time_Upper = {0x0005, CHIP, 0xFFFF, RO, 0x0000, "MSBs of Frame Time", ""} 20 | FB_Config = {0x0007, CHIP, 0x000F, RW, 0x0000, "Frame Buffer Config", ""} 21 | {Output_Mode, 0x0003, RW, "0: 8 Bit, 1: 10 Bit, 2: 12 Bit", ""} 22 | {Swizzle, 0x0004, RW, "Swizzle output of Frame Buffer", ""} 23 | {Capture_Posdge, 0x0008, RW, "Capture incoming data on posedge of pixclk", ""} 24 | [END] -------------------------------------------------------------------------------- /readAptinaRAW.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This script reads the RAW files from the Aptina cameras as numpy arrays, 5 | ready for display or further use. 6 | Made to help Valerie Duay get up to speed :) 7 | """ 8 | import os 9 | import numpy 10 | import matplotlib.pyplot as plt 11 | 12 | Directory = '/scratch/tmp/DevWareX/MT9M001/DSL949A-NIR/' 13 | Folder = '1394629994_MT9M001_DSL949A-NIR_0.0_0.0f_040ms_090mm_to150mm' 14 | File = 'MT9M001_1280x1024_DSL949A-NIR_0.0_0.0f_040ms_090mm_to150mm_090mm.raw' 15 | Size = [int(File.split('_')[1].split('x')[1]), 16 | int(File.split('_')[1].split('x')[0])] 17 | 18 | # fromfile 19 | FileToLoad = os.path.join(Directory, Folder, File) 20 | 21 | FromFile = numpy.fromfile(FileToLoad, dtype=numpy.uint16).reshape(Size) 22 | # FromFile -= numpy.mean(FromFile) 23 | 24 | MemMap = numpy.memmap(FileToLoad, dtype=numpy.uint16, shape=(Size[0], Size[1])) 25 | # MemMap -= numpy.mean(MemMap) 26 | 27 | plt.figure(File) 28 | plt.subplot(121) 29 | plt.imshow(FromFile, cmap='gray') 30 | plt.title('numpy.fromfile > leaves file') 31 | plt.subplot(122) 32 | plt.imshow(MemMap, cmap='gray') 33 | plt.title('numpy.memmap > destroys file') 34 | plt.show() 35 | 36 | print 'Only use "numpy.memmap" for displaying files! If you perform some',\ 37 | 'calculations on the files (e.g "File -= numpy.mean(File)") these',\ 38 | 'calculations are immediately saved to disk, essentially destroying the',\ 39 | 'file! In this case use "numpy.fromfile"!' 40 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_070kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 70 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 43.293309 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 1.30561e-015 14 | 10 5.57532e-010 15 | 11 8.00187e-007 16 | 12 0.00038382 17 | 13 0.0436005 18 | 14 2.2998 19 | 15 47.1252 20 | 16 350.865 21 | 17 1764.36 22 | 18 6097.86 23 | 19 17772.4 24 | 20 43204.5 25 | 21 77159.5 26 | 22 126272 27 | 23 197672 28 | 24 289283 29 | 25 405144 30 | 26 541191 31 | 27 702780 32 | 28 861914 33 | 29 1.03683e+006 34 | 30 1.20556e+006 35 | 31 1.31659e+006 36 | 32 1.41223e+006 37 | 33 1.50653e+006 38 | 34 1.58931e+006 39 | 35 1.6637e+006 40 | 36 1.72767e+006 41 | 37 1.78413e+006 42 | 38 1.82699e+006 43 | 39 1.85966e+006 44 | 40 1.88582e+006 45 | 41 1.8769e+006 46 | 42 1.83091e+006 47 | 43 1.78206e+006 48 | 44 1.76541e+006 49 | 45 1.74255e+006 50 | 46 1.68766e+006 51 | 47 1.62805e+006 52 | 48 1.5895e+006 53 | 49 1.54445e+006 54 | 50 1.48764e+006 55 | 51 1.40843e+006 56 | 52 1.33778e+006 57 | 53 1.26313e+006 58 | 54 1.17954e+006 59 | 55 1.09512e+006 60 | 56 1.00002e+006 61 | 57 912985 62 | 58 838323 63 | 59 758760 64 | 60 706220 65 | 61 627661 66 | 62 550930 67 | 63 470241 68 | 64 394452 69 | 65 306910 70 | 66 220188 71 | 67 136545 72 | 68 88767.1 73 | 69 18522.8 74 | 70 0 75 | 71 0 76 | 77 | -------------------------------------------------------------------------------- /GOTTHARD/Si_Attenuation.dat: -------------------------------------------------------------------------------- 1 | # from http://physics.nist.gov/PhysRefData/XrayMassCoef/ElemTab/z14.html 2 | # Energy μ/ρ μen/ρ 3 | # (MeV) (cm2/g) (cm2/g) 4 | 1.00000E-03 1.570E+03 1.567E+03 5 | 1.50000E-03 5.355E+02 5.331E+02 6 | 1.83890E-03 3.092E+02 3.070E+02 7 | 1.83890E-03 3.192E+03 3.059E+03 8 | 2.00000E-03 2.777E+03 2.669E+03 9 | 3.00000E-03 9.784E+02 9.516E+02 10 | 4.00000E-03 4.529E+02 4.427E+02 11 | 5.00000E-03 2.450E+02 2.400E+02 12 | 6.00000E-03 1.470E+02 1.439E+02 13 | 8.00000E-03 6.468E+01 6.313E+01 14 | 1.00000E-02 3.389E+01 3.289E+01 15 | 1.50000E-02 1.034E+01 9.794E+00 16 | 2.00000E-02 4.464E+00 4.076E+00 17 | 3.00000E-02 1.436E+00 1.164E+00 18 | 4.00000E-02 7.012E-01 4.782E-01 19 | 5.00000E-02 4.385E-01 2.430E-01 20 | 6.00000E-02 3.207E-01 1.434E-01 21 | 8.00000E-02 2.228E-01 6.896E-02 22 | 1.00000E-01 1.835E-01 4.513E-02 23 | 1.50000E-01 1.448E-01 3.086E-02 24 | 2.00000E-01 1.275E-01 2.905E-02 25 | 3.00000E-01 1.082E-01 2.932E-02 26 | 4.00000E-01 9.614E-02 2.968E-02 27 | 5.00000E-01 8.748E-02 2.971E-02 28 | 6.00000E-01 8.077E-02 2.951E-02 29 | 8.00000E-01 7.082E-02 2.875E-02 30 | 1.00000E+00 6.361E-02 2.778E-02 31 | 1.25000E+00 5.688E-02 2.652E-02 32 | 1.50000E+00 5.183E-02 2.535E-02 33 | 2.00000E+00 4.480E-02 2.345E-02 34 | 3.00000E+00 3.678E-02 2.101E-02 35 | 4.00000E+00 3.240E-02 1.963E-02 36 | 5.00000E+00 2.967E-02 1.878E-02 37 | 6.00000E+00 2.788E-02 1.827E-02 38 | 8.00000E+00 2.574E-02 1.773E-02 39 | 1.00000E+01 2.462E-02 1.753E-02 40 | 1.50000E+01 2.352E-02 1.746E-02 41 | 2.00000E+01 2.338E-02 1.757E-02 42 | 43 | -------------------------------------------------------------------------------- /nist-attenuation-scraper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Get some data from NIST 5 | """ 6 | 7 | from BeautifulSoup import BeautifulSoup 8 | import urllib2 9 | import matplotlib.pylab as plt 10 | 11 | URL = 'http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/muscle.html' 12 | 13 | response = urllib2.urlopen(URL) 14 | html = response.read() 15 | soup = BeautifulSoup(html) 16 | 17 | # ascii = soup.find('pre') # extract ASCII formatted table 18 | # for line in ascii: 19 | # print len(str(line).split()) 20 | 21 | Energy = [] 22 | Mu = [] 23 | Muen = [] 24 | table = soup.find('table') 25 | for row in table.findAll('tr'): 26 | col = row.findAll('td') 27 | if len(str(col).split()) == 3: 28 | Energy.append(col[0].find(text=True)) 29 | Mu.append(col[1].find(text=True)) 30 | Muen.append(col[2].find(text=True)) 31 | print col[1] 32 | 33 | plt.loglog(Energy, Mu, label='Mu') 34 | plt.loglog(Energy, Muen, label='Muen') 35 | plt.title(soup.title(text=True)) 36 | plt.legend() 37 | plt.show() 38 | 39 | URL = 'http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/bone.html' 40 | response = urllib2.urlopen(URL) 41 | html = response.read() 42 | soup = BeautifulSoup(html) 43 | 44 | Energy = [] 45 | Mu = [] 46 | Muen = [] 47 | table = soup.find('table') 48 | for row in table.findAll('tr'): 49 | col = row.findAll('td') 50 | if len(str(col).split()) == 3: 51 | Energy.append(col[0].find(text=True)) 52 | Mu.append(col[1].find(text=True)) 53 | Muen.append(col[2].find(text=True)) 54 | asdf = col[1](text=True) 55 | print type(unicode.join(u'\n', map(unicode, asdf))) 56 | -------------------------------------------------------------------------------- /nist/water.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/water.html 2 | #Water, Liquid 3 | #_________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #_________________________________ 8 | 9 | 1.00000E-03 4.078E+03 4.065E+03 10 | 1.50000E-03 1.376E+03 1.372E+03 11 | 2.00000E-03 6.173E+02 6.152E+02 12 | 3.00000E-03 1.929E+02 1.917E+02 13 | 4.00000E-03 8.278E+01 8.191E+01 14 | 5.00000E-03 4.258E+01 4.188E+01 15 | 6.00000E-03 2.464E+01 2.405E+01 16 | 8.00000E-03 1.037E+01 9.915E+00 17 | 1.00000E-02 5.329E+00 4.944E+00 18 | 1.50000E-02 1.673E+00 1.374E+00 19 | 2.00000E-02 8.096E-01 5.503E-01 20 | 3.00000E-02 3.756E-01 1.557E-01 21 | 4.00000E-02 2.683E-01 6.947E-02 22 | 5.00000E-02 2.269E-01 4.223E-02 23 | 6.00000E-02 2.059E-01 3.190E-02 24 | 8.00000E-02 1.837E-01 2.597E-02 25 | 1.00000E-01 1.707E-01 2.546E-02 26 | 1.50000E-01 1.505E-01 2.764E-02 27 | 2.00000E-01 1.370E-01 2.967E-02 28 | 3.00000E-01 1.186E-01 3.192E-02 29 | 4.00000E-01 1.061E-01 3.279E-02 30 | 5.00000E-01 9.687E-02 3.299E-02 31 | 6.00000E-01 8.956E-02 3.284E-02 32 | 8.00000E-01 7.865E-02 3.206E-02 33 | 1.00000E+00 7.072E-02 3.103E-02 34 | 1.25000E+00 6.323E-02 2.965E-02 35 | 1.50000E+00 5.754E-02 2.833E-02 36 | 2.00000E+00 4.942E-02 2.608E-02 37 | 3.00000E+00 3.969E-02 2.281E-02 38 | 4.00000E+00 3.403E-02 2.066E-02 39 | 5.00000E+00 3.031E-02 1.915E-02 40 | 6.00000E+00 2.770E-02 1.806E-02 41 | 8.00000E+00 2.429E-02 1.658E-02 42 | 1.00000E+01 2.219E-02 1.566E-02 43 | 1.50000E+01 1.941E-02 1.441E-02 44 | 2.00000E+01 1.813E-02 1.382E-02 45 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_080kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 80 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 47.090489 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 1.15282e-015 14 | 10 4.07933e-010 15 | 11 5.90006e-007 16 | 12 0.000287072 17 | 13 0.0320615 18 | 14 1.69769 19 | 15 34.6186 20 | 16 266.128 21 | 17 1354.86 22 | 18 4671.37 23 | 19 13573.8 24 | 20 33239.6 25 | 21 59667.2 26 | 22 98457.8 27 | 23 155176 28 | 24 228901 29 | 25 322980 30 | 26 434231 31 | 27 567370 32 | 28 702432 33 | 29 852895 34 | 30 998523 35 | 31 1.09741e+006 36 | 32 1.18737e+006 37 | 33 1.27797e+006 38 | 34 1.35748e+006 39 | 35 1.43118e+006 40 | 36 1.49819e+006 41 | 37 1.5601e+006 42 | 38 1.61107e+006 43 | 39 1.65538e+006 44 | 40 1.69029e+006 45 | 41 1.69651e+006 46 | 42 1.67794e+006 47 | 43 1.65719e+006 48 | 44 1.65342e+006 49 | 45 1.64456e+006 50 | 46 1.62203e+006 51 | 47 1.59615e+006 52 | 48 1.57191e+006 53 | 49 1.54281e+006 54 | 50 1.50474e+006 55 | 51 1.45231e+006 56 | 52 1.40578e+006 57 | 53 1.35627e+006 58 | 54 1.30303e+006 59 | 55 1.24793e+006 60 | 56 1.27831e+006 61 | 57 1.31212e+006 62 | 58 1.34017e+006 63 | 59 1.36384e+006 64 | 60 1.16978e+006 65 | 61 957791 66 | 62 889066 67 | 63 817347 68 | 64 767765 69 | 65 711040 70 | 66 694086 71 | 67 678689 72 | 68 610181 73 | 69 516132 74 | 70 433197 75 | 71 351428 76 | 72 307603 77 | 73 256206 78 | 74 212459 79 | 75 174017 80 | 76 129644 81 | 77 79640.5 82 | 78 40233 83 | 79 3443.04 84 | 80 36.8706 85 | 81 0 86 | 87 | -------------------------------------------------------------------------------- /elphel/gamma.php: -------------------------------------------------------------------------------- 1 | "", 12 | "GTAB_G__0816"=>"", 13 | "GTAB_GB__0816"=>"", 14 | "GTAB_B__0816"=>"", 15 | "GTAB_R__0824"=>"", 16 | "GTAB_G__0824"=>"", 17 | "GTAB_GB__0824"=>"", 18 | "GTAB_B__0824"=>"" 19 | ); 20 | // get existing gamma values 21 | $gammas= elphel_get_P_arr($gammas); 22 | } 23 | 24 | foreach($_GET as $key=>$value) { 25 | 26 | if(strtolower(substr($s, 0, 2))=="0x") 27 | $value= hexdec($value); 28 | switch ($key) { 29 | case "gamma": 30 | // set gammas array and postpone setting values in the camera 31 | $value = intval($value); 32 | $gammas["GTAB_R__0816"]=$value; 33 | $gammas["GTAB_G__0816"]=$value; 34 | $gammas["GTAB_GB__0816"]=$value; 35 | $gammas["GTAB_B__0816"]=$value; 36 | break; 37 | case "black": 38 | // set gammas array and postpone setting values in the camera 39 | $value = intval($value); 40 | $gammas["GTAB_R__0824"]=$value; 41 | $gammas["GTAB_G__0824"]=$value; 42 | $gammas["GTAB_GB__0824"]=$value; 43 | $gammas["GTAB_B__0824"]=$value; 44 | break; 45 | } 46 | // If any gamma value was set, set all gamma values in the camera. 47 | // Was postponed earlier 48 | if ($gammas != NULL) { 49 | // values are taken from the green color 50 | elphel_gamma_add($gammas["GTAB_G__0816"]/100, $gammas["GTAB_G__0824"]); 51 | // Apply the values at the frame ahead 52 | elphel_set_P_arr($gammas, elphel_get_frame() + $ahead); 53 | } 54 | // 55 | // Add code to form the output here 56 | } 57 | 58 | ?> 59 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_090kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 90 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 50.417544 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 1.04158e-015 14 | 10 3.25985e-010 15 | 11 4.90597e-007 16 | 12 0.000232997 17 | 13 0.0254407 18 | 14 1.33779 19 | 15 27.0774 20 | 16 212.969 21 | 17 1092.9 22 | 18 3759.25 23 | 19 10892.2 24 | 20 26798.2 25 | 21 48249 26 | 22 80140.6 27 | 23 126959 28 | 24 188264 29 | 25 266933 30 | 26 360215 31 | 27 472296 32 | 28 589508 33 | 29 721420 34 | 30 849850 35 | 31 938550 36 | 32 1.02206e+006 37 | 33 1.1072e+006 38 | 34 1.18153e+006 39 | 35 1.25149e+006 40 | 36 1.31653e+006 41 | 37 1.37769e+006 42 | 38 1.43107e+006 43 | 39 1.48015e+006 44 | 40 1.51892e+006 45 | 41 1.53174e+006 46 | 42 1.52896e+006 47 | 43 1.52469e+006 48 | 44 1.52522e+006 49 | 45 1.52087e+006 50 | 46 1.51583e+006 51 | 47 1.50822e+006 52 | 48 1.48987e+006 53 | 49 1.46767e+006 54 | 50 1.44256e+006 55 | 51 1.406e+006 56 | 52 1.37255e+006 57 | 53 1.33672e+006 58 | 54 1.30298e+006 59 | 55 1.26746e+006 60 | 56 1.41765e+006 61 | 57 1.57177e+006 62 | 58 1.70271e+006 63 | 59 1.83169e+006 64 | 60 1.4718e+006 65 | 61 1.09428e+006 66 | 62 1.01896e+006 67 | 63 941111 68 | 64 905006 69 | 65 863715 70 | 66 934855 71 | 67 1.00746e+006 72 | 68 881393 73 | 69 740760 74 | 70 646781 75 | 71 549383 76 | 72 518148 77 | 73 482734 78 | 74 453311 79 | 75 428481 80 | 76 396087 81 | 77 359411 82 | 78 328735 83 | 79 282108 84 | 80 269220 85 | 81 236906 86 | 82 197073 87 | 83 167107 88 | 84 137233 89 | 85 108146 90 | 86 78656.5 91 | 87 49455.1 92 | 88 26223.6 93 | 89 8911.82 94 | 90 1620.84 95 | 91 0 96 | 97 | -------------------------------------------------------------------------------- /elphel/GPIO_input.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to work with the Input/Output Pins of the RPi, ltimately thought to 5 | trigger the Elphel camera. 6 | Based on http://code.google.com/p/raspberry-gpio-python/ 7 | """ 8 | 9 | import sys 10 | import time 11 | # Try to import the GPIO library 12 | try: 13 | import RPi.GPIO as GPIO 14 | except ImportError: 15 | print 'I cannot import RPI.GPIO, you have to run the script as root' 16 | print 'try running it again with' 17 | print '---' 18 | print 'sudo', ' '.join(sys.argv) 19 | print '---' 20 | sys.exit(1) 21 | 22 | 23 | def is_even(i): 24 | return (i % 2) == 0 25 | 26 | # to use Raspberry Pi board pin numbers 27 | # Named sequentially, as seen on the connector. compare 28 | # http://elinux.org/File:GPIOs.png 29 | GPIO.setmode(GPIO.BOARD) 30 | # Named GPIO*, see table http://is.gd/xWDsp7 (e.g. 007 is the last pin) 31 | # GPIO.setmode(GPIO.BCM) 32 | 33 | print 'set up GPIO input channel' 34 | Pin = 26 # BOARD 35 | # Pin = 007 # BMC 36 | GPIO.setup(Pin, GPIO.IN) 37 | 38 | print 39 | print 'I am waiting for you to connect pin', Pin, 'and ground' 40 | print 41 | 42 | # Wait for Input, then print something and wait for a short while 43 | # Code according to http://is.gd/G88UyN 44 | counter = 1 45 | Previous_Reading = 0 46 | while True: 47 | if GPIO.input(Pin): 48 | print "Pin", Pin, "and Ground are connected (" + str(counter),\ 49 | "times)." 50 | counter += 1 51 | time.sleep(0.05) 52 | 53 | Counter = 1 54 | Previous_Input = 0 55 | while Counter < 100: 56 | Input = GPIO.input(Pin) 57 | if not Previous_Input and Input: 58 | print "Pin", Pin, "and Ground are connected (" + str(Counter),\ 59 | "times)." 60 | Counter += 1 61 | Previous_Input = Input 62 | time.sleep(0.1) 63 | 64 | # Reset every channel that has been set up by this program to INPUT with no 65 | # pullup/pulldown and no event detection. 66 | GPIO.cleanup() 67 | -------------------------------------------------------------------------------- /nist/a150.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/a150.html 2 | #A-150 Tissue-Equivalent Plastic 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 2.259E+03 2.256E+03 10 | 1.50000E-03 7.282E+02 7.267E+02 11 | 2.00000E-03 3.183E+02 3.172E+02 12 | 3.00000E-03 9.652E+01 9.576E+01 13 | 4.00000E-03 4.081E+01 4.021E+01 14 | 4.03810E-03 3.966E+01 3.907E+01 15 | 20 K 4.03810E-03 5.629E+01 5.326E+01 16 | 5.00000E-03 3.069E+01 2.901E+01 17 | 6.00000E-03 1.813E+01 1.708E+01 18 | 8.00000E-03 7.914E+00 7.337E+00 19 | 1.00000E-02 4.186E+00 3.771E+00 20 | 1.50000E-02 1.394E+00 1.106E+00 21 | 2.00000E-02 7.068E-01 4.605E-01 22 | 3.00000E-02 3.481E-01 1.378E-01 23 | 4.00000E-02 2.562E-01 6.411E-02 24 | 5.00000E-02 2.198E-01 4.018E-02 25 | 6.00000E-02 2.008E-01 3.095E-02 26 | 8.00000E-02 1.803E-01 2.561E-02 27 | 1.00000E-01 1.680E-01 2.520E-02 28 | 1.50000E-01 1.485E-01 2.736E-02 29 | 2.00000E-01 1.353E-01 2.937E-02 30 | 3.00000E-01 1.173E-01 3.159E-02 31 | 4.00000E-01 1.049E-01 3.245E-02 32 | 5.00000E-01 9.579E-02 3.265E-02 33 | 6.00000E-01 8.857E-02 3.249E-02 34 | 8.00000E-01 7.777E-02 3.172E-02 35 | 1.00000E+00 6.992E-02 3.070E-02 36 | 1.25000E+00 6.253E-02 2.934E-02 37 | 1.50000E+00 5.691E-02 2.805E-02 38 | 2.00000E+00 4.880E-02 2.578E-02 39 | 3.00000E+00 3.907E-02 2.247E-02 40 | 4.00000E+00 3.335E-02 2.025E-02 41 | 5.00000E+00 2.958E-02 1.867E-02 42 | 6.00000E+00 2.690E-02 1.751E-02 43 | 8.00000E+00 2.338E-02 1.592E-02 44 | 1.00000E+01 2.117E-02 1.489E-02 45 | 1.50000E+01 1.819E-02 1.346E-02 46 | 2.00000E+01 1.675E-02 1.275E-02 47 | -------------------------------------------------------------------------------- /elphel/setexposure.php: -------------------------------------------------------------------------------- 1 | $value) { 11 | $parameters[$key] = convert($value); 12 | } 13 | 14 | // parameters are set X frames in the future 15 | if (isset($_GET['framedelay'])) 16 | { 17 | $framedelay = $_GET['framedelay']; 18 | } 19 | else 20 | $framedelay = 3; // default framedelay is 3 21 | 22 | function convert($s) { 23 | // clean up 24 | $s = trim($s, "\" "); 25 | // check if value is in HEX 26 | if(strtoupper(substr($s, 0, 2))=="0X") 27 | return intval(hexdec($s)); 28 | else 29 | return intval($s); 30 | } 31 | 32 | // Save CameraIP/phpfile into variable for re-use 33 | $url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 34 | 35 | // Give out some HTML code, so that we actually have a page to look at 36 | echo "\n\n GlobalDiagnostiX - PHP\n\n\n"; 37 | echo "

GlobalDiagnostiX exposure settings page

\n"; 38 | 39 | // only show parameters if we are actually setting anything, i.e when $parameters is not empty 40 | if ( !empty( $parameters ) ) 41 | { 42 | echo "The setting parameters from the URL are
\n"; print_r($parameters); echo "

\n"; 43 | } 44 | echo "
\n"; 45 | 46 | if ( isset ( $parameters["exposure"] ) ) 47 | { 48 | elphel_set_P_value(ELPHEL_AUTOEXP_ON,0); // turn off autoexposure if we're setting the exposure manually. 49 | elphel_set_P_value(ELPHEL_EXPOS,($parameters['exposure'] * 1000 )); // input is in msec, set is in usec 50 | } 51 | 52 | // wait for at least three frames for the setting from above to stick 53 | 54 | elphel_skip_frames($framedelay); 55 | 56 | echo "Frame ".elphel_get_frame()." has an exposure time of ".(elphel_get_P_value(ELPHEL_EXPOS) / 1000)." msec"; 57 | 58 | ?> 59 | -------------------------------------------------------------------------------- /randomMTF.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Calculate the modulation transfer function of a random image. 5 | Testing the idea described in Daniels1995, http://dx.doi.org/10.1117/12.190433 6 | """ 7 | 8 | from scipy import ndimage 9 | import numpy 10 | import matplotlib.pyplot as plt 11 | 12 | 13 | def MTF(ImageBeforeTransformation, ImageAfterTransformation): 14 | # calculate power spectral density of both images, according to Daniels1995 15 | PSD_A = numpy.abs(numpy.fft.fft2(ImageBeforeTransformation)) ** 2 16 | PSD_A = numpy.mean(PSD_A, axis=0) 17 | PSD_B = numpy.abs(numpy.fft.fft2(ImageAfterTransformation)) ** 2 18 | PSD_B = numpy.mean(PSD_B, axis=0) 19 | ImgWidth = ImageBeforeTransformation.shape[1] 20 | aemmteeaeff = numpy.sqrt(PSD_B / PSD_A)[:ImgWidth / 2] 21 | return aemmteeaeff 22 | 23 | length = 1116 24 | RandomImage = numpy.random.randint(2, size=[length, length]) * (2 ** 16) 25 | # Get rid of DC component 26 | RandomImage -= numpy.mean(RandomImage) 27 | RandomImageGauss = ndimage.gaussian_filter(RandomImage, 0.8) 28 | 29 | # PSD according to Daniels1995 30 | PSDImage = numpy.abs(numpy.fft.fft2(RandomImage)) ** 2 31 | PSD = numpy.mean(PSDImage, axis=0) 32 | 33 | PSDImageGauss = numpy.abs(numpy.fft.fft2(RandomImageGauss)) ** 2 34 | PSDGauss = numpy.mean(PSDImageGauss, axis=0) 35 | 36 | plt.subplot(231) 37 | plt.imshow(RandomImage, interpolation='none', cmap='gray') 38 | plt.title('Random image') 39 | plt.subplot(232) 40 | plt.imshow(numpy.fft.fftshift(PSDImage), interpolation='none', cmap='gray') 41 | plt.title('2D FFT') 42 | 43 | plt.subplot(234) 44 | plt.imshow(RandomImageGauss, interpolation='none', cmap='gray') 45 | plt.subplot(235) 46 | plt.imshow(numpy.fft.fftshift(PSDImageGauss), interpolation='none', 47 | cmap='gray') 48 | 49 | 50 | plt.subplot(133) 51 | plt.plot(PSD, label='PSD') 52 | plt.plot(PSDGauss, label='PSD gauss') 53 | plt.xlim([0, length]) 54 | plt.legend(loc='best') 55 | plt.title('PSD') 56 | 57 | plt.figure() 58 | plt.plot(MTF(RandomImage, RandomImageGauss)) 59 | plt.ylim([0, 1]) 60 | plt.title('MTF') 61 | 62 | plt.show() 63 | -------------------------------------------------------------------------------- /DepthOfFocus.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to calculate the depth of focus of the GDX setup. 5 | Formulas from \cite{Greenleaf1950} found via 6 | http://www.dofmaster.com/equations.html 7 | """ 8 | 9 | # H is the hyperfocal distance, mm 10 | # f is the lens focal length, mm 11 | # s is the focus distance 12 | # Dn is the near distance for acceptable sharpness 13 | # Df is the far distance for acceptable sharpness 14 | # N is the f-number 15 | # c is the circle of confusion, mm 16 | 17 | f = 3 18 | N = 1.4 19 | c = 0.08 20 | s = 200 21 | 22 | H = f ** 2 / (N * c + f) 23 | NearDistance = (s * (H - f)) / (H + s) 24 | FarDistance = (s * (H - f)) / (H - s) 25 | 26 | print 'The hyperfocal distance is %s' % H 27 | print 'The near distance for acceptable sharpness Dn is %s' % NearDistance 28 | print 'The far distance for acceptable sharpness Df is %s' % FarDistance 29 | 30 | 31 | print 'Also check image at', \ 32 | 'http://www.cambridgeincolour.com/tutorials/dof-calculator.htm' 33 | 34 | # Testing some stuff 35 | import matplotlib.pyplot as plt 36 | import os 37 | import numpy 38 | 39 | Experiment = '/afs/psi.ch/project/EssentialMed/MasterArbeitBFH/XrayImages/' +\ 40 | 'Toshiba/AR0132/TIS-TBL-6C-3MP/Hand/' 41 | ExperimentID = '5808439' 42 | OriginalImage = plt.imread(os.path.join(Experiment, ExperimentID + 43 | '.image.corrected.png')) 44 | StretchedImage = plt.imread(os.path.join(Experiment, ExperimentID + 45 | '.image.corrected.stretched.png')) 46 | 47 | print 'The minimum of the original image is %s, the maximum is %s' % ( 48 | numpy.min(OriginalImage), numpy.max(OriginalImage)) 49 | print 'The minimum of the stretched image is %s, the maximum is %s' % ( 50 | numpy.min(StretchedImage), numpy.max(StretchedImage)) 51 | 52 | plt.figure() 53 | plt.subplot(121) 54 | plt.imshow(OriginalImage, cmap='bone') 55 | plt.subplot(122) 56 | plt.hist(OriginalImage.flatten(), 64) 57 | 58 | plt.figure() 59 | plt.subplot(121) 60 | plt.imshow(StretchedImage, cmap='bone') 61 | plt.subplot(122) 62 | plt.hist(StretchedImage.flatten(), 64) 63 | plt.show() 64 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_100kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 100 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 53.313420 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 9.59328e-016 14 | 10 2.89124e-010 15 | 11 4.6091e-007 16 | 12 0.000202931 17 | 13 0.021419 18 | 14 1.10787 19 | 15 22.1977 20 | 16 176.993 21 | 17 911.805 22 | 18 3131.74 23 | 19 9054.92 24 | 20 22331.4 25 | 21 40256.7 26 | 22 67276.3 27 | 23 107079 28 | 24 159401 29 | 25 226809 30 | 26 306819 31 | 27 403189 32 | 28 506818 33 | 29 624379 34 | 30 739733 35 | 31 820423 36 | 32 897958 37 | 33 977661 38 | 34 1.04709e+006 39 | 35 1.11313e+006 40 | 36 1.1749e+006 41 | 37 1.23355e+006 42 | 38 1.28694e+006 43 | 39 1.33757e+006 44 | 40 1.37779e+006 45 | 41 1.39361e+006 46 | 42 1.40025e+006 47 | 43 1.40595e+006 48 | 44 1.40842e+006 49 | 45 1.40611e+006 50 | 46 1.41058e+006 51 | 47 1.41294e+006 52 | 48 1.39795e+006 53 | 49 1.37977e+006 54 | 50 1.36366e+006 55 | 51 1.33713e+006 56 | 52 1.31163e+006 57 | 53 1.28416e+006 58 | 54 1.26297e+006 59 | 55 1.24035e+006 60 | 56 1.49301e+006 61 | 57 1.75154e+006 62 | 58 1.97258e+006 63 | 59 2.19431e+006 64 | 60 1.68213e+006 65 | 61 1.15031e+006 66 | 62 1.06437e+006 67 | 63 976154 68 | 64 947196 69 | 65 913981 70 | 66 1.06567e+006 71 | 67 1.2192e+006 72 | 68 1.0353e+006 73 | 69 840445 74 | 70 737295 75 | 71 630842 76 | 72 602263 77 | 73 570544 78 | 74 550413 79 | 75 533653 80 | 76 508713 81 | 77 480272 82 | 78 459239 83 | 79 425881 84 | 80 412101 85 | 81 382424 86 | 82 355633 87 | 83 336578 88 | 84 310491 89 | 85 284486 90 | 86 266318 91 | 87 248068 92 | 88 223712 93 | 89 199272 94 | 90 179855 95 | 91 160656 96 | 92 133933 97 | 93 111879 98 | 94 91799.4 99 | 95 71543.4 100 | 96 52299 101 | 97 33826.4 102 | 98 17335.2 103 | 99 5215.23 104 | 100 549.676 105 | 101 0 106 | 107 | -------------------------------------------------------------------------------- /tiscamera/setup.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | I bought a 5MP USB 2.0 board camera ([DMx 72BUC02](http://www.theimagingsource.com/en_US/products/oem-cameras/usb-cmos-mono/dmm72buc02ml/)) 3 | from the Imaging source, to test for the GlobalDiagnostix project. 4 | 5 | To make the Imaging science camera work on the RPI, I had to follow the setup 6 | procedure as stated on the [tiscamera page](http://code.google.com/p/tiscamera/wiki/GettingStartedCMOSUVC), 7 | where the Imaging Source provides the source code to work with their cameras on 8 | Linux. 9 | 10 | I downloaded the code with 11 | > git clone https://code.google.com/p/tiscamera/ 12 | 13 | (into /afs/psi.ch/project/EssentialMed/Dev/tiscamera) but could not compile the 14 | code due to missing libraries. 15 | 16 | I had to install `libusb` and the `glib` libraries to make compilation work (and 17 | at the same time installed `mplayer` for looking at the video stream) 18 | This was done (on the Raspberry Pi) with 19 | > sudo apt-get install libglib2.0-dev libusb-dev mplayer 20 | 21 | and with the following command on SL6 22 | > sudo yum install libglib* libusb* mplayer 23 | 24 | Afterwards I did this 25 | > cd tiscamera/tools/euvccam-fw/ 26 | > make 27 | 28 | plugged in the camera 29 | > sudo ./euvccam-fw -p 30 | 31 | looked at the output of this command, which informed me that the camera is there 32 | and can be seen by Raspbian. 33 | 34 | To actually look at the image I started `mplayer` with the command below: 35 | > mplayer tv:// -tv driver=v4l2:device=/dev/video0 36 | 37 | This gives a 640x480 window (top left) of the total chip (and made me question 38 | my screwdriver skills, because I suspected that I attached the lens holder 39 | completely wrong). 40 | So, to see the whole chip, start mplayer like this 41 | > mplayer tv:// -tv driver=v4l2:width=2592:height=1922:device=/dev/video0· 42 | 43 | To save a screenshot of the current image, start mplayer with the -vt option, 44 | and press `s` while the image shows (or `S` for continuous, info from [this 45 | website](https://lorenzod8n.wordpress.com/2007/05/23/screenshots-with-mplayer/). 46 | > mplayer tv:// -tv driver=v4l2:device=/dev/video0 -vf screenshot 47 | -------------------------------------------------------------------------------- /Analysis/UnpackFromArchive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Once the analysis of a folder has been done, we use the TarToArchive.py script 5 | to pack it up and sent to the PSI tape archive, the DarkDeleter.py script is 6 | then used to delete all the unnecessary files. 7 | 8 | If you want to get some stuff back, it's annoying manual work to go in each 9 | folder and unpack the tar files. 10 | 11 | This script should alleviate this problem. 12 | """ 13 | 14 | import os 15 | import subprocess 16 | import fnmatch 17 | import glob 18 | 19 | RootFolder = ('/afs/psi.ch/project/EssentialMed/MasterArbeitBFH/' + 20 | 'XrayImages') 21 | 22 | ListOfTARs = [] 23 | for root, dirnames, filenames in os.walk(os.path.join(RootFolder)): 24 | for filename in fnmatch.filter(filenames, '*.gz'): 25 | ListOfTARs.append(os.path.join(root, filename)) 26 | 27 | if not ListOfTARs: 28 | print 'Nothing to do!' 29 | 30 | for counter, item in enumerate(ListOfTARs): 31 | print counter, 'of', len(ListOfTARs), '| unpacking', \ 32 | item[len(RootFolder) + 1:] 33 | UnpackCommand = ['tar', '-xf', item, '--directory', os.path.dirname(item)] 34 | unpackit = subprocess.Popen(UnpackCommand, stdout=subprocess.PIPE) 35 | output, error = unpackit.communicate() 36 | if output: 37 | print output 38 | if error: 39 | print error 40 | print 'Unpacking done, now removing', item[len(RootFolder) + 1:], 41 | try: 42 | os.remove(item) 43 | except OSError: 44 | print 'It is already gone!' 45 | ExperimentID = os.path.splitext(os.path.splitext(item)[0])[0] 46 | DeletionLog = ExperimentID + '.deletion.log' 47 | print 'as well as', os.path.basename(DeletionLog), \ 48 | 'and results from Analyis (all', \ 49 | os.path.basename(ExperimentID) + '*.png)' 50 | try: 51 | os.remove(DeletionLog) 52 | for i in glob.glob(ExperimentID + '*.png'): 53 | try: 54 | os.remove(i) 55 | except OSError: 56 | print 'The analysis images are already gone!' 57 | except OSError: 58 | print 'The log file is already gone!' 59 | print 80 * '-' 60 | print 'Done' 61 | -------------------------------------------------------------------------------- /elphel/GPIO_output.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to work with the Input/Output Pins of the RPi, utimately thought to 5 | trigger the Elphel camera. 6 | Based on http://code.google.com/p/raspberry-gpio-python/ 7 | """ 8 | 9 | import sys 10 | import time 11 | # Try to import the GPIO library 12 | try: 13 | import RPi.GPIO as GPIO 14 | except ImportError: 15 | print 'I cannot import RPI.GPIO, you have to run the script as root' 16 | print 'try running it again with' 17 | print '---' 18 | print 'sudo', ' '.join(sys.argv) 19 | print '---' 20 | sys.exit(1) 21 | 22 | try: 23 | Pin = int(sys.argv[1]) 24 | sleepytime = float(sys.argv[2]) 25 | steps = int(sys.argv[3]) 26 | except IndexError: 27 | print 'Start the script with three parameters' 28 | print sys.argv[0], 'Pin Sleeptime Repeats' 29 | sys.exit(1) 30 | 31 | 32 | def is_even(i): 33 | return (i % 2) == 0 34 | 35 | # to use Raspberry Pi board pin numbers 36 | # Named sequentially, as seen on the connector. compare 37 | # http://elinux.org/File:GPIOs.png 38 | GPIO.setmode(GPIO.BOARD) 39 | # Named GPIO*, see table http://is.gd/xWDsp7 (e.g. 007 is the last pin) 40 | # GPIO.setmode(GPIO.BCM) 41 | 42 | print 'set up GPIO output channel' 43 | # Pin = 26 # BOARD 44 | # Pin = 007 # BMC 45 | GPIO.setup(Pin, GPIO.OUT) 46 | 47 | # set RPi board pin selected above to high for a certain time, wait, set it low 48 | # lather, rinse, repeat for 'steps' steps 49 | try: 50 | for Iteration in range(steps): 51 | if is_even(Iteration): 52 | print str("%.02d" % (Iteration + 1)) + '/' + \ 53 | str("%.02d" % (steps)), '| Pin', Pin, '^ for', sleepytime, 's' 54 | GPIO.output(Pin, GPIO.HIGH) 55 | time.sleep(sleepytime) 56 | else: 57 | print str("%.02d" % (Iteration + 1)) + '/' +\ 58 | str("%.02d" % (steps)), '| Pin', Pin, 'v for', \ 59 | sleepytime, 's' 60 | GPIO.output(Pin, GPIO.LOW) 61 | time.sleep(sleepytime) 62 | except KeyboardInterrupt: 63 | print 64 | print 'User aborted sequence, goodbye' 65 | 66 | # Reset every channel that has been set up by this program to INPUT with no 67 | # pullup/pulldown and no event detection. 68 | GPIO.cleanup() 69 | -------------------------------------------------------------------------------- /nist/adipose.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/adipose.html 2 | #Adipose Tissue (ICRU-44) 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 2.628E+03 2.623E+03 10 | 1.03542E-03 2.392E+03 2.387E+03 11 | 1.07210E-03 2.176E+03 2.171E+03 12 | 11 K 1.07210E-03 2.182E+03 2.177E+03 13 | 1.50000E-03 8.622E+02 8.601E+02 14 | 2.00000E-03 3.800E+02 3.787E+02 15 | 2.47200E-03 2.053E+02 2.043E+02 16 | 16 K 2.47200E-03 2.072E+02 2.060E+02 17 | 2.64140E-03 1.707E+02 1.696E+02 18 | 2.82240E-03 1.405E+02 1.396E+02 19 | 17 K 2.82240E-03 1.420E+02 1.409E+02 20 | 3.00000E-03 1.188E+02 1.178E+02 21 | 4.00000E-03 5.054E+01 4.983E+01 22 | 5.00000E-03 2.587E+01 2.531E+01 23 | 6.00000E-03 1.494E+01 1.446E+01 24 | 8.00000E-03 6.300E+00 5.917E+00 25 | 1.00000E-02 3.268E+00 2.935E+00 26 | 1.50000E-02 1.083E+00 8.103E-01 27 | 2.00000E-02 5.677E-01 3.251E-01 28 | 3.00000E-02 3.063E-01 9.495E-02 29 | 4.00000E-02 2.396E-01 4.575E-02 30 | 5.00000E-02 2.123E-01 3.085E-02 31 | 6.00000E-02 1.974E-01 2.567E-02 32 | 8.00000E-02 1.800E-01 2.358E-02 33 | 1.00000E-01 1.688E-01 2.433E-02 34 | 1.50000E-01 1.500E-01 2.737E-02 35 | 2.00000E-01 1.368E-01 2.959E-02 36 | 3.00000E-01 1.187E-01 3.194E-02 37 | 4.00000E-01 1.062E-01 3.283E-02 38 | 5.00000E-01 9.696E-02 3.304E-02 39 | 6.00000E-01 8.965E-02 3.289E-02 40 | 8.00000E-01 7.873E-02 3.211E-02 41 | 1.00000E+00 7.078E-02 3.108E-02 42 | 1.25000E+00 6.330E-02 2.970E-02 43 | 1.50000E+00 5.760E-02 2.839E-02 44 | 2.00000E+00 4.940E-02 2.610E-02 45 | 3.00000E+00 3.955E-02 2.275E-02 46 | 4.00000E+00 3.377E-02 2.050E-02 47 | 5.00000E+00 2.995E-02 1.891E-02 48 | 6.00000E+00 2.725E-02 1.773E-02 49 | 8.00000E+00 2.368E-02 1.612E-02 50 | 1.00000E+01 2.145E-02 1.509E-02 51 | 1.50000E+01 1.843E-02 1.365E-02 52 | 2.00000E+01 1.698E-02 1.293E-02 53 | -------------------------------------------------------------------------------- /PlotXraySpectra.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Plot the x-ray spectra downloaded from the [Siemens simulator] 5 | (https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/) 6 | """ 7 | 8 | import matplotlib.pylab as plt 9 | import os 10 | import scipy 11 | import numpy as np 12 | from scipy.integrate import trapz 13 | 14 | # http://stackoverflow.com/a/11249430/323100 15 | Spectrapath = '/afs/psi.ch/project/EssentialMed/Dev/Spectra' 16 | Spectra = [ 17 | (os.path.join(Spectrapath, 'Xray-Spectrum_040kV.txt')), 18 | (os.path.join(Spectrapath, 'Xray-Spectrum_046kV.txt')), 19 | (os.path.join(Spectrapath, 'Xray-Spectrum_053kV.txt')), 20 | (os.path.join(Spectrapath, 'Xray-Spectrum_060kV.txt')), 21 | (os.path.join(Spectrapath, 'Xray-Spectrum_070kV.txt')), 22 | (os.path.join(Spectrapath, 'Xray-Spectrum_080kV.txt')), 23 | (os.path.join(Spectrapath, 'Xray-Spectrum_090kV.txt')), 24 | (os.path.join(Spectrapath, 'Xray-Spectrum_100kV.txt')), 25 | (os.path.join(Spectrapath, 'Xray-Spectrum_100kV.txt')), 26 | (os.path.join(Spectrapath, 'Xray-Spectrum_120kV.txt'))] 27 | 28 | Data = [(np.loadtxt(FileName)) for FileName in Spectra] 29 | Energy = [int(open(FileName).readlines()[2].split()[4]) for FileName in Spectra] 30 | Mean = [float(open(FileName).readlines()[5].split()[3]) for FileName in Spectra] 31 | 32 | for i in range(len(Spectra)): 33 | plt.plot(Data[i][:, 0], Data[i][:, 1], 34 | label=str(Energy[i]) + 'kV, Mean=' + 35 | str(round(Mean[i], 3)) + 'keV') 36 | 37 | plt.legend(loc='best') 38 | plt.title('X-ray spectra') 39 | plt.xlabel('Energy [kV]') 40 | plt.ylabel('Photons') 41 | plt.savefig('plot.pdf') 42 | 43 | plt.figure(figsize=[22, 5]) 44 | for counter, spectra in enumerate(Spectra): 45 | plt.subplot(1, len(Spectra), counter + 1) 46 | plt.plot(Data[counter][:, 0], Data[counter][:, 1]) 47 | Integral = scipy.integrate.trapz(Data[counter][:, 1], Data[counter][:, 0]) 48 | print 'The integral for', Energy[counter], 'kV is', \ 49 | str(round(Integral / 1e6, 3)) + 'e6 photons' 50 | plt.title(str(Energy[counter]) + 'kV\n' + 51 | str(round(Integral / 1e6, 3)) + 'e6 photons') 52 | plt.xlim([0, 150]) 53 | plt.ylim([0, 3e6]) 54 | # Turn off y-ticks for subplots 2-end (counter >0) 55 | if counter: 56 | plt.setp(plt.gca().get_yticklabels(), visible=False) 57 | plt.tight_layout() 58 | plt.show() 59 | -------------------------------------------------------------------------------- /Spectra/Xray-Spectrum_120kV.txt: -------------------------------------------------------------------------------- 1 | # https://w9.siemens.com/cms/oemproducts/Home/X-rayToolbox/spektrum/Pages/radIn.aspx 2 | # Anode material: Tungsten 3 | # Peak tube voltage: 120 kV 4 | # Relative voltage ripple: 0.04 5 | # Air kerma 0.0025 Gy 6 | # Mean energy 58.333157 keV 7 | # List of filters 8 | # Material Thickness, mm 9 | # AIR 1400 10 | # Al 4 11 | # Spektrum 12 | 8 0 13 | 9 8.42289e-016 14 | 10 3.01667e-010 15 | 11 5.29287e-007 16 | 12 0.000180995 17 | 13 0.0171807 18 | 14 0.840895 19 | 15 16.3811 20 | 16 130.497 21 | 17 669.317 22 | 18 2304.22 23 | 19 6660.89 24 | 20 16401 25 | 21 29497.8 26 | 22 50004.7 27 | 23 80446.3 28 | 24 120545 29 | 25 172538 30 | 26 234394 31 | 27 309197 32 | 28 392890 33 | 29 488898 34 | 30 585029 35 | 31 654297 36 | 32 721399 37 | 33 791110 38 | 34 852861 39 | 35 912739 40 | 36 967831 41 | 37 1.02086e+006 42 | 38 1.07109e+006 43 | 39 1.12003e+006 44 | 40 1.15928e+006 45 | 41 1.17705e+006 46 | 42 1.19216e+006 47 | 43 1.20689e+006 48 | 44 1.2138e+006 49 | 45 1.21664e+006 50 | 46 1.22735e+006 51 | 47 1.23648e+006 52 | 48 1.22813e+006 53 | 49 1.21744e+006 54 | 50 1.2117e+006 55 | 51 1.19633e+006 56 | 52 1.18048e+006 57 | 53 1.16321e+006 58 | 54 1.15419e+006 59 | 55 1.14432e+006 60 | 56 1.54677e+006 61 | 57 1.95873e+006 62 | 58 2.31496e+006 63 | 59 2.67602e+006 64 | 60 1.933e+006 65 | 61 1.16639e+006 66 | 62 1.06059e+006 67 | 63 952697 68 | 64 929221 69 | 65 902507 70 | 66 1.17044e+006 71 | 67 1.44123e+006 72 | 68 1.18402e+006 73 | 69 917237 74 | 70 784909 75 | 71 651424 76 | 72 623371 77 | 73 592583 78 | 74 583814 79 | 75 576617 80 | 76 560463 81 | 77 541498 82 | 78 529714 83 | 79 510049 84 | 80 497108 85 | 81 474108 86 | 82 471209 87 | 83 455507 88 | 84 437559 89 | 85 419385 90 | 86 409932 91 | 87 400701 92 | 88 383307 93 | 89 365685 94 | 90 351492 95 | 91 336849 96 | 92 321326 97 | 93 304512 98 | 94 291365 99 | 95 278246 100 | 96 267916 101 | 97 257655 102 | 98 244215 103 | 99 230727 104 | 100 220216 105 | 101 209089 106 | 102 195008 107 | 103 179673 108 | 104 167981 109 | 105 156445 110 | 106 142757 111 | 107 129366 112 | 108 117530 113 | 109 104957 114 | 110 92589.9 115 | 111 80799.3 116 | 112 69849.9 117 | 113 60282.2 118 | 114 48043.5 119 | 115 35011.1 120 | 116 24697.7 121 | 117 15567 122 | 118 8464.95 123 | 119 2617.51 124 | 120 541.578 125 | 121 0 126 | 127 | -------------------------------------------------------------------------------- /nist/lung.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/lung.html 2 | # Lung Tissue (ICRU-44) 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 3.803E+03 3.791E+03 10 | 1.03542E-03 3.469E+03 3.459E+03 11 | 1.07210E-03 3.164E+03 3.155E+03 12 | 11 K 1.07210E-03 3.176E+03 3.167E+03 13 | 1.50000E-03 1.283E+03 1.280E+03 14 | 2.00000E-03 5.746E+02 5.727E+02 15 | 2.14550E-03 4.707E+02 4.690E+02 16 | 15 K 2.14550E-03 4.752E+02 4.732E+02 17 | 2.30297E-03 3.883E+02 3.865E+02 18 | 2.47200E-03 3.170E+02 3.154E+02 19 | 16 K 2.47200E-03 3.226E+02 3.206E+02 20 | 2.64140E-03 2.668E+02 2.650E+02 21 | 2.82240E-03 2.204E+02 2.189E+02 22 | 17 K 2.82240E-03 2.248E+02 2.229E+02 23 | 3.00000E-03 1.888E+02 1.870E+02 24 | 3.60740E-03 1.103E+02 1.091E+02 25 | 19 K 3.60740E-03 1.125E+02 1.110E+02 26 | 4.00000E-03 8.306E+01 8.181E+01 27 | 5.00000E-03 4.296E+01 4.210E+01 28 | 6.00000E-03 2.497E+01 2.431E+01 29 | 8.00000E-03 1.058E+01 1.010E+01 30 | 1.00000E-02 5.459E+00 5.067E+00 31 | 1.50000E-02 1.721E+00 1.423E+00 32 | 2.00000E-02 8.316E-01 5.740E-01 33 | 3.00000E-02 3.815E-01 1.635E-01 34 | 4.00000E-02 2.699E-01 7.286E-02 35 | 5.00000E-02 2.270E-01 4.393E-02 36 | 6.00000E-02 2.053E-01 3.282E-02 37 | 8.00000E-02 1.826E-01 2.625E-02 38 | 1.00000E-01 1.695E-01 2.550E-02 39 | 1.50000E-01 1.493E-01 2.748E-02 40 | 2.00000E-01 1.359E-01 2.945E-02 41 | 3.00000E-01 1.177E-01 3.167E-02 42 | 4.00000E-01 1.053E-01 3.252E-02 43 | 5.00000E-01 9.607E-02 3.272E-02 44 | 6.00000E-01 8.882E-02 3.257E-02 45 | 8.00000E-01 7.800E-02 3.179E-02 46 | 1.00000E+00 7.013E-02 3.077E-02 47 | 1.25000E+00 6.271E-02 2.940E-02 48 | 1.50000E+00 5.706E-02 2.810E-02 49 | 2.00000E+00 4.900E-02 2.586E-02 50 | 3.00000E+00 3.935E-02 2.262E-02 51 | 4.00000E+00 3.374E-02 2.048E-02 52 | 5.00000E+00 3.005E-02 1.898E-02 53 | 6.00000E+00 2.746E-02 1.789E-02 54 | 8.00000E+00 2.407E-02 1.643E-02 55 | 1.00000E+01 2.198E-02 1.551E-02 56 | 1.50000E+01 1.922E-02 1.427E-02 57 | 2.00000E+01 1.794E-02 1.367E-02 58 | -------------------------------------------------------------------------------- /nist/muscle.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/muscle.html 2 | #Muscle, Skeletal 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 3.719E+03 3.709E+03 10 | 1.03542E-03 3.393E+03 3.383E+03 11 | 1.07210E-03 3.094E+03 3.085E+03 12 | 11 K 1.07210E-03 3.100E+03 3.091E+03 13 | 1.50000E-03 1.251E+03 1.247E+03 14 | 2.00000E-03 5.594E+02 5.574E+02 15 | 2.14550E-03 4.581E+02 4.564E+02 16 | 15 K 2.14550E-03 4.626E+02 4.606E+02 17 | 2.30297E-03 3.776E+02 3.762E+02 18 | 2.47200E-03 3.085E+02 3.069E+02 19 | 16 K 2.47200E-03 3.140E+02 3.121E+02 20 | 2.64140E-03 2.597E+02 2.579E+02 21 | 2.82240E-03 2.145E+02 2.130E+02 22 | 17 K 2.82240E-03 2.160E+02 2.143E+02 23 | 3.00000E-03 1.812E+02 1.796E+02 24 | 3.60740E-03 1.057E+02 1.046E+02 25 | 19 K 3.60740E-03 1.100E+02 1.083E+02 26 | 4.00000E-03 8.127E+01 7.992E+01 27 | 5.00000E-03 4.206E+01 4.116E+01 28 | 6.00000E-03 2.446E+01 2.377E+01 29 | 8.00000E-03 1.037E+01 9.888E+00 30 | 1.00000E-02 5.356E+00 4.964E+00 31 | 1.50000E-02 1.693E+00 1.396E+00 32 | 2.00000E-02 8.205E-01 5.638E-01 33 | 3.00000E-02 3.783E-01 1.610E-01 34 | 4.00000E-02 2.685E-01 7.192E-02 35 | 5.00000E-02 2.262E-01 4.349E-02 36 | 6.00000E-02 2.048E-01 3.258E-02 37 | 8.00000E-02 1.823E-01 2.615E-02 38 | 1.00000E-01 1.693E-01 2.544E-02 39 | 1.50000E-01 1.492E-01 2.745E-02 40 | 2.00000E-01 1.358E-01 2.942E-02 41 | 3.00000E-01 1.176E-01 3.164E-02 42 | 4.00000E-01 1.052E-01 3.249E-02 43 | 5.00000E-01 9.598E-02 3.269E-02 44 | 6.00000E-01 8.874E-02 3.254E-02 45 | 8.00000E-01 7.793E-02 3.177E-02 46 | 1.00000E+00 7.007E-02 3.074E-02 47 | 1.25000E+00 6.265E-02 2.938E-02 48 | 1.50000E+00 5.701E-02 2.808E-02 49 | 2.00000E+00 4.896E-02 2.584E-02 50 | 3.00000E+00 3.931E-02 2.259E-02 51 | 4.00000E+00 3.369E-02 2.045E-02 52 | 5.00000E+00 3.000E-02 1.895E-02 53 | 6.00000E+00 2.741E-02 1.786E-02 54 | 8.00000E+00 2.401E-02 1.639E-02 55 | 1.00000E+01 2.192E-02 1.547E-02 56 | 1.50000E+01 1.915E-02 1.421E-02 57 | 2.00000E+01 1.786E-02 1.361E-02 58 | -------------------------------------------------------------------------------- /nist/bone.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/bone.html 2 | #Bone, Cortical (ICRU-44) 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 3.781E+03 3.772E+03 10 | 1.03542E-03 3.452E+03 3.444E+03 11 | 1.07210E-03 3.150E+03 3.143E+03 12 | 11 K 1.07210E-03 3.156E+03 3.149E+03 13 | 1.18283E-03 2.434E+03 2.429E+03 14 | 1.30500E-03 1.873E+03 1.869E+03 15 | 12 K 1.30500E-03 1.883E+03 1.878E+03 16 | 1.50000E-03 1.295E+03 1.291E+03 17 | 2.00000E-03 5.869E+02 5.846E+02 18 | 2.14550E-03 4.824E+02 4.803E+02 19 | 15 K 2.14550E-03 7.114E+02 6.961E+02 20 | 2.30297E-03 5.916E+02 5.789E+02 21 | 2.47200E-03 4.907E+02 4.805E+02 22 | 16 K 2.47200E-03 4.962E+02 4.857E+02 23 | 3.00000E-03 2.958E+02 2.897E+02 24 | 4.00000E-03 1.331E+02 1.303E+02 25 | 4.03810E-03 1.296E+02 1.269E+02 26 | 20 K 4.03810E-03 3.332E+02 3.006E+02 27 | 5.00000E-03 1.917E+02 1.757E+02 28 | 6.00000E-03 1.171E+02 1.085E+02 29 | 8.00000E-03 5.323E+01 4.987E+01 30 | 1.00000E-02 2.851E+01 2.680E+01 31 | 1.50000E-02 9.032E+00 8.388E+00 32 | 2.00000E-02 4.001E+00 3.601E+00 33 | 3.00000E-02 1.331E+00 1.070E+00 34 | 4.00000E-02 6.655E-01 4.507E-01 35 | 5.00000E-02 4.242E-01 2.336E-01 36 | 6.00000E-02 3.148E-01 1.400E-01 37 | 8.00000E-02 2.229E-01 6.896E-02 38 | 1.00000E-01 1.855E-01 4.585E-02 39 | 1.50000E-01 1.480E-01 3.183E-02 40 | 2.00000E-01 1.309E-01 3.003E-02 41 | 3.00000E-01 1.113E-01 3.032E-02 42 | 4.00000E-01 9.908E-02 3.069E-02 43 | 5.00000E-01 9.022E-02 3.073E-02 44 | 6.00000E-01 8.332E-02 3.052E-02 45 | 8.00000E-01 7.308E-02 2.973E-02 46 | 1.00000E+00 6.566E-02 2.875E-02 47 | 1.25000E+00 5.871E-02 2.745E-02 48 | 1.50000E+00 5.346E-02 2.623E-02 49 | 2.00000E+00 4.607E-02 2.421E-02 50 | 3.00000E+00 3.745E-02 2.145E-02 51 | 4.00000E+00 3.257E-02 1.975E-02 52 | 5.00000E+00 2.946E-02 1.864E-02 53 | 6.00000E+00 2.734E-02 1.788E-02 54 | 8.00000E+00 2.467E-02 1.695E-02 55 | 1.00000E+01 2.314E-02 1.644E-02 56 | 1.50000E+01 2.132E-02 1.587E-02 57 | 2.00000E+01 2.068E-02 1.568E-02 58 | -------------------------------------------------------------------------------- /nist/tissue.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/tissue.html 2 | #Tissue, Soft (ICRU-44) 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 3.712E+03 3.701E+03 10 | 1.03542E-03 3.386E+03 3.376E+03 11 | 1.07210E-03 3.087E+03 3.079E+03 12 | 11 K 1.07210E-03 3.099E+03 3.090E+03 13 | 1.50000E-03 1.251E+03 1.247E+03 14 | 2.00000E-03 5.596E+02 5.577E+02 15 | 2.14550E-03 4.583E+02 4.566E+02 16 | 15 K 2.14550E-03 4.650E+02 4.629E+02 17 | 2.30297E-03 3.800E+02 3.782E+02 18 | 2.47200E-03 3.102E+02 3.086E+02 19 | 16 K 2.47200E-03 3.158E+02 3.137E+02 20 | 2.64140E-03 2.612E+02 2.594E+02 21 | 2.82240E-03 2.158E+02 2.142E+02 22 | 17 K 2.82240E-03 2.188E+02 2.169E+02 23 | 3.00000E-03 1.836E+02 1.819E+02 24 | 3.60740E-03 1.073E+02 1.061E+02 25 | 19 K 3.60740E-03 1.105E+02 1.089E+02 26 | 4.00000E-03 8.161E+01 8.030E+01 27 | 5.00000E-03 4.224E+01 4.135E+01 28 | 6.00000E-03 2.456E+01 2.389E+01 29 | 8.00000E-03 1.042E+01 9.935E+00 30 | 1.00000E-02 5.379E+00 4.987E+00 31 | 1.50000E-02 1.699E+00 1.402E+00 32 | 2.00000E-02 8.230E-01 5.663E-01 33 | 3.00000E-02 3.790E-01 1.616E-01 34 | 4.00000E-02 2.688E-01 7.216E-02 35 | 5.00000E-02 2.264E-01 4.360E-02 36 | 6.00000E-02 2.048E-01 3.264E-02 37 | 8.00000E-02 1.823E-01 2.617E-02 38 | 1.00000E-01 1.693E-01 2.545E-02 39 | 1.50000E-01 1.492E-01 2.745E-02 40 | 2.00000E-01 1.358E-01 2.942E-02 41 | 3.00000E-01 1.175E-01 3.164E-02 42 | 4.00000E-01 1.052E-01 3.249E-02 43 | 5.00000E-01 9.598E-02 3.269E-02 44 | 6.00000E-01 8.873E-02 3.254E-02 45 | 8.00000E-01 7.793E-02 3.176E-02 46 | 1.00000E+00 7.006E-02 3.074E-02 47 | 1.25000E+00 6.265E-02 2.938E-02 48 | 1.50000E+00 5.701E-02 2.807E-02 49 | 2.00000E+00 4.895E-02 2.583E-02 50 | 3.00000E+00 3.931E-02 2.259E-02 51 | 4.00000E+00 3.369E-02 2.045E-02 52 | 5.00000E+00 3.000E-02 1.895E-02 53 | 6.00000E+00 2.741E-02 1.786E-02 54 | 8.00000E+00 2.401E-02 1.639E-02 55 | 1.00000E+01 2.193E-02 1.547E-02 56 | 1.50000E+01 1.915E-02 1.422E-02 57 | 2.00000E+01 1.787E-02 1.362E-02 58 | -------------------------------------------------------------------------------- /Switch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to switch Network interfaces between DHCP and Elphel 5 | First version based on reading the values from their respective outputs 6 | New version simply sets to Elphel as default when called without a flag. 7 | Options -p or -p switch to the *P*SI *D*HCP server 8 | """ 9 | 10 | import os 11 | from optparse import OptionParser 12 | 13 | # Setup the Options 14 | Parser = OptionParser() 15 | Usage = 'Usage: % prog [Options] arg' 16 | 17 | Parser.add_option('-e', '--Elphel', dest='Elphel', 18 | help='Switch the network interface to work with the ' 19 | 'Elphel camera (default)s', 20 | default=True, 21 | action='store_true') 22 | Parser.add_option('-d', '--DHCP', dest='DHCP', 23 | help='Switch the network interface to *D*HCP, to ' 24 | 'work with PSI ethernet', 25 | action='store_true') 26 | Parser.add_option('-p', '--PSI', dest='PSI', 27 | help='Switch the network interface to DHCP, to work ' 28 | 'with *P*SI ethernet', 29 | action='store_true') 30 | (Options, Arguments) = Parser.parse_args() 31 | 32 | if Options.DHCP or Options.PSI: 33 | print ('Setting network interface to "PSI" with ' 34 | '"sudo dhclient eth0 > /dev/null"') 35 | os.system('sudo dhclient eth0 > /dev/null') 36 | print 37 | print ('Unplug the Ethernet cable from the Camera, plug in the PSI ' 38 | 'Ethernet cable and interweb') 39 | elif Options.Elphel: 40 | print ('Setting network interface to "Elphel" with ' 41 | '"sudo ifconfig eth0 192.168.0.1 > /dev/null"') 42 | os.system('sudo ifconfig eth0 192.168.0.1 > /dev/null') 43 | print 44 | print ('Uplug the PSI Ethernet cable, plug in both ends of the Elphel ' 45 | 'ethernet cable, powercycle the camera with the POE supply and ' 46 | 'wait a while for the camera to boot') 47 | 48 | if Options.DHCP or Options.PSI: 49 | print ('If you just switched from Elphel to to PSI, then it is probably ' 50 | 'a good idea commit all your changes and push them to the remote ' 51 | 'repository. You can then pull all versioned changes from there.') 52 | print '---' 53 | print 'cd;git commit -a;git push' 54 | print '---' 55 | print 'would to that all at once...' 56 | print 57 | print 'Or you can copy all the non-versioned stuff to AFS with' 58 | print '---' 59 | print 'cd;bash SyncToAFS.cmd' 60 | print '---' 61 | -------------------------------------------------------------------------------- /nist/blood.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/blood.html 2 | #Blood, Whole (ICRU-44) 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 3.806E+03 3.795E+03 10 | 1.03542E-03 3.473E+03 3.462E+03 11 | 1.07210E-03 3.167E+03 3.158E+03 12 | 11 K 1.07210E-03 3.173E+03 3.164E+03 13 | 1.50000E-03 1.282E+03 1.278E+03 14 | 2.00000E-03 5.737E+02 5.718E+02 15 | 2.14550E-03 4.700E+02 4.682E+02 16 | 15 K 2.14550E-03 4.722E+02 4.703E+02 17 | 2.30297E-03 3.858E+02 3.841E+02 18 | 2.47200E-03 3.149E+02 3.134E+02 19 | 16 K 2.47200E-03 3.186E+02 3.168E+02 20 | 2.64140E-03 2.633E+02 2.618E+02 21 | 2.82240E-03 2.175E+02 2.161E+02 22 | 17 K 2.82240E-03 2.219E+02 2.201E+02 23 | 3.00000E-03 1.862E+02 1.846E+02 24 | 3.60740E-03 1.088E+02 1.076E+02 25 | 19 K 3.60740E-03 1.109E+02 1.094E+02 26 | 4.00000E-03 8.187E+01 8.066E+01 27 | 5.00000E-03 4.232E+01 4.147E+01 28 | 6.00000E-03 2.458E+01 2.393E+01 29 | 7.11200E-03 1.479E+01 1.425E+01 30 | 26 K 7.11200E-03 1.514E+01 1.450E+01 31 | 8.00000E-03 1.068E+01 1.013E+01 32 | 1.00000E-02 5.519E+00 5.096E+00 33 | 1.50000E-02 1.744E+00 1.440E+00 34 | 2.00000E-02 8.428E-01 5.831E-01 35 | 3.00000E-02 3.852E-01 1.669E-01 36 | 4.00000E-02 2.715E-01 7.443E-02 37 | 5.00000E-02 2.278E-01 4.477E-02 38 | 6.00000E-02 2.057E-01 3.332E-02 39 | 8.00000E-02 1.827E-01 2.645E-02 40 | 1.00000E-01 1.695E-01 2.559E-02 41 | 1.50000E-01 1.492E-01 2.749E-02 42 | 2.00000E-01 1.358E-01 2.944E-02 43 | 3.00000E-01 1.176E-01 3.164E-02 44 | 4.00000E-01 1.052E-01 3.249E-02 45 | 5.00000E-01 9.598E-02 3.269E-02 46 | 6.00000E-01 8.874E-02 3.254E-02 47 | 8.00000E-01 7.793E-02 3.177E-02 48 | 1.00000E+00 7.007E-02 3.074E-02 49 | 1.25000E+00 6.265E-02 2.938E-02 50 | 1.50000E+00 5.701E-02 2.807E-02 51 | 2.00000E+00 4.896E-02 2.584E-02 52 | 3.00000E+00 3.932E-02 2.260E-02 53 | 4.00000E+00 3.371E-02 2.046E-02 54 | 5.00000E+00 3.002E-02 1.897E-02 55 | 6.00000E+00 2.743E-02 1.788E-02 56 | 8.00000E+00 2.405E-02 1.642E-02 57 | 1.00000E+01 2.196E-02 1.550E-02 58 | 1.50000E+01 1.920E-02 1.425E-02 59 | 2.00000E+01 1.793E-02 1.366E-02 60 | -------------------------------------------------------------------------------- /Demonstrator/detect_lines_in_checkerboard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to detect horizontal and vertical lines on a sliding window along images 5 | The script is based on the answers to 6 | [this Stack Overflow question](http://stackoverflow.com/q/7227074/323100) 7 | """ 8 | 9 | from __future__ import division 10 | import matplotlib 11 | import matplotlib.pylab as plt 12 | import os 13 | import cv2 14 | import math 15 | 16 | BasePath = '/afs/psi.ch/project/EssentialMed/Images/DetectorElectronicsTests/' \ 17 | 'EssentialLab/Valerie' 18 | 19 | tcpIpPool = ['192.168.1.31', '192.168.1.32', '192.168.1.33', '192.168.1.34', 20 | '192.168.1.35', '192.168.1.36', '192.168.1.37', '192.168.1.38', 21 | '192.168.1.39', '192.168.1.40', '192.168.1.41', '192.168.1.42'] 22 | 23 | ImageName = tcpIpPool[0] + '.png' 24 | 25 | plt.figure(figsize=[16, 9]) 26 | 27 | InputImage = cv2.imread(os.path.join(BasePath, ImageName)) 28 | 29 | RegionWidth = 200 30 | for i in range(0, InputImage.shape[1] - RegionWidth, RegionWidth): 31 | print i 32 | plt.clf() 33 | plt.suptitle(' '.join(['Region of size', str(RegionWidth), 34 | 'starting at px.', str(i)])) 35 | img = InputImage[:, i:i + RegionWidth, :] 36 | # img = cv2.GaussianBlur(img, (5, 5), 0) 37 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 38 | edges = cv2.Canny(gray, 80, 120) 39 | lines = cv2.HoughLinesP(cv2.flip(edges, flipCode=0), 1, math.pi / 2, 2, 40 | None, 30, 1) 41 | for line in lines[0]: 42 | pt1 = (line[0], line[1]) 43 | pt2 = (line[2], line[3]) 44 | 45 | plt.subplot(141) 46 | plt.imshow(InputImage, interpolation='none') 47 | window = matplotlib.patches.Rectangle((i, 0), RegionWidth, 48 | InputImage.shape[0], color='blue', 49 | alpha=0.25) 50 | plt.gca().add_patch(window) 51 | plt.axvline(x=i) 52 | plt.axvline(x=i + RegionWidth) 53 | plt.title('Sliding window along image') 54 | plt.subplot(142) 55 | plt.imshow(gray, interpolation='none', cmap='gray') 56 | plt.title('Region') 57 | plt.subplot(143) 58 | plt.imshow(edges, interpolation='none', cmap='gray') 59 | plt.title('Detected Edges') 60 | plt.subplot(144) 61 | plt.imshow(cv2.flip(gray, flipCode=0), interpolation='none', cmap='gray') 62 | for coordinates in lines[0]: 63 | plt.plot([coordinates[0], coordinates[2]], 64 | [coordinates[1], coordinates[3]], color='y', linestyle='-', 65 | linewidth='5', alpha=0.5) 66 | plt.plot([coordinates[0], coordinates[2]], 67 | [coordinates[1], coordinates[3]], color='k', linestyle='-') 68 | plt.xlim([0, img.shape[1]]) 69 | plt.ylim([0, img.shape[0]]) 70 | plt.title('Detected horizontal\nand vertical lines') 71 | plt.pause(0.001) 72 | plt.show() 73 | -------------------------------------------------------------------------------- /nist/gadolinium.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/gadolinium.html 2 | #Gadolinium Oxysulfide 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 2.497E+03 2.487E+03 10 | 1.08867E-03 2.103E+03 2.093E+03 11 | 1.18520E-03 1.765E+03 1.756E+03 12 | 64 M5 1.18520E-03 1.984E+03 1.973E+03 13 | 1.20109E-03 2.465E+03 2.450E+03 14 | 1.21720E-03 3.644E+03 3.621E+03 15 | 64 M4 1.21720E-03 4.311E+03 4.282E+03 16 | 1.50000E-03 4.390E+03 4.361E+03 17 | 1.54400E-03 4.092E+03 4.065E+03 18 | 64 M3 1.54400E-03 4.699E+03 4.668E+03 19 | 1.61454E-03 4.235E+03 4.207E+03 20 | 1.68830E-03 3.819E+03 3.793E+03 21 | 64 M2 1.68830E-03 4.046E+03 4.019E+03 22 | 1.78195E-03 3.585E+03 3.562E+03 23 | 1.88080E-03 3.175E+03 3.154E+03 24 | 64 M1 1.88080E-03 3.311E+03 3.289E+03 25 | 2.00000E-03 2.883E+03 2.863E+03 26 | 2.47200E-03 1.752E+03 1.739E+03 27 | 16 K 2.47200E-03 1.909E+03 1.885E+03 28 | 3.00000E-03 1.205E+03 1.189E+03 29 | 4.00000E-03 5.916E+02 5.822E+02 30 | 5.00000E-03 3.371E+02 3.302E+02 31 | 6.00000E-03 2.118E+02 2.062E+02 32 | 7.24280E-03 1.306E+02 1.261E+02 33 | 64 L3 7.24280E-03 3.312E+02 2.983E+02 34 | 7.57876E-03 2.994E+02 2.667E+02 35 | 7.93030E-03 2.625E+02 2.378E+02 36 | 64 L2 7.93030E-03 3.539E+02 3.134E+02 37 | 8.00000E-03 3.470E+02 3.074E+02 38 | 8.37560E-03 3.096E+02 2.754E+02 39 | 64 L1 8.37560E-03 3.560E+02 3.152E+02 40 | 1.00000E-02 2.285E+02 2.053E+02 41 | 1.50000E-02 7.902E+01 7.232E+01 42 | 2.00000E-02 3.689E+01 3.376E+01 43 | 3.00000E-02 1.254E+01 1.125E+01 44 | 4.00000E-02 5.854E+00 5.084E+00 45 | 5.00000E-02 3.274E+00 2.732E+00 46 | 5.02391E-02 3.234E+00 2.695E+00 47 | 64 K 5.02391E-02 1.555E+01 4.677E+00 48 | 6.00000E-02 9.815E+00 3.946E+00 49 | 8.00000E-02 4.666E+00 2.453E+00 50 | 1.00000E-01 2.613E+00 1.545E+00 51 | 1.50000E-01 9.381E-01 6.043E-01 52 | 2.00000E-01 4.812E-01 3.035E-01 53 | 3.00000E-01 2.185E-01 1.225E-01 54 | 4.00000E-01 1.423E-01 7.211E-02 55 | 5.00000E-01 1.095E-01 5.218E-02 56 | 6.00000E-01 9.153E-02 4.240E-02 57 | 8.00000E-01 7.225E-02 3.322E-02 58 | 1.00000E+00 6.163E-02 2.883E-02 59 | 1.25000E+00 5.335E-02 2.558E-02 60 | 1.50000E+00 4.832E-02 2.367E-02 61 | 2.00000E+00 4.271E-02 2.183E-02 62 | 3.00000E+00 3.829E-02 2.129E-02 63 | 4.00000E+00 3.699E-02 2.198E-02 64 | 5.00000E+00 3.685E-02 2.297E-02 65 | 6.00000E+00 3.722E-02 2.397E-02 66 | 8.00000E+00 3.864E-02 2.579E-02 67 | 1.00000E+01 4.038E-02 2.730E-02 68 | 1.50000E+01 4.478E-02 2.985E-02 69 | 2.00000E+01 4.837E-02 3.099E-02 70 | -------------------------------------------------------------------------------- /SurfaceEntranceDoseCalculator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script used to calculate the surface entrance dose of a certain x-ray 5 | measurement. Gives the same results as the 'Diagnostische Referenzwerte' 6 | Excel calculator on the BAG-page, in the right side-bar of http://is.gd/E2qIPA. 7 | The calculation is based on 'Merkblatt R-06-04' from BAG. 8 | The file is an extensiuon of SurfaceEntranceDose.py, which was used to plot 9 | the SED for a talk at an SLS Symposium. 10 | """ 11 | 12 | from __future__ import division # fix integer division 13 | from optparse import OptionParser 14 | import sys 15 | 16 | 17 | # Use Pythons Optionparser to define and read the options, and also 18 | # give some help to the user 19 | parser = OptionParser() 20 | usage = "usage: %prog [options] arg" 21 | parser.add_option('-v', '--kilovolt', dest='kV', 22 | type='float', 23 | help='set kV', 24 | metavar='90') 25 | parser.add_option('-a', '--milliamperesecond', dest='mAs', 26 | type='float', 27 | help='set mAs', 28 | metavar='125') 29 | parser.add_option('-d', '--distance', dest='FocusDistance', 30 | type='float', 31 | default=140., 32 | help='Focus distance in cm. Defaults to 140 cm', 33 | metavar='120') 34 | (options, args) = parser.parse_args() 35 | 36 | # show the help if no parameters are given 37 | if options.kV is None or options.mAs is None: 38 | parser.print_help() 39 | print 'Example:' 40 | print 'The command below calculates the surface entrance dose of a',\ 41 | '"Chest: PA standing", where the WHO manual gives 120 kV and 2 mAs',\ 42 | 'as base values. The distance is shortened fromt the default 140 cm',\ 43 | 'to 1 meter.' 44 | print 45 | print sys.argv[0], '-v 120 -a 2 -d 100' 46 | print 47 | sys.exit(1) 48 | 49 | # Parameters 50 | # The K-value is based on the machine. The BAG-calculator (see below) list 0.1 51 | K = 0.1 52 | # BSF as found by Arouna2000, cited by BAG2012. *This* BSF gives the same SED 53 | # values as the XLS-calculator from BAG (http://is.gd/oTpniQ) which I copied to 54 | # /afs/psi.ch/project/EssentialMed/PresentationsAndInfo/BAG/R-0 DRWCalc 5.0.xls 55 | BSF = 1.35 56 | # BSF as found in BAG2012. "Der ueber verschiedene Anlagen gemittelte 57 | # Korrekturfaktor betrug 1.15" 58 | # BSF = 1.15 59 | 60 | # calculating while converting Focusdistance from m to cm 61 | SED = K * (options.kV / 100) ** 2 * options.mAs *\ 62 | (100 / options.FocusDistance) ** 2 * BSF 63 | print 64 | print 'Calculating the surface entrance dose for an x-ray pulse with' 65 | print ' *', options.kV, 'kV' 66 | print ' *', options.mAs, 'mAs and' 67 | print ' *', options.FocusDistance / 100, 'm focal distance.' 68 | print ' * the characteristic constant K of the setup was set to', K,\ 69 | 'mGy/mA.' 70 | print ' * the back-scatter factor was set to', str(BSF) + '.' 71 | print 'The surface entrance dose is thus SED = K*(U/100)^2*Q*(1/FOD)^2*BSF=' +\ 72 | str(round(SED, 3)), 'mGy' 73 | -------------------------------------------------------------------------------- /Analysis/DisplayExperimentID.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | """ 4 | Script to display only some experiments, based on either choice of ID or choice 5 | of component 6 | """ 7 | 8 | import os 9 | import matplotlib.pyplot as plt 10 | import numpy as np 11 | from functions import estimate_image_noise 12 | 13 | ID = [ 14 | 8466438, 15 | 8466246, 16 | 8596690, 17 | 8595968, 18 | 5808743, 19 | 5814382, 20 | 5814190, 21 | 9078293, 22 | 9074884, 23 | 9071872, 24 | 8466170, 25 | 9080226, 26 | 7733379 27 | ] 28 | 29 | # ID = [123456] 30 | 31 | ID = [9078313] 32 | 33 | getfromHD = False 34 | if getfromHD: 35 | RootFolder = '/media/WINDOWS' 36 | else: 37 | RootFolder = '/afs/psi.ch/project/EssentialMed/MasterArbeitBFH/XrayImages' 38 | 39 | # Get all folders (Experiment) and ExperimentIDs inside StartingFolder 40 | print 'Looking for the folder with the given Experiment IDs ' 41 | Folder = [] 42 | ExperimentID = [] 43 | for root, dirs, files in os.walk(RootFolder): 44 | # Go through all the directories, see if the last foldername is a number, 45 | # and it's in the list of IDs above, 46 | # Go through all the directories 47 | try: 48 | # If the last foldername is a number, 49 | int(os.path.basename(root)) 50 | # see if it's in the list of IDs above, 51 | if int(os.path.basename(root)) in ID: 52 | print 'Experiment', os.path.basename(root), 'found in', \ 53 | os.path.dirname(root) 54 | Folder.append(root) 55 | ExperimentID.append(int(os.path.basename(root))) 56 | # print len(Folder), len(ExperimentID), len(ID) 57 | # otherwise just pass 58 | except ValueError: 59 | continue 60 | 61 | if getfromHD: 62 | print 'The experiment', ID, 'is in the folder', Folder, 'on', RootFolder 63 | exit() 64 | 65 | # Warn the user if we were not able to find an image... 66 | if len(ExperimentID) != len(ID): 67 | for i in ID: 68 | if i not in ExperimentID: 69 | print '\nExperiment ID', i, 'was not found in', RootFolder 70 | exit('Hope it is only a typo...') 71 | 72 | # Extract data from the images 73 | print 'Reading and preparing images' 74 | Images = [plt.imread(i + '.image.corrected.stretched.png') for i in Folder] 75 | Mean = [np.mean(i) * 255 for i in Images] 76 | STD = [np.std(i) * 255 for i in Images] 77 | Noise = [estimate_image_noise(i) * 255 for i in Images] 78 | 79 | print 'Mean goes from', round(min(Mean), 1), 'to', round(max(Mean), 1) 80 | print 'STD goes from', round(min(STD), 1), 'to', round(max(STD), 1) 81 | print 'Noise goes from', round(min(Noise), 1), 'to', round(max(Noise), 1) 82 | 83 | plt.figure(figsize=(20, 4)) 84 | for c, i in enumerate(Images): 85 | print 'Reading and preparing image', c, 'of', len(Folder) 86 | plt.subplot(1, len(Folder), c + 1) 87 | plt.imshow(i, cmap='bone') 88 | title = '\n'.join(['ID ' + str(ExperimentID[c]), 'Mean ' + str(round( 89 | Mean[c], 1)), 'STD ' + str(round(STD[c], 1)), 'Noise ' + 90 | str(round(Noise[c]))]) 91 | plt.title(title) 92 | plt.axis('off') 93 | plt.tight_layout() 94 | plt.show() 95 | -------------------------------------------------------------------------------- /nist/cesium.dat: -------------------------------------------------------------------------------- 1 | #http://physics.nist.gov/PhysRefData/XrayMassCoef/ComTab/cesium.html 2 | #Cesium Iodide 3 | #________________________________________ 4 | # 5 | # Energy μ/ρ μen/ρ 6 | # (MeV) (cm2/g) (cm2/g) 7 | #________________________________________ 8 | 9 | 1.00000E-03 9.234E+03 9.213E+03 10 | 1.03199E-03 8.653E+03 8.633E+03 11 | 1.06500E-03 8.098E+03 8.080E+03 12 | 55 M2 1.06500E-03 8.339E+03 8.320E+03 13 | 1.06854E-03 8.281E+03 8.262E+03 14 | 1.07210E-03 8.224E+03 8.205E+03 15 | 53 M1 1.07210E-03 8.387E+03 8.368E+03 16 | 1.14230E-03 7.344E+03 7.327E+03 17 | 1.21710E-03 6.413E+03 6.398E+03 18 | 55 M1 1.21710E-03 6.569E+03 6.553E+03 19 | 1.50000E-03 4.132E+03 4.120E+03 20 | 2.00000E-03 2.114E+03 2.104E+03 21 | 3.00000E-03 7.880E+02 7.809E+02 22 | 4.00000E-03 3.836E+02 3.776E+02 23 | 4.55710E-03 2.752E+02 2.696E+02 24 | 53 L3 4.55710E-03 5.174E+02 4.936E+02 25 | 4.70229E-03 4.851E+02 4.628E+02 26 | 4.85210E-03 4.510E+02 4.303E+02 27 | 53 L2 4.85210E-03 5.637E+02 5.332E+02 28 | 5.00000E-03 5.296E+02 5.012E+02 29 | 5.01190E-03 5.268E+02 4.985E+02 30 | 55 L3 5.01190E-03 7.511E+02 7.037E+02 31 | 5.09924E-03 7.193E+02 6.744E+02 32 | 5.18810E-03 6.881E+02 6.457E+02 33 | 53 L1 5.18810E-03 7.453E+02 6.987E+02 34 | 5.27305E-03 7.196E+02 6.716E+02 35 | 5.35940E-03 6.875E+02 6.454E+02 36 | 55 L2 5.35940E-03 7.923E+02 7.397E+02 37 | 5.53401E-03 7.323E+02 6.847E+02 38 | 5.71430E-03 6.761E+02 6.331E+02 39 | 55 L1 5.71430E-03 7.268E+02 6.795E+02 40 | 6.00000E-03 6.448E+02 6.043E+02 41 | 8.00000E-03 3.071E+02 2.906E+02 42 | 1.00000E-02 1.711E+02 1.624E+02 43 | 1.50000E-02 5.815E+01 5.486E+01 44 | 2.00000E-02 2.686E+01 2.496E+01 45 | 3.00000E-02 9.045E+00 8.071E+00 46 | 3.31694E-02 6.923E+00 6.088E+00 47 | 53 K 3.31694E-02 2.122E+01 9.086E+00 48 | 3.45483E-02 2.687E+01 8.529E+00 49 | 3.59846E-02 1.719E+01 7.990E+00 50 | 55 K 3.59846E-02 3.027E+01 1.059E+01 51 | 4.00000E-02 2.297E+01 9.395E+00 52 | 5.00000E-02 1.287E+01 6.596E+00 53 | 6.00000E-02 7.921E+00 4.586E+00 54 | 8.00000E-02 3.677E+00 2.399E+00 55 | 1.00000E-01 2.035E+00 1.391E+00 56 | 1.50000E-01 7.290E-01 4.951E-01 57 | 2.00000E-01 3.805E-01 2.401E-01 58 | 3.00000E-01 1.818E-01 9.634E-02 59 | 4.00000E-01 1.237E-01 5.828E-02 60 | 5.00000E-01 9.809E-02 4.366E-02 61 | 6.00000E-01 8.373E-02 3.657E-02 62 | 8.00000E-01 6.769E-02 2.987E-02 63 | 1.00000E+00 5.848E-02 2.657E-02 64 | 1.25000E+00 5.110E-02 2.402E-02 65 | 1.50000E+00 4.644E-02 2.243E-02 66 | 2.00000E+00 4.123E-02 2.089E-02 67 | 3.00000E+00 3.721E-02 2.059E-02 68 | 4.00000E+00 3.616E-02 2.144E-02 69 | 5.00000E+00 3.622E-02 2.255E-02 70 | 6.00000E+00 3.673E-02 2.364E-02 71 | 8.00000E+00 3.838E-02 2.563E-02 72 | 1.00000E+01 4.030E-02 2.725E-02 73 | 1.50000E+01 4.492E-02 2.992E-02 74 | 2.00000E+01 4.867E-02 3.114E-02 75 | -------------------------------------------------------------------------------- /Analysis/Comparator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | """ 4 | Script to compare the contents of Ivans HD with the stuff present on AFS. 5 | Due to space shortage, some folders might have been missed during copying. 6 | 7 | And since a large part of the folders were already processed, we cannot just 8 | use `rsync` or that will copy over all the files that already were deleted. 9 | So we use a little routine to find missing folders and rsync only those one 10 | after the other. 11 | This should also take care of the speed issue should the script to have to run 12 | several times. 13 | """ 14 | 15 | import os 16 | import subprocess 17 | import errno 18 | 19 | 20 | def readit(InputFolder): 21 | ''' 22 | Get all folders (Experiment) and ExperimentIDs inside StartingFolder 23 | ''' 24 | print 'Looking for Experiment ID folders in', InputFolder 25 | print 'This will take a while' 26 | Folder = [] 27 | ExperimentID = [] 28 | for root, dirs, files in os.walk(InputFolder): 29 | # Go through all the directories and see if the last foldername is a 30 | # number 31 | try: 32 | if int(os.path.basename(root)): 33 | Folder.append(root) 34 | ExperimentID.append(int(os.path.basename(root))) 35 | # otherwise just continue 36 | except ValueError: 37 | continue 38 | 39 | return Folder, ExperimentID 40 | 41 | # Read *all* experiment IDs from HD 42 | RootFolderHD = ('/media/WINDOWS/Aptina') 43 | FolderHD, IDHD = readit(RootFolderHD) 44 | 45 | # Read *all* experiment IDs from AFS 46 | RootFolderAFS = ('/afs/psi.ch/project/EssentialMed/MasterArbeitBFH/XrayImages') 47 | FolderAFS, IDAFS = readit(RootFolderAFS) 48 | 49 | for counter, experiment in enumerate(IDHD): 50 | print 5 * '-', '|', str(counter + 1) + '/' + str(len(IDHD)), '|', 60 * '-' 51 | if experiment not in IDAFS: 52 | InputPath = FolderHD[IDHD.index(experiment)] 53 | OutputPath = os.path.dirname(os.path.join(RootFolderAFS, 54 | FolderHD[IDHD.index(experiment)][len(RootFolderHD) + 1:])) 55 | print 'Experiment', experiment, 'was not found on AFS' 56 | print 57 | print 'It should be moved from' 58 | print InputPath 59 | print 'to' 60 | print OutputPath 61 | # rsync can only mkdir ONE level, so we first make the path to copy to 62 | try: 63 | os.makedirs(OutputPath) 64 | except OSError as e: 65 | if e.errno != errno.EEXIST: 66 | # If the error is not about the directory existing, raise it 67 | # again 68 | raise 69 | rsynccommand = ['rsync', '-ar', InputPath, OutputPath] 70 | print 71 | print 'rsyncing it now with the command' 72 | print ' '.join(rsynccommand) 73 | synchronizeit = subprocess.Popen(rsynccommand, stdout=subprocess.PIPE) 74 | output, error = synchronizeit.communicate() 75 | if error: 76 | print 'The below error happened' 77 | print error 78 | break 79 | else: 80 | print 'Experiment', experiment, 'from' 81 | print FolderHD[IDHD.index(experiment)] 82 | print 'is already on AFS at' 83 | print FolderAFS[IDAFS.index(experiment)] 84 | print 'Done with experiment', experiment 85 | 86 | print 80 * '-' 87 | print 'Done with all', len(IDHD), 'experiment IDs on', RootFolderHD 88 | -------------------------------------------------------------------------------- /GOTTHARD/Photon-Calculation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Plot Attenuation and transmission of GOTTHARD detector element 5 | """ 6 | 7 | from __future__ import division 8 | import matplotlib.pylab as plt 9 | import os 10 | import glob 11 | import numpy as np 12 | 13 | GOTTHARDArea = 1130 * (50 / 1000) * 2 # mm 14 | Distance = 163 # cm 15 | ScintillatorArea = 430 * 430 # mm 16 | print 'The area of the GOTTHARD sensor we used was', int(GOTTHARDArea), 'mm²' 17 | print 'This is', int(round(ScintillatorArea / GOTTHARDArea)), 'times smaller',\ 18 | 'than the scintillator we plan to use (430 x 430 mm²)' 19 | 20 | SiliconAttenuation = np.loadtxt('Si_Attenuation.dat') 21 | SiliconTransmission = np.loadtxt('Si_Transmission.dat') 22 | SiliconDensity = 2.329 # g/cm³ 23 | SiliconThickness = 320 # um 24 | 25 | plt.figure() 26 | plt.hold(True) 27 | plt.subplot(1, 2, 1) 28 | plt.plot(SiliconAttenuation[:, 0] * 1000, 29 | 1 - (np.exp(1) ** - (SiliconAttenuation[:, 1] * SiliconDensity * 30 | SiliconThickness / 10000)), color='k') 31 | plt.xlabel('Photon Energy [keV]') 32 | plt.rc('text', usetex=True) 33 | plt.rc('font', family='serif') 34 | plt.ylabel(r'Attenuation coefficient $\frac{\mu}{\rho}$ [cm2/g]') 35 | plt.title('Attenuation') 36 | plt.xlim([0, 120]) 37 | plt.ylim([0, 1]) 38 | 39 | from scipy import interpolate 40 | x = SiliconAttenuation[:, 0] * 1000 41 | y = (np.exp(- (SiliconAttenuation[:, 1] * SiliconDensity * SiliconThickness / 42 | 10000))) 43 | f1 = interpolate.interp1d(x, y) 44 | f2 = interpolate.interp1d(x, y, kind='cubic') 45 | 46 | xnew = np.arange(1, 120, 0.1) 47 | 48 | plt.subplot(1, 2, 2) 49 | plt.plot(SiliconTransmission[:, 0] / 1000, SiliconTransmission[:, 1], 50 | color='k', label='from Anna') 51 | plt.plot(SiliconAttenuation[:, 0] * 1000, 52 | (np.exp(- (SiliconAttenuation[:, 1] * SiliconDensity * 53 | SiliconThickness / 10000))), 'gD', 54 | label='from NIST (1-Attenuation)') 55 | plt.plot(xnew, f1(xnew) + 0.1, 'r', label='Int') 56 | plt.plot(xnew, f2(xnew) + 0.2, 'b', label='Int') 57 | # plt.legend(loc='best') 58 | plt.xlabel('Photon Energy [keV]') 59 | plt.ylabel('Tranmission') 60 | plt.title('Transmission for a thickness of 320 um') 61 | plt.xlim([0, 120]) 62 | # plt.ylim([0, 1]) 63 | 64 | # plt.savefig('Si_Attenuation_Transmission.pdf') 65 | plt.show() 66 | 67 | Spectrapath = '/afs/psi.ch/project/EssentialMed/Images/' \ 68 | 'GOTTHARD_and_TIS/GOTTHARD' 69 | Spectra = sorted(glob.glob(os.path.join(Spectrapath, '*.txt'))) 70 | 71 | FileName = [os.path.basename(item) for item in Spectra] 72 | Data = [np.loadtxt(item) for item in Spectra] 73 | DataName = [open(item).readlines()[0].split()[0][1:-2] for item in Spectra] 74 | 75 | # Get Filenames of Spectra and split it up into the desired values like kV, mAs 76 | # and exposure time with some basic string handling. 77 | Modality = [item.split('_')[0] for item in FileName] 78 | Energy = [int(item.split('_')[1][:-2]) for item in FileName] 79 | Current = [int(item.split('_')[2][:-2]) for item in FileName] 80 | mAs = [float(item.split('_')[3][:-3]) for item in FileName] 81 | ExposureTime = [int(item.split('_')[4][:-6]) for item in FileName] 82 | 83 | Frames = [open(item).readlines()[0].split()[1] for item in Spectra] 84 | BinCenter = [open(item).readlines()[1].split()[0] for item in Spectra] 85 | Photons = [open(item).readlines()[1].split()[1] for item in Spectra] 86 | PhotonsPerFrame = [open(item).readlines()[1].split()[2] for item in Spectra] 87 | -------------------------------------------------------------------------------- /DetectorMockup/ScintillatorBackplates.scad: -------------------------------------------------------------------------------- 1 | // Scintillator backplate 2 | 3 | // Basic length variables 4 | CenterSzintillator = [459/2, 380 - 80 - 172/2, 0]; 5 | screwheight = 20; 6 | screwradius = 3; 7 | 8 | // Toshiba Szintillator 9 | SizeToshiba = [236, 172, 20]; 10 | color ("black", 1) { 11 | translate(CenterSzintillator+SizeToshiba/2+[screwradius+2,-20,-SizeToshiba[2]]){ 12 | cylinder(h = screwheight , r=3); 13 | } 14 | translate(CenterSzintillator+SizeToshiba/2+[screwradius+2,-SizeToshiba[1]+20,-13]){ 15 | cylinder(h = screwheight , r=3); 16 | } 17 | translate(CenterSzintillator-SizeToshiba/2-[screwradius+2,-20,0]){ 18 | cylinder(h = screwheight , r=3); 19 | } 20 | translate(CenterSzintillator-SizeToshiba/2-[screwradius+2,-SizeToshiba[1]+20,0]){ 21 | cylinder(h = screwheight , r=3); 22 | } 23 | translate(CenterSzintillator-SizeToshiba/2-[-20,-SizeToshiba[1]-screwradius,0]){ 24 | cylinder(h = screwheight , r=3); 25 | } 26 | translate(CenterSzintillator-SizeToshiba/2-[-SizeToshiba[0]+20,-SizeToshiba[1]-screwradius,0]){ 27 | cylinder(h = screwheight , r=3); 28 | } 29 | } 30 | difference(){ 31 | cube([459, 380, 6]); 32 | translate(CenterSzintillator-SizeToshiba/2){ 33 | cube(SizeToshiba); 34 | } 35 | } 36 | 37 | // Pingseng Szintillator 38 | SizePinseng = [200, 200, 20]; 39 | translate([500,0,0]){ 40 | color ("black", 1) { 41 | translate(CenterSzintillator+SizePinseng/2+[screwradius+2,-20,-20]){ 42 | cylinder(h = screwheight , r=3); 43 | } 44 | translate(CenterSzintillator+SizePinseng/2+[screwradius+2,-SizePinseng[1]+20,-SizePinseng[2]]){ 45 | cylinder(h = screwheight , r=3); 46 | } 47 | translate(CenterSzintillator-SizePinseng/2-[screwradius+2,-20,0]){ 48 | cylinder(h = screwheight , r=3); 49 | } 50 | translate(CenterSzintillator-SizePinseng/2-[screwradius+2,-SizePinseng[1]+20,0]){ 51 | cylinder(h = screwheight , r=3); 52 | } 53 | translate(CenterSzintillator-SizePinseng/2-[-20,-SizePinseng[1]-screwradius,0]){ 54 | cylinder(h = screwheight , r=3); 55 | } 56 | translate(CenterSzintillator-SizePinseng/2-[-SizePinseng[0]+20,-SizePinseng[1]-screwradius,0]){ 57 | cylinder(h = screwheight , r=3); 58 | } 59 | } 60 | difference(){ 61 | cube([459, 380, 6]); 62 | translate(CenterSzintillator-SizePinseng/2){ 63 | cube(SizePinseng); 64 | } 65 | } 66 | } 67 | 68 | // Applied Scintillation Technologies Szintillator 69 | SizeAppScinTech = [106, 106, 20]; 70 | translate([1000,0,0]){ 71 | color ("black", 1) { 72 | translate(CenterSzintillator+SizeAppScinTech/2+[screwradius+2,-20,-20]){ 73 | cylinder(h = screwheight , r=3); 74 | } 75 | translate(CenterSzintillator+SizeAppScinTech/2+[screwradius+2,-SizeAppScinTech[1]+20,-SizeAppScinTech[2]]){ 76 | cylinder(h = screwheight , r=3); 77 | } 78 | translate(CenterSzintillator-SizeAppScinTech/2-[screwradius+2,-20,0]){ 79 | cylinder(h = screwheight , r=3); 80 | } 81 | translate(CenterSzintillator-SizeAppScinTech/2-[screwradius+2,-SizeAppScinTech[1]+20,0]){ 82 | cylinder(h = screwheight , r=3); 83 | } 84 | translate(CenterSzintillator-SizeAppScinTech/2-[-20,-SizeAppScinTech[1]-screwradius,0]){ 85 | cylinder(h = screwheight , r=3); 86 | } 87 | translate(CenterSzintillator-SizeAppScinTech/2-[-SizeAppScinTech[0]+20,-SizeAppScinTech[1]-screwradius, 0]){ 88 | cylinder(h = screwheight , r=3); 89 | } 90 | } 91 | difference(){ 92 | cube([459, 380, 6]); 93 | translate(CenterSzintillator-SizeAppScinTech/2){ 94 | cube(SizeAppScinTech); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Demonstrator/darkimages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to analyze the "dark" images from the experiments in the EssentialLab 5 | """ 6 | 7 | import glob 8 | import os 9 | import numpy 10 | import matplotlib.pylab as plt 11 | import matplotlib.patches as patches 12 | 13 | 14 | def processimage(inputimage, clip=3): 15 | """ 16 | Clip image brightness to "mean +- 3 STD" (by default). Another value can 17 | be given. This is applied to the input images if the -c commandline 18 | parameter is given. 19 | """ 20 | return numpy.clip(inputimage, 21 | numpy.mean(inputimage) - (clip * numpy.std(inputimage)), 22 | numpy.mean(inputimage) + (clip * numpy.std(inputimage))) 23 | 24 | 25 | def my_display_histogram(img, howmanybins=128, histogramcolor='k', 26 | rangecolor='r', clip=3): 27 | """ 28 | Display the histogram of an input image, including the ranges we clip 29 | the gray values to 30 | """ 31 | plt.hist(img.flatten(), bins=howmanybins, histtype='stepfilled', 32 | fc=histogramcolor, alpha=0.309) 33 | plt.axvline(x=numpy.mean(img) - clip * numpy.std(img), color=rangecolor, 34 | linestyle='--') 35 | plt.axvline(x=numpy.mean(img), color='k', linestyle='--') 36 | plt.axvline(x=numpy.mean(img) + clip * numpy.std(img), 37 | color=rangecolor, linestyle='--') 38 | # turn off y-ticks: http://stackoverflow.com/a/2176591/323100 39 | plt.gca().axes.get_yaxis().set_ticks([]) 40 | plt.title('Histogram. Black = mean\nRed = Display range') 41 | 42 | BasePath = '/afs/psi.ch/user/h/haberthuer/EssentialMed/Images/' \ 43 | 'DetectorElectronicsTests/EssentialLab/' 44 | 45 | FileNames = sorted([item for item in glob.glob(os.path.join(BasePath, '*', 46 | '*36.gray'))]) 47 | 48 | # Select only dark images 49 | DarkFolderList = ['1421327978_noxray', 50 | '1421327947_noxray', 51 | '1421156423_output', 52 | '1421155907_output'] 53 | DarkFileNames = [] 54 | for i in FileNames: 55 | if os.path.split(os.path.dirname(i))[1] in DarkFolderList: 56 | DarkFileNames.append(i) 57 | 58 | print 'Reading all %s images' % len(FileNames) 59 | Images = [numpy.fromfile(item, dtype=numpy.uint16, count=-1, 60 | sep='').reshape(1024, 1280) for item in FileNames] 61 | print 'Reading only %s dark images' % len(DarkFileNames) 62 | DarkImages = [numpy.fromfile(item, dtype=numpy.uint16, count=-1, 63 | sep='').reshape(1024, 1280) for item in 64 | DarkFileNames] 65 | 66 | plt.figure('dark images', figsize=[16, 9]) 67 | for counter, image in enumerate(DarkImages): 68 | plt.subplot(3, len(DarkImages), counter + 1) 69 | plt.imshow(processimage(image), interpolation='bicubic', cmap='gray_r') 70 | Zoom = patches.Rectangle((333, 456), width=100, height=50, color='g', 71 | alpha=0.618) 72 | plt.gcf().gca().add_patch(Zoom) 73 | 74 | FigureTitle = os.path.basename(os.path.split(FileNames[counter])[0]), \ 75 | '\n', os.path.basename(FileNames[counter]), '\nmean',\ 76 | str(round(numpy.mean(processimage(image)))), '\nSTD', \ 77 | str(round(numpy.std(processimage(image)))) 78 | plt.title(' '.join(FigureTitle)) 79 | plt.axis('off') 80 | plt.subplot(3, len(DarkImages), counter + len(DarkImages) + 1) 81 | plt.imshow(processimage(image)[333:333 + 50, 456:456 + 100], 82 | interpolation='nearest', cmap='gray_r') 83 | plt.title('Zoomed region') 84 | plt.axis('off') 85 | plt.subplot(3, len(DarkImages), counter + 2 * len(DarkImages) + 1) 86 | my_display_histogram(image) 87 | plt.xlim([0, 256]) 88 | 89 | plt.show() 90 | -------------------------------------------------------------------------------- /aptina/data/board_data/DualMipiFPGA.cdat: -------------------------------------------------------------------------------- 1 | // Auto Generated by RegisterDefinitions.pl script. 2 | // REGDEF = {ADDR, TYPE, MASK, RW, DEFAULT, DESC, DETAIL} 3 | // {BITDEF, MASK, RW, DESC, DETAIL} 4 | // {BITDEF, MASK, RW, DESC, DETAIL} 5 | // ... 6 | // {BITDEF, MASK, RW, DESC, DETAIL} 7 | 8 | [CHIP_DESCRIPTOR] 9 | CHIPNAME = "DualMipiFPGA" 10 | SERIAL_BASE_ADDRESS = 0xCA 11 | SERIAL_DATA_SIZE = 16 12 | [END] 13 | 14 | [Registers] 15 | ControlReg1 = {0x00, FPGA, 0xFFF3, RW, 0x0000, "Control Reg 1", ""} 16 | {MODE_SEL, 0x0003, RW, "1-0: Board operating mode", "0: DIPSwitch controlled, 1: Sensor Parallel Mode, 2: MIPI, 3: CCP"} 17 | {MAX_MIPI_LANES, 0x000C, RO, "3-2: Number of instantiated MIPI lanes", "0: 1 lane, 1: 2 lanes, 2: illegal, 3: 4 lanes"} 18 | {LANES, 0x0030, RW, "5-4: Number of active MIPI lanes", "0: 1 lane, 1: 2 lanes, 2: illegal, 3: 4 lanes"} 19 | {VIRTUAL_CHANNEL, 0x00C0, RW, "7:6: Virtual MIPI channel", ""} 20 | {DATA_SIZE, 0x0700, RW, "10-8: CCP data width", "0: 8 bit, 1: 10 bit, 2: 12 bit 7-3: reserved"} 21 | {EMBEDDED_DATA_EN, 0x0800, RW, "11: Disable Mipi embedded data", "1: disable"} 22 | {CCP_CLASS, 0x1000, RW, "12: CCP class select", "0: Class 0, 1:Class 1/2"} 23 | {OUTPUT_EN, 0x8000, RW, "15: Disable FPGA outputs", "1: disable"} 24 | ControlReg2 = {0x01, FPGA, 0xFFFF, RO, 0x0000, "Control Reg 2", ""} 25 | {DIPSW, 0x00FF, RO, "DIPSwitch", ""} 26 | {ACTIVE_MODE, 0x0300, RO, "Current board operating mode", "0: Disabled, 1: Sensor Parallel Mode, 2: MIPI, 3: CCP"} 27 | {ACTIVE_DATA_SIZE, 0x1C00, RO, "Current CCP data width", "0: 8 bit, 1: 10 bit, 2: 12 bit 7-3: reserved"} 28 | {ACTIVE_CCP_CLASS, 0x2000, RO, "Current CCP class", "0: Class 0, 1:Class 1/2"} 29 | {ACTIVE_MIPI_LANES, 0xC000, RO, "Current active Mipi lanes", "0: 1 lane, 1: 2 lanes, 2: illegal, 3: 4 lanes"} 30 | Checksum = {0x02, FPGA, 0xFFFF, RO, 0x0000, "Video Checksum readback", "Video checksum for last ccp or mipi frame"} 31 | ErrorStatus = {0x03, FPGA, 0xFFFF, RW, 0x0000, "Error Status", ""} 32 | {CRC_ERROR, 0x0001, RO, "0: CRC Error", "1: error"} 33 | {CRC_ERROR_CLR, 0x0002, RW, "1: Clear CRC error", "1: clear CRC_ERROR bit"} 34 | {LINE_FIFO_OVFL, 0x0010, RO, "4: Line FIFO Overflow", "1: overflow"} 35 | {LINE_FIFO_OVFL_CLR, 0x0020, RW, "5: Clear Line FIFO Overflow", "1: clear LINE_FIFO_OVFL bit"} 36 | CHIP_VERSION_REG = {0x08, FPGA, 0xFFFF, RO, 0x0010, "Chip Version", "Chip Version "} 37 | VERSION = {0x12, FPGA, 0xFFFF, RO, 0x0010, "FPGA Version", "FPGA Version "} 38 | {VER_MINOR, 0x000F, RO, "3-0: Firmware Minor Version #", ""} 39 | {VER_MAJOR, 0x00F0, RO, "7-4: Firmware Major Version #", ""} 40 | {SUPER_MAJOR, 0xFF00, RO, "15-8: Firmware Super Major Version ", ""} 41 | DATESTAMPREG = {0x14, FPGA, 0xFFFF, RO, 0x0000, "Firmware Build DateStamp", "m/d/y"} 42 | TIMESTAMPREG = {0x15, FPGA, 0xFFFF, RO, 0x0000, "Firmware Build TimeStamp", "h/m/s"} 43 | [END] -------------------------------------------------------------------------------- /Demonstrator/lineprofiler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Line profile function used in several scripts. 5 | """ 6 | 7 | 8 | def lineprofile(inputimage, coordinates=False, showimage=False, debug=False): 9 | """ 10 | Function to draw a line profile from a selection in the image. 11 | Comes in handy to look at the resolution phantom in x-ray images shot with 12 | the iAi electronics prototype in the x-ray lab. 13 | Based on http://stackoverflow.com/a/7880726/323100 14 | If the user supplies coordinates (in the form of coordinates = 15 | ((x0,y0), (x1,y1)), we use those, otherwise we let the user select some. 16 | If "debug" is set to true, we show Lena. 17 | 18 | The function returns the coordinates and the lineprofile of the input image 19 | along these coordinates. 20 | """ 21 | 22 | # suppress pep8 warning about potentially unreferenced variables 23 | global x1, x0, y1, y0 24 | 25 | import numpy 26 | import scipy.ndimage 27 | import matplotlib.pyplot as plt 28 | import random 29 | 30 | # Debug 31 | if debug: 32 | inputimage = scipy.misc.lena() 33 | 34 | if coordinates is False: 35 | showimage = True 36 | 37 | # Prepare image 38 | if showimage: 39 | # make large figure numbers, so we don't get into troubles with 40 | # plotting to other figure numbers 41 | plt.figure(random.randint(500, 1000), figsize=(16, 16)) 42 | if debug: 43 | plt.ion() 44 | plt.imshow(inputimage, cmap='bone', vmin=numpy.min(inputimage), 45 | vmax=numpy.mean(inputimage) + 3 * numpy.std(inputimage)) 46 | plt.title('Please select the end-points for the line-profile') 47 | 48 | if coordinates: 49 | # The user gave coordinates to use. Use them. 50 | x0 = coordinates[0][0] 51 | y0 = coordinates[0][1] 52 | x1 = coordinates[1][0] 53 | y1 = coordinates[1][1] 54 | else: 55 | # Let the user select a line to plot the profile from 56 | pts = [] 57 | while len(pts) < 2: 58 | pts = numpy.asarray(plt.ginput(2, timeout=-1)) 59 | x0 = pts[1, 0] 60 | x1 = pts[0, 0] 61 | y0 = pts[1, 1] 62 | y1 = pts[0, 1] 63 | 64 | # Interpolate a line between x0, y0 and x1, y1 with double the length of 65 | # the line 66 | length = 2 * int(numpy.hypot(x1 - x0, y1 - y0)) 67 | x, y = numpy.linspace(x0, x1, length), numpy.linspace(y0, y1, length) 68 | 69 | # Extract the values along the line, using cubic interpolation 70 | profileinterpolated = scipy.ndimage.map_coordinates(numpy.transpose( 71 | inputimage), numpy.vstack((x, y))) 72 | # Just do nearest-neighbour value extraction 73 | profilenn = numpy.transpose(inputimage)[x.astype(numpy.int), 74 | y.astype(numpy.int)] 75 | 76 | if showimage: 77 | # Draw the image and line profile again 78 | plt.subplot(211) 79 | plt.imshow(inputimage, cmap='bone', vmin=numpy.min(inputimage), 80 | vmax=numpy.mean(inputimage) + 3 * numpy.std(inputimage)) 81 | plt.plot((x0, x1), (y0, y1), 'r') 82 | plt.plot(x0, y0, 'yo') 83 | plt.plot(x1, y1, 'ko') 84 | plt.axis('image') 85 | plt.draw() 86 | 87 | plt.subplot(212) 88 | plt.plot(profileinterpolated, 'red', label='interpolated') 89 | plt.plot(profilenn, 'orange', label='nearest neighbour') 90 | plt.plot(0, profilenn[0], 'yo', markersize=25, alpha=0.309) 91 | plt.plot(len(profilenn) - 1, profilenn[-1], 'ko', markersize=25, 92 | alpha=0.309) 93 | plt.xlim([0, len(profilenn) - 1]) 94 | plt.legend(loc='best') 95 | plt.draw() 96 | plt.ioff() 97 | if debug: 98 | plt.show() 99 | return ((x0, y0), (x1, y1)), profileinterpolated 100 | -------------------------------------------------------------------------------- /.screenrc: -------------------------------------------------------------------------------- 1 | # 2 | # Example of a user's .screenrc file 3 | # 4 | 5 | # below is from http://is.gd/YxpfN6 6 | caption always "%{= kw}%?%-Lw%?%{Kw}%n*%f %t%?(%u)%?%{kw}%?%+Lw%? %=%{= dw} %H " 7 | # 8 | 9 | hardstatus alwayslastline 10 | 11 | # This is how one can set a reattach password: 12 | # password ODSJQf.4IJN7E # "1234" 13 | 14 | # no annoying audible bell, please 15 | vbell on 16 | 17 | # detach on hangup 18 | autodetach on 19 | 20 | # don't display the copyright page 21 | startup_message off 22 | 23 | # emulate .logout message 24 | pow_detach_msg "Screen session of \$LOGNAME \$:cr:\$:nl:ended." 25 | 26 | # advertise hardstatus support to $TERMCAP 27 | # termcapinfo * '' 'hs:ts=\E_:fs=\E\\:ds=\E_\E\\' 28 | 29 | # make the shell in every window a login shell 30 | #shell -$SHELL 31 | 32 | # autoaka testing 33 | # shellaka '> |tcsh' 34 | # shellaka '$ |sh' 35 | 36 | # set every new windows hardstatus line to somenthing descriptive 37 | # defhstatus "screen: ^En (^Et)" 38 | 39 | defscrollback 1000 40 | 41 | # don't kill window after the process died 42 | # zombie "^[" 43 | 44 | ################ 45 | # 46 | # xterm tweaks 47 | # 48 | 49 | #xterm understands both im/ic and doesn't have a status line. 50 | #Note: Do not specify im and ic in the real termcap/info file as 51 | #some programs (e.g. vi) will not work anymore. 52 | termcap xterm hs@:cs=\E[%i%d;%dr:im=\E[4h:ei=\E[4l 53 | terminfo xterm hs@:cs=\E[%i%p1%d;%p2%dr:im=\E[4h:ei=\E[4l 54 | 55 | #80/132 column switching must be enabled for ^AW to work 56 | #change init sequence to not switch width 57 | termcapinfo xterm Z0=\E[?3h:Z1=\E[?3l:is=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;4;6l 58 | 59 | # Make the output buffer large for (fast) xterms. 60 | termcapinfo xterm* OL=10000 61 | 62 | # tell screen that xterm can switch to dark background and has function 63 | # keys. 64 | termcapinfo xterm 'VR=\E[?5h:VN=\E[?5l' 65 | termcapinfo xterm 'k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~' 66 | termcapinfo xterm 'kh=\E[1~:kI=\E[2~:kD=\E[3~:kH=\E[4~:kP=\E[H:kN=\E[6~' 67 | 68 | # special xterm hardstatus: use the window title. 69 | termcapinfo xterm 'hs:ts=\E]2;:fs=\007:ds=\E]2;screen\007' 70 | 71 | #terminfo xterm 'vb=\E[?5h$<200/>\E[?5l' 72 | termcapinfo xterm 'vi=\E[?25l:ve=\E[34h\E[?25h:vs=\E[34l' 73 | 74 | # emulate part of the 'K' charset 75 | termcapinfo xterm 'XC=K%,%\E(B,[\304,\\\\\326,]\334,{\344,|\366,}\374,~\337' 76 | 77 | # xterm-52 tweaks: 78 | # - uses background color for delete operations 79 | termcapinfo xterm ut 80 | 81 | ################ 82 | # 83 | # wyse terminals 84 | # 85 | 86 | #wyse-75-42 must have flow control (xo = "terminal uses xon/xoff") 87 | #essential to have it here, as this is a slow terminal. 88 | termcapinfo wy75-42 xo:hs@ 89 | 90 | # New termcap sequences for cursor application mode. 91 | termcapinfo wy* CS=\E[?1h:CE=\E[?1l:vi=\E[?25l:ve=\E[?25h:VR=\E[?5h:VN=\E[?5l:cb=\E[1K:CD=\E[1J 92 | 93 | ################ 94 | # 95 | # other terminals 96 | # 97 | 98 | #make hp700 termcap/info better 99 | termcapinfo hp700 'Z0=\E[?3h:Z1=\E[?3l:hs:ts=\E[62"p\E[0$~\E[2$~\E[1$}:fs=\E[0}\E[61"p:ds=\E[62"p\E[1$~\E[61"p:ic@' 100 | 101 | # Extend the vt100 desciption by some sequences. 102 | termcap vt100* ms:AL=\E[%dL:DL=\E[%dM:UP=\E[%dA:DO=\E[%dB:LE=\E[%dD:RI=\E[%dC 103 | terminfo vt100* ms:AL=\E[%p1%dL:DL=\E[%p1%dM:UP=\E[%p1%dA:DO=\E[%p1%dB:LE=\E[%p1%dD:RI=\E[%p1%dC 104 | 105 | 106 | ################ 107 | # 108 | # keybindings 109 | # 110 | 111 | #remove some stupid / dangerous key bindings 112 | bind k 113 | bind ^k 114 | bind . 115 | bind ^\ 116 | bind \\ 117 | bind ^h 118 | bind h 119 | #make them better 120 | bind 'K' kill 121 | bind 'I' login on 122 | bind 'O' login off 123 | bind '}' history 124 | 125 | # Yet another hack: 126 | # Prepend/append register [/] to the paste if ^a^] is pressed. 127 | # This lets me have autoindent mode in vi. 128 | register [ "\033:se noai\015a" 129 | register ] "\033:se ai\015a" 130 | bind ^] paste [.] 131 | 132 | ################ 133 | # 134 | # default windows 135 | # 136 | 137 | # screen -t local 0 138 | # screen -t mail 1 elm 139 | # screen -t 40 2 rlogin faui40 140 | 141 | # caption always "%3n %t%? @%u%?%? [%h]%?" 142 | # hardstatus alwaysignore 143 | # hardstatus alwayslastline "%w" 144 | 145 | -------------------------------------------------------------------------------- /elphel/globaldiagnostix.php: -------------------------------------------------------------------------------- 1 | $value) { 13 | $parameters[$key] = convert($value); 14 | } 15 | 16 | // parameters are set X frames in the future 17 | if (isset($_GET['framedelay'])) 18 | { 19 | $framedelay = $_GET['framedelay']; 20 | } 21 | else 22 | $framedelay = 3; // default framedelay is 3 23 | 24 | function convert($s) { 25 | // clean up 26 | $s = trim($s, "\" "); 27 | // check if value is in HEX 28 | if(strtoupper(substr($s, 0, 2))=="0X") 29 | return intval(hexdec($s)); 30 | else 31 | return intval($s); 32 | } 33 | 34 | /* 35 | get the current URL [http://is.gd/vllbf] and use 36 | echo print_r($_SERVER); 37 | to see all the variables available (as seen in the first comment on 38 | http://stackoverflow.com/a/189113/323100). 39 | */ 40 | 41 | // Save CameraIP/phpfile into variable for re-use 42 | $url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 43 | 44 | // Give out some HTML code, so that we actually have a page to look at 45 | echo "\n\n GlobalDiagnostiX - PHP\n\n\n"; 46 | echo "

GlobalDiagnostiX exposure settings page

\n"; 47 | echo "

Image before doing anything

\n"; 48 | echo "\nImage before settings change\n
\n"; # go to the 'last' frame, go back '$framedelay +1' frames and show this 'img'. This should be the one image before setting anything (or one before that). 49 | echo "Image ".elphel_get_frame()." with"; 50 | echo ""; 59 | echo ".
\n"; 60 | 61 | // only show parameters if we are actually setting anything, i.e when $parameters is not empty 62 | if ( !empty( $parameters ) ) 63 | { 64 | echo "

Setting parameters for camera

\n"; 65 | echo "
\n"; 66 | echo "The setting parameters from the URL are
\n"; print_r($parameters); echo "

\n"; 67 | } 68 | echo "
\n"; 69 | 70 | if ( isset ( $parameters["exposure"] ) ) 71 | { 72 | elphel_set_P_value(ELPHEL_AUTOEXP_ON,0); // turn off autoexposure if we're setting the exposure manually. 73 | elphel_set_P_value(ELPHEL_EXPOS,($parameters['exposure'] * 1000 )); // input is in msec, set is in usec 74 | } 75 | 76 | echo "

Image after updated settings

\n"; 77 | // wait for at least three frames for the setting from above to stick 78 | // it seems that wait_frame is not enough, we need to do skip_frames. Maybe this is because of the way PHP works... 79 | elphel_skip_frames($framedelay); 80 | 81 | echo "\nImage after settings change\n
\n"; 82 | echo "Image ".elphel_get_frame()." with"; 83 | echo ""; 92 | echo ".
\n"; 93 | 94 | echo "
\n"; 95 | /* 96 | echo "Click the links here to turn AE either 'ON' or 'OFF'.
\n"; 97 | */ 98 | echo "\n\n"; 99 | 100 | // set autoexposure to what the user requested with "URL?autoexposure=bool" 101 | if ( isset ( $parameters["autoexposure"] ) ) 102 | { 103 | elphel_set_P_value(ELPHEL_AUTOEXP_ON,$parameters["autoexposure"]); 104 | } 105 | elphel_skip_frames($framedelay); 106 | 107 | ?> 108 | -------------------------------------------------------------------------------- /aptina/README.md: -------------------------------------------------------------------------------- 1 | # How to setup DevWareX on Ubuntu 12.04 2 | 3 | ## Install Python 3 4 | - Follow the [LinuxInstructions] from Aptina on their Atalassian-page, namely 5 | - `wget http://www.python.org/ftp/python/3.3.2/Python-3.3.2.tar.xz` 6 | - `tar -xvf Python-3.3.2.tar.xz` 7 | - `cd Python-3.3.2` 8 | - `./configure --enable-shared --prefix=/usr && make && make install` 9 | - `sudo ln -s /Library/Frameworks/Python.framework/Versions/3.3/Python /usr/lib/libpython3.3m.dylib` 10 | 11 | ## Install DevWare 12 | - `sudo apt-get install libtbb-dev` to install a necessary [library] 13 | - Download a recent version from the [DevSuite]-website, either manually or with this command, which downloads Version 1.9 for Linux32, unpacks it and starts the installation: 14 | `wget https://aptina.atlassian.net/wiki/download/attachments/11501573/DevWareX_Linux32_1_9.tar.gz;tar -xvf DevWare*.tar;./Developer` 15 | 16 | ## Get sensor and board files 17 | ### Very Easy 18 | - Get the files from your Aptina representative, save them to the `data` directory. 19 | 20 | ### Easy 21 | Since you've probably already checked out the [GlobalDiagnostiX repository][GDXrepo] or are working at PSI, you can just symlink the necessary files. 22 | - `cd` into the `data` directory inside the directory were you installed the DevSuite. 23 | - `ln -s /afs/psi.ch/project/EssentialMed/Dev/aptina/data/* .` 24 | 25 | ### Harder 26 | You can only check out the files you need from the [GlobaldiagnostiX repository][GDXrepo]. Although you probably want to go the *Very Easy* or *Easy route*... 27 | - `cd` into the directory were you installed the DevSuite. 28 | - `rm -r data` to remove the original `data` directory. 29 | - `git init data;cd data` to make a new Git repository. 30 | - `git remote add -f origin git@github.com:habi/GlobalDiagnostiX.git` to add the original repository as remote. 31 | - `git config core.sparsecheckout true;mkdir .git/info` to enable sparse checkout 32 | - `echo aptina/data/apps_data/ >> .git/info/sparse-checkout;echo aptina/data/board_data/ >> .git/info/sparse-checkout;echo aptina/data/sensor_data/ >> .git/info/sparse-checkout` to add the necessary files to the desired files to checkout. 33 | - `git pull origin master` to get them 34 | - `mv aptina/data/* .;rm -r aptina` to remove some cruft 35 | 36 | ## Start the DevSuite 37 | - `cd PATH_TO_DEVSUITE` and start it with `./DevWareX.exe` 38 | 39 | [LinuxInstructions]: https://aptina.atlassian.net/wiki/display/DEVS/DevWareX+Installation+Instructions+-+Linux 40 | [library]: http://packages.ubuntu.com/precise/libtbb-dev 41 | [DevSuite]: https://aptina.atlassian.net/wiki/display/DEVS/Software+Downloads 42 | [GDXrepo]: https://github.com/habi/GlobalDiagnostiX 43 | 44 | # How to setup DevWareX on Mac OS X 45 | ## Install Python 3 46 | - Follow the [MacInstructions] from Aptina on their Atalassian-page, namely 47 | - `cd; cd Downloads` 48 | - `wget https://www.python.org/ftp/python/3.3.2/python-3.3.2-macosx10.6.dmg; open python-3.3.2-macosx10.6.dmg` 49 | - Double-click on "Python.mpkg" and install Python 3. 50 | - `sudo ln -s /Library/Frameworks/Python.framework/Versions/3.3/Python /usr/lib/libpython3.3m.dylib` 51 | 52 | ## Install DevWare 53 | - `cd; cd Downloads` 54 | - `wget https://aptina.atlassian.net/wiki/download/attachments/11501573/DevWareX_MacOSX_1_8.dmg; open DevWareX_MacOSX_1_8.dmg` 55 | - Double-click on "Developer" ton install DevWare 56 | - `cd` into the `data` directory inside the directory were you installed the DevSuite. 57 | 58 | ## Get sensor and board files 59 | ### Very Easy 60 | - Get the files from your Aptina representative, save them to the `data` directory. 61 | 62 | ### Easy 63 | Since you've probably already checked out the [GlobalDiagnostiX repository][GDXrepo] or are working at PSI, you can just symlink the necessary files. 64 | 65 | - `cd` into the directory inside the directory were you installed the DevSuite, probably something like `cd /Applications/Aptina_DevWare` 66 | - If you're (always) connected to AFS, you can just enter `ln -s /afs/psi.ch/project/EssentialMed/Dev/aptina/data/* .` 67 | - Otherwise, if you're not connected to AFS, you just symlink the [data] directory of your checked out repository to the base directory of the DevWare installation. 68 | 69 | ## Start the DevSuite 70 | - `open /Applications/Aptina_DevWare/DevWareX.app/`, because you'll need the command-line output! 71 | 72 | You'll also need to associate the INI and SDAT files to "TextEditor" for various DevWareX features to work correctly: 73 | 74 | - Select any .ini file 75 | - `⌘+i` and select "Open With" and under "Applications" select "TextEdit". 76 | - Repeat these steps on any .xsdat file. 77 | 78 | [MacInstructions]: https://aptina.atlassian.net/wiki/display/DEVS/DevWareX+Installation+Instructions+-+MacOS 79 | [data]: https://github.com/habi/GlobalDiagnostiX/tree/master/aptina/data 80 | 81 | -------------------------------------------------------------------------------- /SurfaceEntranceDose.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script used to calculate the surface entrance dose of a certain x-ray 5 | measurement. Gives the same results as the 'Diagnostische Referenzwerte' 6 | Excel calculator on the BAG-page, in the right side-bar of http://is.gd/E2qIPA. 7 | The calculation is based on 'Merkblatt R-06-04' from BAG. 8 | """ 9 | 10 | import numpy as np 11 | import matplotlib.pylab as plt 12 | from mpl_toolkits.mplot3d import Axes3D 13 | 14 | plt.ion() 15 | 16 | # Parameters 17 | 18 | # The K-value is based on the machine. The BAG-calculator (see below) list 0.1 19 | K = 0.1 20 | FocusDistance = 1.4 21 | # RSF as found by Arouna2000, cited by BAG2012. *This* RSF gives the same SED 22 | # values as the XLS-calculator from BAG (http://is.gd/oTpniQ) which I copied to 23 | # /afs/psi.ch/project/EssentialMed/PresentationsAndInfo/BAG/R-0 DRWCalc 5.0.xls 24 | RSF = 1.35 25 | # RSF as found in BAG2012. "Der über verschiedene Anlagen gemittelte 26 | # Korrekturfaktor betrug 1.15" 27 | # RSF = 1.15 28 | 29 | Range_kV = range(30, 130) 30 | Range_mAs = range(0, 60) 31 | 32 | # Using *bloody* list comprehension in Python as specified by 33 | # http://stackoverflow.com/a/2397150/323100 Why do I always forget this? 34 | Dose = np.asarray([[K * 35 | (float(kV) / 100) ** 2 36 | * mAs * 37 | (1 / FocusDistance) ** 2 38 | * RSF 39 | for kV in Range_kV] for mAs in Range_mAs]) 40 | 41 | # Give out fixed data 42 | print '---' 43 | print 'The characteristic constant of the x-ray setup is', K, 'mGy/mA, the' 44 | print 'focus-surface-distance is', FocusDistance, 'm, the "Rückstreufaktor"' 45 | print 'RSF was set to', str(RSF) + '.' 46 | print 47 | print 'If we calculate the surface entrance dose' 48 | print 'SED = K*(U/100)^2*Q*(1/FOD)^2*RSF' 49 | 50 | # Give out plot and varying data 51 | # Surfaceplot based on http://stackoverflow.com/a/3812324/323100, since we only 52 | # have matplotlib 0.99.1.1 installed here at PSI. Check the installed version 53 | # with "python -c 'import matplotlib; print matplotlib.__version__'" in your 54 | # terminal. 55 | fig = plt.figure() 56 | ax = Axes3D(fig) 57 | X, Y = np.meshgrid(Range_kV, Range_mAs) 58 | ax.plot_surface(X, Y, Dose, cmap='jet', cstride=1, rstride=1, linewidth=0) 59 | 60 | showCase = 1 61 | if showCase == 1: 62 | # Multiple Values, including Zhentians Breast scan 63 | showkV = (46., 70., 120., 125., 40.) 64 | showmAs = (50., 2, 50., 2., 25.) 65 | what = ('Wrist 1', 'Wrist 2', 'LWS ap', 'Thorax', 'Zhentian') 66 | elif showCase == 2: 67 | # General values for radiology (from Heinz and R-06-04). Used in 68 | # movie in presentation 69 | showkV = (46., 70., 120., 125.) 70 | showmAs = (50., 2, 50., 2.) 71 | what = ('Wrist 1', 'Wrist 2', 'LWS ap', 'Thorax') 72 | elif showCase == 3: 73 | # The two wrist images in the talk, 70 kV with much too high mAs, but 74 | # otherwise we wouldn't have reached the exposure time needed to sync the 75 | # camera. 76 | showkV = (46., 70., 70.) 77 | showmAs = (50., 50, 2.) 78 | what = ('Wrist 1', 'Wrist 2 (Experiment)', 'Wrist 2 (Theory)') 79 | 80 | for kV, mAs, method in zip(showkV, showmAs, what): 81 | CurrentDose = Dose[Range_mAs.index(mAs)][Range_kV.index(kV)] 82 | ax.plot([kV], [mAs], CurrentDose, 'o', c='r', ms=20) 83 | label = '%d kV & %d mAs\nSE-Dose %0.3f mGy\n%s' % (kV, mAs, CurrentDose, 84 | method) 85 | if not showCase == 3: 86 | ax.text(kV, mAs, CurrentDose, label) 87 | print 88 | print 'For a radiography of a', method 89 | print 'with a source voltage of', kV, 'kV and a charge of', mAs, 'mAs, ' 90 | print 'we get a SED of', round(CurrentDose, 3), 'mGy.' 91 | 92 | ax.set_xlabel('kV') 93 | ax.set_ylabel('mAs') 94 | ax.set_zlabel('Dose [mGy]') 95 | 96 | savemovie = False 97 | if savemovie: 98 | # Save output as movie: http://stackoverflow.com/a/12905458/323100 99 | angles = range(225 - 44, 225 + 44) # 150:270 good values for presentation 100 | counter = 1 101 | for angle in angles: 102 | ax.view_init(elev=34.4, azim=angle) 103 | print 'saving angle', str(angle + 1) + '° of 360° as movie' + \ 104 | str("%03d" % angle) + '.png - [' + str(counter) + '/' +\ 105 | str(len(angles)) + ']' 106 | plt.savefig('Dose_movie' + str("%03d" % angle) + '.png', 107 | transparent=False) 108 | plt.draw() 109 | counter += 1 110 | else: 111 | ax.view_init(elev=34.4, azim=225) 112 | plt.savefig('Dose' + str(showCase) + '.png', transparent=True) 113 | 114 | plt.ioff() 115 | print 116 | print 'done' 117 | plt.show() 118 | -------------------------------------------------------------------------------- /GenerateTestImage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Generate test-image for calculating the MTF 5 | """ 6 | from __future__ import division 7 | import math 8 | import numpy 9 | import scipy.misc 10 | import itertools 11 | import matplotlib.pyplot as plt 12 | 13 | 14 | def draw_fansegment(startradius, length, angle): 15 | plt.arrow(Size[1] / 2 + (startradius * numpy.cos(numpy.deg2rad(angle))), 16 | Size[0] / 2 + (startradius * numpy.sin(numpy.deg2rad(angle))), 17 | length * numpy.cos(numpy.deg2rad(angle)), 18 | length * numpy.sin(numpy.deg2rad(angle))) 19 | 20 | 21 | def get_git_hash(): 22 | """ 23 | Get the current git hash from the repository. 24 | Good for saving this information into the log files of process. 25 | Based on http://stackoverflow.com/a/949391/323100 and 26 | http://stackoverflow.com/a/18283905/323100 27 | """ 28 | from subprocess import Popen, PIPE 29 | import os 30 | gitprocess = Popen(['git', '--git-dir', os.path.join(os.getcwd(), '.git'), 31 | 'rev-parse', '--short', '--verify', 'HEAD'], 32 | stdout=PIPE) 33 | (output, _) = gitprocess.communicate() 34 | return output.strip() 35 | 36 | # Setup 37 | Size = [800, 600] 38 | numpy.random.seed(1796) 39 | FilePrefix = 'Phantom_' + get_git_hash() + '_' 40 | 41 | print 'Generating random image with a size of', Size[0], 'x', Size[1], 'px' 42 | # Generate random image 43 | ImageRandom = numpy.random.randint(2, size=Size) * 256 44 | scipy.misc.imsave(FilePrefix + 'random.png', ImageRandom) 45 | print 'Saved random image as', FilePrefix + 'random.png\n' 46 | 47 | # Write grid onto image 48 | GridSize = 100 49 | for x, y in itertools.izip_longest(range(0, Size[0], GridSize), 50 | range(0, Size[1], GridSize)): 51 | if x: 52 | ImageRandom[x, :] = 1 53 | if y: 54 | ImageRandom[:, y] = 1 55 | scipy.misc.imsave(FilePrefix + 'random_grid.png', ImageRandom) 56 | print 'Saved random image with grid as', FilePrefix + 'random_grid.png\n' 57 | 58 | # Checkerboard 59 | print 'Generating checkerboard pattern with a size of', Size[0], 'x', Size[1], \ 60 | 'px' 61 | StripeSize = 50 62 | CheckerBoard = numpy.zeros(Size) 63 | # Horizontal stripes 64 | for x in range(Size[0]): 65 | if math.fmod(x, StripeSize * 2) < StripeSize: 66 | CheckerBoard[x, :] = 1 67 | # vertical stripes stipes -> flip vertical 0/1 68 | for y in range(Size[1]): 69 | if math.fmod(y, StripeSize * 2) < StripeSize: 70 | for x in range(Size[0]): 71 | if CheckerBoard[x, y]: 72 | CheckerBoard[x, y] = 0 73 | else: 74 | CheckerBoard[x, y] = 1 75 | CheckerBoard[:StripeSize, :] = 1 76 | CheckerBoard[-StripeSize:, :] = 1 77 | CheckerBoard[:, :StripeSize] = 1 78 | CheckerBoard[:, -StripeSize:] = 1 79 | scipy.misc.imsave(FilePrefix + 'checkerboard.png', CheckerBoard) 80 | print 'Saved checkerboard image as', FilePrefix + 'checkerboard.png\n' 81 | 82 | print 'Generating star pattern with a size of', Size[0], 'x', Size[1], 'px' 83 | # Draw star-pattern with defined length 84 | saveDPI = 150 85 | fig = plt.figure(figsize=(Size[1] / saveDPI, Size[0] / saveDPI)) 86 | Length = 100 87 | angles = numpy.linspace(0, 360, 3600) 88 | for radius in range(0, max(Size), 2 * Length): 89 | for angle in angles: 90 | if round(angle / 10) % 2 == 0: 91 | draw_fansegment(radius, Length, angle) 92 | draw_fansegment(radius + Length, Length, angle + 10) 93 | plt.plot(Size[1] / 2 + radius * numpy.cos(numpy.deg2rad(angles)), 94 | Size[0] / 2 + radius * numpy.sin(numpy.deg2rad(angles)), 95 | color='k', linewidth=2) 96 | plt.plot(Size[1] / 2 + radius / 2 * numpy.cos(numpy.deg2rad(angles)), 97 | Size[0] / 2 + radius / 2 * numpy.sin(numpy.deg2rad(angles)), 98 | color='k', linewidth=2) 99 | plt.axis([0, Size[1], 0, Size[0]]) 100 | plt.gca().axes.get_xaxis().set_visible(False) 101 | plt.gca().axes.get_yaxis().set_visible(False) 102 | fig.savefig(FilePrefix + 'star.png', dpi=saveDPI) 103 | plt.close() 104 | print 'Saved star pattern as', FilePrefix + 'star.png\n' 105 | 106 | # Show what we've done 107 | plt.figure('Result', figsize=(10, 10)) 108 | plt.suptitle('Images from script version ' + get_git_hash()) 109 | plt.subplot(221) 110 | plt.imshow(plt.imread(FilePrefix + 'random.png'), cmap='gray', 111 | interpolation='nearest') 112 | plt.title('Random image') 113 | plt.axis('off') 114 | plt.subplot(222) 115 | plt.imshow(plt.imread(FilePrefix + 'random_grid.png'), cmap='gray', 116 | interpolation='nearest') 117 | plt.title('with superimposed grid') 118 | plt.axis('off') 119 | plt.subplot(223) 120 | plt.imshow(plt.imread(FilePrefix + 'star.png'), cmap='gray', 121 | interpolation='nearest') 122 | plt.title('Poor mans Siemens star') 123 | plt.subplot(224) 124 | plt.axis('off') 125 | plt.imshow(plt.imread(FilePrefix + 'checkerboard.png'), cmap='gray', 126 | interpolation='nearest') 127 | plt.title('Checkerboard pattern') 128 | plt.axis('off') 129 | plt.show() 130 | -------------------------------------------------------------------------------- /Analysis/functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Functions that we use for each and every script in the folder 5 | /afs/psi.ch/project/EssentialMed/Dev/Analysis 6 | """ 7 | 8 | 9 | def AskUser(Blurb, Choices): 10 | """ 11 | Ask for user input. 12 | Based on function in MasterThesisIvan.ini 13 | """ 14 | print(Blurb) 15 | for Counter, Item in enumerate(sorted(Choices)): 16 | print ' * [' + str(Counter) + ']:', Item 17 | Selection = [] 18 | while Selection not in range(len(Choices)): 19 | try: 20 | Selection = int(input(' '.join(['Please enter the choice you', 21 | 'want [0-' + 22 | str(len(Choices) - 1) + 23 | ']:']))) 24 | except SyntaxError: 25 | print 'You actually have to select *something*' 26 | if Selection not in range(len(Choices)): 27 | print 'Try again with a valid choice' 28 | print 'You selected', sorted(Choices)[Selection] 29 | return sorted(Choices)[Selection] 30 | 31 | 32 | def get_git_hash(): 33 | """ 34 | Get the current git hash from the repository. 35 | Good for saving this information into the log files of process. 36 | Based on http://stackoverflow.com/a/949391/323100 and 37 | http://stackoverflow.com/a/18283905/323100 38 | """ 39 | from subprocess import Popen, PIPE 40 | import platform 41 | 42 | if platform.node() == 'anomalocaris': 43 | gitprocess = Popen(['git', '--git-dir', 44 | '/Volumes/slslc/EssentialMed/Dev/.git', 45 | 'rev-parse', '--short', '--verify', 'HEAD'], 46 | stdout=PIPE) 47 | else: 48 | gitprocess = Popen(['git', '--git-dir', 49 | '/afs/psi.ch/project/EssentialMed/Dev/.git', 50 | 'rev-parse', '--short', '--verify', 'HEAD'], 51 | stdout=PIPE) 52 | (output, _) = gitprocess.communicate() 53 | return output.strip() 54 | 55 | 56 | def myLogger(Folder, LogFileName): 57 | """ 58 | Since logging in a loop does always write to the first instaniated file, 59 | we make a little wrapper around the logger function to write to a defined 60 | log file. 61 | Based on http://stackoverflow.com/a/2754216/323100 62 | """ 63 | import logging 64 | import os 65 | 66 | logger = logging.getLogger(LogFileName) 67 | # either set INFO or DEBUG 68 | # ~ logger.setLevel(logging.DEBUG) 69 | logger.setLevel(logging.INFO) 70 | handler = logging.FileHandler(os.path.join(Folder, LogFileName), 'w') 71 | logger.addHandler(handler) 72 | return logger 73 | 74 | 75 | def get_experiment_list(StartingFolder): 76 | """ 77 | Get all folders (Experiment) and ExperimentIDs inside StartingFolder 78 | """ 79 | import os 80 | import platform 81 | 82 | if platform.node() != 'slslc06' and platform.node() != 'x02da-cons-2': 83 | from progressbar import ProgressBar, Percentage, Bar, ETA 84 | 85 | widgets = ['Reading: ', Percentage(), ' ', Bar(), ' ', ETA()] 86 | pbar = ProgressBar(widgets=widgets, maxval=5000).start() 87 | Experiment = [] 88 | ExperimentID = [] 89 | for root, dirs, files in os.walk(StartingFolder): 90 | if (len(os.path.basename(root)) == 7 91 | or len(os.path.basename(root)) == 8) \ 92 | and not os.path.basename(root).startswith('2014') \ 93 | and 'RECYCLE' not in os.path.basename(root) \ 94 | and 'Pingseng' not in os.path.basename(root) \ 95 | and 'Toshiba' not in os.path.basename(root) \ 96 | and 'MT9' not in os.path.basename(root) \ 97 | and 'AR0' not in os.path.basename(root): 98 | Experiment.append(root) 99 | ExperimentID.append(os.path.basename(root)) 100 | if platform.node() != 'slslc06' and platform.node() != 'x02da-cons-2': 101 | pbar.update(len(ExperimentID)) 102 | if platform.node() != 'slslc06' and platform.node() != 'x02da-cons-2': 103 | pbar.finish() 104 | return Experiment, ExperimentID 105 | 106 | 107 | def distance(Folder, chatty=False): 108 | import os 109 | import glob 110 | 111 | RawFileName = glob.glob(os.path.join(Folder, '*.raw'))[0] 112 | ScintillatorCMOSDistance = int(RawFileName.split('_')[5][:-5]) 113 | if chatty: 114 | print 'Experiment', os.path.basename(Folder), \ 115 | 'was performed with a scintillator-CMOS-distance of', \ 116 | ScintillatorCMOSDistance, 'mm' 117 | return ScintillatorCMOSDistance 118 | 119 | 120 | def estimate_image_noise(image): 121 | """ 122 | # Noise estimation according to http://stackoverflow.com/a/25436112/323100 123 | # based on Immerkaer1996 124 | """ 125 | height, width = image.shape 126 | M = [[1, -2, 1], 127 | [-2, 4, -2], 128 | [1, -2, 1]] 129 | from scipy.signal import convolve2d 130 | import numpy as np 131 | 132 | sigma = np.sum(np.sum(np.absolute(convolve2d(image, M)))) 133 | sigma = sigma * np.sqrt(0.5 * np.pi) / (6 * (width - 2) * (height - 2)) 134 | return sigma 135 | -------------------------------------------------------------------------------- /FocusFOVViewer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to view the images from the lens-test (FOV and distance) 5 | The 'old' version from the first lenses was before git commit a100d19 6 | 7 | We load the RAW images in the folder for each sensor and give out some 8 | information on the images (distance, exposure time, etc) in a nice image 9 | 10 | The images were acquired with the INI file part "[Python: Focus-Distance-Test 11 | for Ivan]" of GDX.ini in DevWare 12 | """ 13 | 14 | from __future__ import division 15 | import os 16 | import glob 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | from matplotlib.offsetbox import OffsetImage, AnnotationBbox 20 | 21 | BaseDir = '/afs/psi.ch/project/EssentialMed/Images/Lens_FOV_and_Distance' 22 | # BaseDir = '/scratch/tmp/DevWareX/FocusDistance/' 23 | 24 | SensorList = [] 25 | for item in os.listdir(BaseDir): 26 | if 'OldStuff' not in item: 27 | if not os.path.isfile(os.path.join(BaseDir, item)): 28 | SensorList.append(item) 29 | 30 | for Sensor in SensorList: 31 | print Sensor 32 | ImageFiles = sorted(glob.glob(os.path.join(BaseDir, Sensor, '*.raw'))) 33 | Lens = [os.path.basename(item).split('_')[1] for item in ImageFiles] 34 | ImageSize = [[int(os.path.basename(item).split('_')[2].split('x')[1]), 35 | int(os.path.basename(item).split('_')[2].split('x')[0])] 36 | for item in ImageFiles] 37 | # The RAW files are "16-bit Unsigned" with the "Width" and "Height" in 38 | # "Little-Endian byte order" 39 | ImageData = [np.fromfile(item, dtype=np.uint16).reshape( 40 | ImageSize[ImageFiles.index(item)]) 41 | for item in ImageFiles] 42 | SDD = [int(os.path.basename(item).split('_')[3].split('mm')[0]) 43 | for item in ImageFiles] 44 | ExposureTime = [int(os.path.basename(item).split('_')[4].split('ms')[0]) 45 | for item in ImageFiles] 46 | print 'SDD varies from', min(SDD), 'to', max(SDD), 'mm' 47 | print 'Exposure times vary from', min(ExposureTime), 'to',\ 48 | max(ExposureTime), 'ms' 49 | 50 | # Display grid with images 51 | plt.figure(figsize=(16, 9)) 52 | for counter, item in enumerate(ImageFiles): 53 | print str(counter + 1).zfill(len(str(len(ImageFiles)))) + '/' + \ 54 | str(len(ImageFiles)) + '| Distance', str(SDD[counter]).rjust(3), \ 55 | 'mm | Exp.', str(ExposureTime[counter]).rjust(2), 'ms | Lens', \ 56 | Lens[counter] 57 | plt.subplot(3, int(np.ceil(len(ImageFiles) / 3)), counter + 1) 58 | # Show images 59 | plt.imshow(ImageData[counter], cmap=plt.cm.gray) 60 | # increase contrast 61 | # plt.imshow(ImageData[counter], cmap=plt.cm.gray, vmin=512, 62 | # vmax=rawimage.max() * 0.9) 63 | # draw contour 64 | # plt.contour(ImageData[counter], [rawimage.max() / 2]) 65 | ImageTitle = str(Lens[counter]), '\nExp. time', \ 66 | str(ExposureTime[counter]), 'ms, Dist.', str(SDD[counter]), 'mm' 67 | plt.title(' '.join(ImageTitle)) 68 | plt.axis('off') 69 | plt.draw() 70 | # Save plot to an image we can view 71 | ImageOverview = os.path.join(BaseDir, Sensor + '_Images.png') 72 | plt.savefig(ImageOverview) 73 | print 'Overview image saved to', ImageOverview 74 | print 80 * '-' 75 | 76 | # Scatter plot sdd vs. exposure time with and without images, according to 77 | # http://matplotlib.org/examples/pylab_examples/demo_annotation_box.html 78 | # Plot the values with labels 79 | plt.figure(figsize=(16, 9)) 80 | plt.subplot(121) 81 | plt.plot(SDD, ExposureTime, linestyle='', marker='o') 82 | for counter, item in enumerate(Lens): 83 | # Label data with lens name: http://stackoverflow.com/a/5147430/323100 84 | plt.annotate(item, xy=(SDD[counter], 85 | ExposureTime[counter] + np.random.random()), 86 | xytext=(0, 10), textcoords='offset points', ha='center', 87 | va='center', bbox=dict(boxstyle='round,pad=0.5', fc='b', 88 | alpha=0.125)) 89 | plt.title(Sensor) 90 | plt.xlabel('Szintillator-Sensor-Distance [mm]') 91 | plt.ylabel('Exposure time [ms]') 92 | plt.grid(True) 93 | plt.xlim([0, max(SDD) * 1.1]) 94 | plt.ylim([0, max(ExposureTime) * 1.1]) 95 | # Scatter plot the images 96 | ax = plt.subplot(122) 97 | plt.plot(SDD, ExposureTime, linestyle='', marker='o') 98 | plt.title(Sensor) 99 | plt.xlabel('Szintillator-Sensor-Distance [mm]') 100 | plt.ylabel('Exposure time [ms]') 101 | plt.grid(True) 102 | 103 | ax = plt.subplot(122) 104 | for counter, item in enumerate(ImageFiles): 105 | imagebox = OffsetImage(ImageData[counter], zoom=0.1, cmap=plt.cm.gray) 106 | SubImage = AnnotationBbox(imagebox, [SDD[counter], 107 | ExposureTime[counter]], pad=0) 108 | ax.add_artist(SubImage) 109 | plt.xlim([0, max(SDD) * 1.1]) 110 | plt.ylim([0, max(ExposureTime) * 1.1]) 111 | plt.draw() 112 | ImageDistances = os.path.join(BaseDir, Sensor + '_Scatterplot.png') 113 | plt.savefig(ImageDistances) 114 | print 'Overview image saved to', ImageDistances 115 | print 80 * '-' 116 | plt.show() 117 | -------------------------------------------------------------------------------- /FocusPlotLine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to plot line on TIFF, useful for 'calculating' the best focus of 5 | the EssentialMed Setup. Since the objective doesn't really have the focal 6 | distance Edmund optics stated... 7 | Reads TIFF-Stack with images from focus shifting on the LINOS-rail. Plots 8 | a line (coordinates read from a txt-File, see around line 45) on the 9 | 'original' image and generates a lineplot from this line side by side 10 | with the slice. Opens fiji with all images in the end so we can easily 11 | browse through all those slices and find the best focus... 12 | """ 13 | 14 | import libtiff 15 | import matplotlib.pylab as plt 16 | import linecache 17 | import os 18 | 19 | # Setup 20 | # All Apertures, "mixed media" on the intensifying screen 21 | # Series = 2 22 | # Only extreme Apertures, only Siemens-Star 23 | Series = 3 24 | 25 | # Leave '0' for 'natural' length of plot axis. Set to a value if scaling is 26 | # desired 27 | ScaleAxis = 600 28 | 29 | # Just plot a horizontal line in the middle of the images (or shifted by YSHIFT 30 | # below). If set to zero, Coordinates are read from file. 31 | SimpleHorizontalLine = 0 32 | # Shift this many pixels up from 1024, the middle of the image (Only used if 33 | # plotting SimpleHorizontalLine) 34 | YSHIFT = -50 35 | 36 | if Series == 2: 37 | Apertures = [1.8, 4, 8, 16] 38 | elif Series == 3: 39 | Apertures = [1.8, 16] 40 | 41 | # The trick with the [-X:] loads only the X last entries of the Aperture-List. 42 | for F in Apertures[-4:]: 43 | # Open tiff file 44 | tif = libtiff.TIFFfile('/afs/psi.ch/project/EssentialMed/Images/' + 45 | str(Series) + '-FocusTest/Series_F' + 46 | str('%04.1f' % F) + '.tif') 47 | # plt.ion() 48 | for i in range(0, len(tif.pages)): 49 | plt.figure(figsize=(16, 8)) 50 | plt.subplots_adjust(left=None, bottom=None, right=None, top=None, 51 | wspace=None, hspace=None) 52 | # Add first subplot with Original Image and plot the line where we take 53 | # the lineprofile 54 | ax1 = plt.subplot(121) 55 | ax1.imshow(tif.asarray(key=i), cmap=plt.cm.gray) 56 | ax1.set_xlim([0, 2048]) 57 | ax1.set_ylim([2048, 0]) 58 | plt.axis('off') 59 | if SimpleHorizontalLine == 1: 60 | ax1.plot([0 + 400, 2048 - 400], [1024 - YSHIFT, 1024 - YSHIFT], 61 | 'r') 62 | # Cheat with 'line', so we have something to write at the end... 63 | line = 'asdf ' + str(0 + 400) + ' ' + str(1024 - YSHIFT) + ' ' + \ 64 | str(2048 - 400) + ' ' + str(1024 - YSHIFT) 65 | else: 66 | # Reads coordinates from a text file which will then be used to 67 | # plot stuff on images. 68 | # '+2' since we have two headerlines '+1' since python starts at 69 | # line 0 :) 70 | line = linecache.getline('/afs/psi.ch/project/EssentialMed/Images/Coordinates_UpInStar.txt', i + 3) 71 | line = linecache.getline('/afs/psi.ch/project/EssentialMed/Images/Coordinates_Arbitrary.txt', i + 3) 72 | line = linecache.getline('/afs/psi.ch/project/EssentialMed/Images/Coordinates_LowerLine.txt', i + 3) 73 | ax1.plot([line.split()[1], line.split()[3]], 74 | [line.split()[2], line.split()[4]], 'r') 75 | plt.title('Image ' + str(i)) 76 | # Plot Lineprofile 77 | ax2 = plt.subplot(122) 78 | if SimpleHorizontalLine == 1: 79 | ax2.plot(tif.asarray(key=i)[1024 - YSHIFT, 0 + 400:2048 - 400]) 80 | else: 81 | ax2.plot(tif.asarray(key=i)[str(line.split()[2]), 82 | int(line.split()[1]):int(line.split()[3])]) 83 | if ScaleAxis == 0: 84 | # scale x-axis of lineplot to real length 85 | ax2.set_xlim([0, (int(line.split()[3]) - int(line.split()[1]))]) 86 | else: 87 | # scale x-axis of lineplot to the same length for all plots 88 | ax2.set_xlim([0, ScaleAxis]) 89 | # Adapt to Brightness 90 | if F == 1.8: 91 | if Series == 2: 92 | ax2.set_ylim([0, 256]) 93 | else: 94 | ax2.set_ylim([0, 256]) 95 | # Only for Series 2... 96 | elif F == 4: 97 | ax2.set_ylim([0, 180]) 98 | # Only for Series 2... 99 | elif F == 8: 100 | ax2.set_ylim([0, 250]) 101 | elif F == 16: 102 | if Series == 2: 103 | ax2.set_ylim([0, 75]) 104 | else: 105 | ax2.set_ylim([0, 256]) 106 | plt.title('Line length: ' + str(int(line.split()[3]) - 107 | int(line.split()[1]))) 108 | plt.draw() 109 | SaveName = '/afs/psi.ch/project/EssentialMed/Images/' + \ 110 | str(Series) + '-FocusTest/F' + str('%04.1f' % F) + '/F' + \ 111 | str('%04.1f' % F) + '_Image_' + str('%02d' % i) + \ 112 | '_LineProfile_from_' + str(line.split()[1]) + '_to_' + \ 113 | str(line.split()[3]) + '_on_height_' + str(line.split()[2]) + \ 114 | '.png' 115 | plt.savefig(SaveName) 116 | print 'Figure ' + str(i) + ' saved to ' + SaveName 117 | plt.close() 118 | 119 | # View the figures we just saved as stack in Fiji 120 | viewcommand = '/scratch/Apps/Fiji.app/fiji-linux ' + \ 121 | '/afs/psi.ch/project/EssentialMed/Images/' + str(Series) +\ 122 | '-FocusTest/F' + str('%04.1f' % F) + '/F* &' 123 | os.system(viewcommand) 124 | -------------------------------------------------------------------------------- /DetectorMockup/OmmatiDiag.scad: -------------------------------------------------------------------------------- 1 | // Mockup of OmmatiDiag 2 | /* 3 | * The mockup was done after the first crude drawing with 4 | * [Tinkercad](https://tinkercad.com/things/3utMiKsZhx9) proved to be too 5 | * inflexible. 6 | * The basic unit of the detector will be the scintillator size, which we 7 | * chose to be 17" x 17" translating to 430 x 430 mm in SI units. 8 | */ 9 | 10 | // Generate screenshot on the Mac with 11 | // /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD OmmatiDiag.scad --render --imgsize=1024,768 --projection=p -o Screenshot.png 12 | viewport_distance = 2000; 13 | 14 | // Basic length variables [mm] 15 | unitlength = 430; 16 | padding = 20; 17 | height = 100; 18 | overscan = 1.1; 19 | semitransparent = 0.618; 20 | nearlytransparent = 0.309; 21 | 22 | // dimlines.scad is used to easily draw dimensional measurements 23 | // http://www.cannymachines.com/entries/9/openscad_dimensioned_drawings 24 | include 25 | 26 | DIM_LINE_WIDTH = .025 * unitlength; 27 | DIM_SPACE = .1 * unitlength; 28 | 29 | module Scalebar() 30 | translate([unitlength + 100 , padding/2, 0]){ 31 | rotate([0,0,90]) 32 | dimensions(unitlength, line_width=DIM_LINE_WIDTH, loc=0); 33 | } 34 | 35 | // Housing 36 | module Housing() 37 | color ("gray", alpha=nearlytransparent) { 38 | // box bottom 39 | cube([unitlength + padding, unitlength + padding,1]); 40 | // box back walls 41 | cube([unitlength + padding, 1, height]); 42 | cube([1, unitlength + padding, height]); 43 | // box front walls, can (and should be turned off for increased visibility) 44 | translate(v = [0, unitlength + padding, 0]) { 45 | cube([unitlength + padding, 1, height]); 46 | } 47 | translate(v = [unitlength + padding, 0, 0]) { 48 | cube([1, unitlength + padding, height]); // 49 | } 50 | } 51 | 52 | // Scintillator 53 | module Scintillator() 54 | translate([padding/2, padding/2, 1]) { 55 | color ("green") { 56 | cube([unitlength,unitlength,1]); 57 | } 58 | } 59 | 60 | // CMOS Backplate 61 | module Backplate() 62 | translate([(unitlength+padding)/2 + 5, (unitlength+padding)/2 + 5, height-15]) { 63 | color ("red", alpha=semitransparent) { 64 | cube([350,320,1],center=true); 65 | } 66 | } 67 | 68 | // Ommatidium 69 | module Ommatidium() { 70 | // FOV of one Ommatidium 71 | FOVSize = ([unitlength/4, unitlength/3]); 72 | module FOV() 73 | color("red") 74 | cube([FOVSize[0] * overscan, FOVSize[1] * overscan, 1], center=true); 75 | 76 | // CMOS (Aptina AR0130) 77 | CMOSSize = ([1280, 960]); 78 | pixelsize = 3.75 / 1000; // [um] 79 | module CMOS() 80 | color ("blue") 81 | cube([CMOSSize[0] * pixelsize, CMOSSize[1] * pixelsize, 0.5], center=true); 82 | translate ([0,0,0]) CMOS(); 83 | 84 | // Ommatidium back plate 85 | module Ommatidiumplate() 86 | color ("red") 87 | cube([15, 20, 1], center=true); 88 | translate ([5,7,0]) Ommatidiumplate(); 89 | 90 | // FOV CMOS 91 | d_cmos_lens = 15; 92 | d_lens_scintillator = height - d_cmos_lens - 15; 93 | module CMOSCone() 94 | color("orange", alpha=nearlytransparent) 95 | cylinder(h = d_cmos_lens, 96 | r1 = CMOSSize[0] * pixelsize / 2 , 97 | r2 = lensdiameter * 2, center=true); 98 | module CMOSCross() { 99 | color("blue", alpha=semitransparent) 100 | polyhedron( 101 | points=[ [0, 0, 0], //origin 102 | [-CMOSSize[0]*pixelsize/2, 0, d_cmos_lens], // horizontal_1 103 | [CMOSSize[0]*pixelsize/2, 0,d_cmos_lens], // horizontal_2 104 | [0, -CMOSSize[1] * pixelsize / 2,d_cmos_lens], // vertical_1 105 | [0, CMOSSize[1] * pixelsize / 2,d_cmos_lens]], // vertical_2 106 | faces=[[0,1,2], [0,3,4]]); 107 | } 108 | 109 | mirror([0,0,1]) translate([0,0,-d_cmos_lens]) 110 | CMOSCross(); 111 | translate([0,0,d_cmos_lens/2]) 112 | CMOSCone(); 113 | 114 | // Lens 115 | lensdiameter = 4; 116 | module Lens() 117 | // the lens is a squashed sphere: http://is.gd/4H9sZf 118 | scale([2,2,0.5]) sphere(lensdiameter, $fn=50, center=true); 119 | translate([0, 0, d_cmos_lens]) Lens(); 120 | 121 | // FOV Lens 122 | module LensCone() 123 | color("orange", alpha=nearlytransparent) 124 | scale([1, 4/3, 1]) 125 | cylinder(h = d_lens_scintillator, 126 | r1 = lensdiameter * 2, 127 | r2 = FOVSize[1]/2 * 1.2); 128 | module LensCross() { 129 | color("blue", alpha=semitransparent) polyhedron( 130 | points=[ [0, 0, 0], //origin 131 | [-FOVSize[0]/2 * overscan, 0, d_lens_scintillator], // horizontal_1 132 | [FOVSize[0]/2 * overscan, 0,d_lens_scintillator], // horizontal_2 133 | [0, -FOVSize[1]/2 * overscan ,d_lens_scintillator], // vertical_1 134 | [0, FOVSize[1]/2 * overscan,d_lens_scintillator]], // vertical_2 135 | faces=[[0,1,2], [0,3,4]]); 136 | } 137 | 138 | translate([0,0,d_cmos_lens]) 139 | LensCross(); 140 | translate([0,0,d_cmos_lens + d_lens_scintillator]) 141 | FOV(); 142 | translate([0,0,d_cmos_lens]) 143 | LensCone(); 144 | 145 | } 146 | 147 | rotate([0,0,$t*360]) 148 | translate([-(unitlength+padding)/2,-(unitlength+padding)/2,0]){ 149 | Scalebar(); 150 | Scintillator(); 151 | translate([unitlength/4/2 + padding/2, unitlength/3/2 + padding/2, height-14]) 152 | mirror([0,0,1]) 153 | for (xpos=[0:3], ypos = [0:2]) // iterate over x and y 154 | translate([xpos*unitlength/4, ypos*unitlength/3, 0]) 155 | Ommatidium(); 156 | Backplate(); 157 | Housing(); 158 | }; 159 | -------------------------------------------------------------------------------- /aptina/data/sensor_data/sensor_desc.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Aptina Imaging <xsl:value-of select="sensor/@part_number"/>-<xsl:value-of select="sensor/@version_name"/> 8 | 9 | 10 |
11 |

12 | 13 |  Aptina Imaging     14 |

15 | 16 |

 Register/Bitfield Long Descriptions

17 |

 Note that items displayed in Red indicate their contents are Confidential, items displayed in Black indicate their contents are NDA, and items displayed in Green indicate their contents are Public.

18 | 19 | 20 | 21 | 22 | 23 |


24 | 25 |
26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | Register Name 34 | Long Description 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |    128 | 129 | 130 | 131 | 132 | 133 | 134 |    135 | 136 | 137 | 138 | 139 | 140 |    141 | 142 | 143 | 144 | 145 |
146 | 147 | 148 | -------------------------------------------------------------------------------- /MTF.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to calculate the Modulation transfer function of a edge target. 5 | 6 | Kickstarted from from https://gist.github.com/stefanv/2051954 and additional 7 | info from http://www.normankoren.com/Tutorials/MTF.html which tells us that 8 | "MTF can be defined as the magnitude of the Fourier transform of the point or 9 | line spread function. And some wikipedia lookup. 10 | """ 11 | 12 | import numpy as np 13 | import scipy 14 | import scipy.ndimage 15 | import matplotlib.pylab as plt 16 | import time 17 | 18 | 19 | def MTF(edgespreadfunction): 20 | ''' 21 | Compute the modulation transfer function (MTF). 22 | 23 | The MTF is defined as the FFT of the line spread function. 24 | The line spread function is defined as the derivative of the edge spread 25 | function. The edge spread function are the values along an edge, ideally a 26 | knife-edge test target. See an explanation here: http://is.gd/uSC5Ve 27 | ''' 28 | linespreadfunction = np.diff(edgespreadfunction) 29 | return np.abs(np.fft.fft(linespreadfunction)) 30 | 31 | 32 | def LSF(edgespreadfunction): 33 | ''' 34 | Compute the modulation transfer function (MTF). 35 | 36 | The MTF is defined as the FFT of the line spread function. 37 | The line spread function is defined as the derivative of the edge spread 38 | function. The edge spread function are the values along an edge, ideally a 39 | knife-edge test target. See an explanation here: http://is.gd/uSC5Ve 40 | ''' 41 | return np.abs(np.diff(edgespreadfunction)) 42 | 43 | 44 | def polynomialfit(data, order): 45 | ''' 46 | calculate the polynomial fit of an input for a defined degree 47 | ''' 48 | x, y = range(len(data)), data 49 | coefficients = np.polyfit(x, y, order) 50 | return np.polyval(coefficients, x) 51 | 52 | 53 | # Generate edge for N points 54 | N = 250 55 | dirac = np.zeros(N) 56 | dirac[:N / 2] = 1 57 | 58 | # Filter edge 59 | sigma = [0.4, 0.6, 1] 60 | gauss_1 = scipy.ndimage.gaussian_filter(dirac, sigma=sigma[0]) 61 | gauss_2 = scipy.ndimage.gaussian_filter(dirac, sigma=sigma[1]) 62 | gauss_3 = scipy.ndimage.gaussian_filter(dirac, sigma=sigma[2]) 63 | 64 | SaveFigure = False 65 | # Total = 55 66 | # for iteration in range(Total): 67 | # print 'Plotting', iteration, 'of', Total 68 | 69 | noise_sigma = 0.001 70 | gauss_1_noise = gauss_1 + noise_sigma * np.random.randn(len(dirac)) 71 | gauss_2_noise = gauss_2 + noise_sigma * np.random.randn(len(dirac)) 72 | gauss_3_noise = gauss_3 + noise_sigma * np.random.randn(len(dirac)) 73 | 74 | 75 | ''' 76 | Save the plots in a dictionary, so we can iterate through it afterwards. See 77 | http://stackoverflow.com/a/2553532/323100 and http://is.gd/d008ai for reference 78 | ''' 79 | plots = dict((name, eval(name)) for name in ['dirac', 80 | 'gauss_1', 'gauss_1_noise', 81 | 'gauss_2', 'gauss_2_noise', 82 | 'gauss_3', 'gauss_3_noise']) 83 | 84 | plt.figure(figsize=(16, 16)) 85 | counter = 0 86 | ShowRegion = 10 87 | for name, data in sorted(plots.iteritems()): 88 | counter += 1 89 | plt.subplot(4, len(plots), counter) 90 | plt.plot(data) 91 | plt.ylim(-0.1, 1.1) 92 | # plt.xlim(len(dirac) / 2 - ShowRegion / 2, len(dirac) / 2 + ShowRegion / 93 | # 2) 94 | if name == 'dirac': 95 | plt.ylabel('Edge response') 96 | plt.title(name) 97 | if name == 'gauss_1': 98 | plt.title(name + '\nSigma=' + str(sigma[0])) 99 | if name == 'gauss_2': 100 | plt.title(name + '\nSigma=' + str(sigma[1])) 101 | if name == 'gauss_3': 102 | plt.title(name + '\nSigma=' + str(sigma[2])) 103 | 104 | plt.subplot(4, len(plots), counter + len(plots)) 105 | plt.plot(LSF(data)) 106 | plt.ylim(-0.1, 1.1) 107 | if name == 'dirac': 108 | plt.ylabel('Edge response') 109 | 110 | plt.subplot(4, len(plots), counter + 2 * len(plots)) 111 | plt.plot(MTF(data)) 112 | plt.plot(np.ones(N) * MTF(data)[len(dirac) / 2]) 113 | plt.ylim(-0.1, 1.1) 114 | plt.xlim(0, len(dirac) / 2) 115 | if name == 'dirac': 116 | plt.ylabel('MTF @ Nyquist') 117 | plt.text(0.618 * len(dirac) / 2, MTF(data)[len(dirac) / 2] - 0.1, 118 | ' '.join([str(np.round(MTF(data)[len(dirac) / 2], 3) * 100), 119 | '%']), 120 | fontsize=12, backgroundcolor='w') 121 | 122 | plt.subplot(4, len(plots), counter + 3 * len(plots)) 123 | plt.plot(MTF(data), label='orig') 124 | # for degree in range(10,25): 125 | # plt.plot(polynomialfit(MTF(data), degree), label=str(degree)) 126 | # plt.legend() 127 | degree = 4 128 | plt.plot(polynomialfit(MTF(data), degree), label=str(degree), color='r') 129 | plt.plot(np.ones(N) * polynomialfit(MTF(data), degree)[len(dirac) / 2], 130 | color='g') 131 | plt.ylim(-0.1, 1.1) 132 | plt.xlim(0, len(dirac) / 2) 133 | if name == 'dirac': 134 | plt.ylabel(' '.join(['polynomial fit of order', str(degree), 135 | '\nfitted MTF @ Nyquist'])) 136 | plt.text(0.618 * len(dirac) / 2, MTF(data)[len(dirac) / 2] - 0.1, 137 | ' '.join([str(np.round(polynomialfit(MTF(data), 138 | degree)[len(dirac) / 2], 139 | 3) * 100), '%']), 140 | fontsize=12, backgroundcolor='w') 141 | 142 | plt.subplot(4, len(plots), 1) 143 | plt.plot(dirac, 'b') 144 | plt.ylim(-0.1, 1.1) 145 | plt.axvspan(len(dirac) / 2 - ShowRegion / 2, len(dirac) / 2 + ShowRegion / 2, 146 | facecolor='r', alpha=0.5) 147 | plt.title('Ideal knife edge\n red zoom-region\n is shown right') 148 | 149 | if SaveFigure: 150 | plt.savefig('MTF_' + str(int(time.time() * 10)) + '.png') 151 | else: 152 | plt.show() 153 | -------------------------------------------------------------------------------- /Analysis/DetectWhichImageIsFocusedBest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | """ 4 | Script to load a bunch of images and caculate the focus of them, based on the 5 | standard deviation of the gray values. The image with the smallest standard 6 | deviation (from the same scene!) should be the one focused best. 7 | """ 8 | 9 | from __future__ import division 10 | import glob 11 | import os 12 | import matplotlib.pyplot as plt 13 | import sys 14 | import numpy 15 | 16 | # Setup 17 | Root = '/afs/psi.ch/project/EssentialMed/Images/13-Aptina_Focus_Test/' 18 | # Root = '/Volumes/WINDOWS' 19 | # Root = '/scratch/tmp/DevWareX/AR0130' 20 | # Root = '/scratch/tmp/DevWareX/A-1300' 21 | 22 | Directories = sorted([x[0] for x in os.walk(Root)][1:]) 23 | 24 | print 'I found these folders in', Root 25 | print 'Which folder do you want to analyze?' 26 | for item in enumerate(Directories): 27 | print str(item[0]) + ':', os.path.basename(item[1]), 'containing' 28 | print ' ', len(glob.glob(os.path.join(item[1], '*.raw'))), \ 29 | 'raw and' 30 | print ' ', len(glob.glob(os.path.join(item[1], '*.png'))), 'png files' 31 | 32 | ChosenFolder = [] 33 | while ChosenFolder not in range(len(Directories)): 34 | ChosenFolder = int(raw_input('Enter [0:' + str(len(Directories) - 1) + 35 | ']: ')) 36 | 37 | # Get list of files. These are the images we acquired of which 38 | # one has the best focus 39 | Images = sorted(glob.glob(os.path.join(Directories[ChosenFolder], '*.png'))) 40 | if not Images: 41 | print 'I cannot find any png images in', \ 42 | os.path.basename(Directories[ChosenFolder]) 43 | print 'Did you save the images as RAW and not convert them yet?' 44 | sys.exit('Please try again...') 45 | else: 46 | print 'I will work with the', \ 47 | len(glob.glob(os.path.join(Directories[ChosenFolder], '*.png'))),\ 48 | 'png files in', Directories[ChosenFolder] 49 | # Discard some images 50 | Images = Images[3:] 51 | 52 | print 53 | print 'I will use', len(Images), '*.png images in', Directories[ChosenFolder] 54 | 55 | # Iterate through the files, calculate the standard deviation of each image and 56 | # plot this. 57 | print 'Calculating Mean of each of', len(Images), 'images' 58 | MeanExposure = [numpy.mean(plt.imread(x)) for x in Images] 59 | 60 | print 'Calculating standard deviation of each of', len(Images), 'of images' 61 | STD = [numpy.std(plt.imread(x)) for x in Images] 62 | 63 | normalize = True 64 | if normalize: 65 | # Normalize the values around the mean and convert the now array back to a 66 | # list 67 | MeanExposure = MeanExposure - numpy.mean(MeanExposure) 68 | MeanExposure = MeanExposure.tolist() 69 | STD = STD - numpy.mean(STD) 70 | STD = STD.tolist() 71 | 72 | plt.figure(figsize=(16, 9)) 73 | plt.subplot(311) 74 | plt.title(' '.join([str(len(Images)), 'Images read from', 75 | os.path.basename(Directories[ChosenFolder])])) 76 | # Plot values 77 | plt.plot(MeanExposure, color='r', alpha=0.5, 78 | label='Exposure with Max @ Img. ' + 79 | str(MeanExposure.index(max(MeanExposure)))) 80 | plt.plot(STD, color='b', alpha=0.5, 81 | label='STD with Max @ Img. ' + str(STD.index(max(STD)))) 82 | # Print details and plot positions of 'Details' chosen images 83 | Details = 10 84 | for i in range(1, len(Images), int(round(len(Images) / Details))): 85 | print str(i).zfill(2), '|', 86 | if normalize: 87 | print 'normalized', 88 | print 'Exp', str(round(MeanExposure[i], 4)), '|', 89 | if normalize: 90 | print 'normalized', 91 | print 'STD of', round(STD[i], 4) 92 | plt.plot(i, MeanExposure[i], color='r', marker='.') 93 | plt.plot(i, STD[i], color='b', marker='.') 94 | plt.annotate(i, xy=(i, STD[i]), xytext=(0, 15), 95 | textcoords='offset points', ha='center', va='top') 96 | # Plot and mark worst and best image: http://stackoverflow.com/a/5147430/323100 97 | plt.plot(STD.index(min(STD)), min(STD), color='b', marker='v') 98 | plt.annotate(os.path.basename(Images[STD.index(min(STD))]), 99 | xy=(STD.index(min(STD)), min(STD)), xytext=(0, 30), 100 | textcoords='offset points', ha='center', va='bottom', 101 | bbox=dict(boxstyle='round,pad=0.5', fc='b', alpha=0.125), 102 | arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.79')) 103 | plt.plot(STD.index(max(STD)), max(STD), color='b', marker='^') 104 | plt.annotate(os.path.basename(Images[STD.index(max(STD))]), 105 | xy=(STD.index(max(STD)), max(STD)), xytext=(0, 30), 106 | textcoords='offset points', ha='center', va='bottom', 107 | bbox=dict(boxstyle='round,pad=0.5', fc='b', alpha=0.125), 108 | arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.79')) 109 | plt.xlim([0, len(Images) - 1]) 110 | plt.legend(loc='best') 111 | print 112 | print 'Image', str(MeanExposure.index(max(MeanExposure))), '(' + \ 113 | os.path.basename(Images[MeanExposure.index(max(MeanExposure))]) +\ 114 | ') is the brightest.' 115 | print 'Image', str(STD.index(max(STD))), '(' + \ 116 | os.path.basename(Images[STD.index(max(STD))]) + ') has the largest STD.' 117 | print 118 | print 'Use Image' 119 | print Images[STD.index(max(STD))] 120 | print 'for further tests' 121 | 122 | # Display selection of images 123 | Counter = Details + 1 124 | for i in range(1, len(Images), int(round(len(Images) / Details))): 125 | plt.subplot(3, 10, Counter) 126 | plt.imshow(plt.imread(Images[i]), cmap='gray') 127 | # plt.title(os.path.basename(Images[i])) 128 | plt.title('Img ' + str(i)) 129 | Counter += 1 130 | # Display worst and best image 131 | plt.subplot(3, 2, 5) 132 | plt.imshow(plt.imread(Images[STD.index(min(STD))]), cmap='gray') 133 | plt.title('worst@' + os.path.basename(Images[STD.index(min(STD))])) 134 | plt.subplot(3, 2, 6) 135 | plt.imshow(plt.imread(Images[STD.index(max(STD))]), cmap='gray') 136 | plt.title('best@' + os.path.basename(Images[STD.index(max(STD))])) 137 | plt.savefig(Directories[ChosenFolder] + '.png') 138 | plt.show() 139 | -------------------------------------------------------------------------------- /SetupPi.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi setup from scratch 2 | I plugged in a cheap 4 GB SD card I just had lying around, brought the system up and running and cobbled together bits and pieces and got a working system. 3 | This document here describes *all* the necessary steps to get up and runnig with a virgin SD card. 4 | If you follow all the steps in this document, you should have a RPi ready to use for the [GlobalDiagnostiX](http://globaldiagnostix.org) project. 5 | The aim is to be able to interact with several cameras ([Elphel](http://elphel.com), [Awaiba](http://www.awaiba.com/), [The Imaging Source](http://www.theimagingsource.com/)) and to acquire images from a scintillator screen using a (commercial) x-ray source. 6 | 7 | ## Prerequisites 8 | - According to the [Embedded Linux Wiki](http://elinux.org/RPi_SD_cards), a Transcend 16GB SDHC card is working well. Order one from [Digitec](https://www.digitec.ch/ProdukteDetails2.aspx?Reiter=Details&Artikel=194092) for (currently) 24 CHF. 9 | - Download the [BerryBoot Installer](http://www.berryterminal.com/doku.php/berryboot) and unzip it onto your SD card 10 | - Boot your Raspberry Pi from this SD card to install a current version of [Raspbian](http://www.raspbian.org/) or any other operating system. 11 | - Reboot. This will then go through `raspi-config` to reconfigure locales, keyboard and timezone (if necessary) 12 | - `sudo /etc/ntp.conf` to add the timeservers of PSI to the configuration, so we have the correct time. Add the lines below as first `server` entries 13 | # Permit time synchronization with our time source, but do not 14 | # permit the source to query or modify the service on this system. 15 | server pstime1.psi.ch 16 | restrict pstime1.psi.ch mask 255.255.255.255 nomodify notrap noquery 17 | server pstime2.psi.ch 18 | restrict pstime2.psi.ch mask 255.255.255.255 nomodify notrap noquery 19 | server pstime3.psi.ch 20 | restrict pstime3.psi.ch mask 255.255.255.255 nomodify notrap noquery 21 | 22 | ## Further setup 23 | - Update the repositories and upgrade the system to the newest packages with `sudo apt-get update && sudo apt-get upgrade`. 24 | This will will take a *long* time 25 | - Reboot 26 | - Install git and pull the GlobalDiagnostiX repository into your home folder. 27 | This should also take care of the `git` configuration, since we're also pulling the `.gitconfig` from the repo. 28 | 29 | sudo apt-get install git 30 | cd 31 | sudo rm -r * # to remove *EVERYTHING* from your home directory 32 | sudo rm -r .* # REALLY, EVERYTHING! 33 | git clone https://github.com/habi/GlobalDiagnostiX.git ~ # clone the GitHub repo into your home directory 34 | nano SetupPi.md # change something in the file 35 | git commit -a;git push # commit the change and push it back to the repo to see if that works 36 | 37 | - Install other packages with the lines below. This will take either take a *very* long time or go quite quickly if the command above took long. Anways, go and have a coffe.... 38 | You can either do it line by line (below, with explanations) or just copy the oneliner at the bottom which does everything in one go. 39 | 40 | sudo apt-get install libblas-dev # good for scipy and numpy, see also http://raspberrypi.stackexchange.com/a/1730 41 | sudo apt-get install liblapack-dev # ditto 42 | sudo apt-get install python-dev # we want to develop in python 43 | sudo apt-get install libatlas-base-dev # speeds up execution according to http://is.gd/H7zqxv 44 | sudo apt-get install gfortran # compiler for scipy and numpy 45 | sudo apt-get install python-setuptools # helps with download, build and installation of Python packages 46 | sudo apt-get install python-scipy # install scipy 47 | sudo apt-get install python-numpy # install numpy 48 | sudo apt-get install python-matplotlib # no plotting without it 49 | sudo apt-get install ipython # interactive Pythoning 50 | sudo apt-get install geany # my preferred Python IDE 51 | sudo apt-get install imagemagick # do some image magic 52 | sudo apt-get install imagej # view and work with images 53 | sudo apt-get install chromium-browser # faster than Midori according to http://is.gd/8Hgfcc 54 | sudo apt-get install screen # Nice terminal multiplexer 55 | sudo apt-get install telnet # so we can `telnet` to the Elphel-camera as 'root'/'pass' 56 | sudo apt-get install ftp # so we can `ftp`stuff to the Elphel-camera 57 | sudo apt-get install gftp # so we can graphically `ftp`stuff to the Elphel-camera 58 | sudo apt-get install gedit # graphical text editor 59 | sudo apt-get install tkdiff # a graphical tool to diff files 60 | sudo apt-get isntall mplayer vlc # install two movie players, to look at camera output 61 | 62 | Here's all of the above as a oneliner: 63 | 64 | sudo apt-get install libblas-dev liblapack-dev python-dev libatlas-base-dev gfortran python-setuptools python-scipy python-numpy python-matplotlib ipython geany imagemagick openjdk-6-jre openjdk-6-jdk imagej chromium-browser screen telnet ftp gftp gedit tkdiff mplayer vlc 65 | 66 | # Tweaks 67 | - Num Lock on boot (according to [RPi-forum](http://is.gd/Fa0DxF) 68 | - `sudo nano /etc/kbd/config` 69 | - CTRL+V two or three times to go to line 67 (nearly at the bottom) 70 | - remove the comment in front of "LEDS=+num" 71 | - Install and set up `pep8` 72 | - `sudo easy_install pep8` 73 | - Open Geany and open the *Build* > *Set Build Commands* menu 74 | - For *Independend commands* Nr. 2, paste `pep8 %f` and for *Error regular expression* add `([^:]+):([0-9]+):([0-9:]+)? .* 75 | ` (adapted from [the Geany wiki](http://wiki.geany.org/howtos/check_python_code)). 76 | - Buy a small monitor 77 | - I bought http://bit.ly/10N9MbN and used a 12V power supply we had at TOMCAT. 78 | - Setup the composite (yellow) output to support the resolution of the monitor (PAL or NTSC, 480x272) 79 | - Go to 'Edit menu' in the BerryBoot boot menu, click on the arrow on top right to go to 'Advanced configuration' and edit the config.txt. See [this post in the RPI forum](http://raspberrypi.org/phpBB3//viewtopic.php?f=26&t=16403) for a screenshot. 80 | - add `framebuffer_width=480` and `sdtv_aspect=3`, according to the [Video mode options](http://elinux.org/RPiconfig#Video_mode_options) 81 | - Reboot with 'Exit' 82 | - Have fun! 83 | -------------------------------------------------------------------------------- /Demonstrator/undistort_images.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to undistort camera images, based on checkerboard pattern. 5 | The script is based on [the OpenCV-Python tutorials](http://is.gd/KhTOuX) and 6 | the [OpenCV Python samples](http://git.io/dydjBQ) 7 | 8 | The script *needs* the example images left*.jpg found in opencv/samples/cpp or 9 | available for download [here](http://git.io/MDUBRw). 10 | """ 11 | 12 | import os 13 | import numpy 14 | import cv2 15 | import glob 16 | import matplotlib.pylab as plt 17 | from scipy import ndimage 18 | 19 | LoadOmmatidiag = True 20 | 21 | if LoadOmmatidiag: 22 | BaseDir = '/afs/psi.ch/project/EssentialMed/Images' \ 23 | '/DetectorElectronicsTests/EssentialLab/1421142758_checkerboard' 24 | OriginalsList = glob.glob(os.path.join(BaseDir, 'data-e*-g*-i*-??.png')) 25 | ImageList = OriginalsList 26 | else: 27 | BaseDir = '/afs/psi.ch/project/EssentialMed/Dev/Demonstrator' 28 | ImageList = glob.glob(os.path.join(BaseDir, 'left*.jpg')) 29 | OriginalsList = ImageList 30 | 31 | # termination criteria 32 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 50, 0.001) 33 | 34 | # We *have* to give the pattern size to look for. 35 | # This is the number of visible chess board "inner edges", i.e. number of 36 | # rows and columns -1. 37 | # For the sample images (LoadOmmatidiag = False) (7,6) is good. 38 | # For the edited checkerboard images, we use (5, 4). 39 | # See http://dasl.mem.drexel.edu/~noahKuntz/openCVTut10.html for counting :) 40 | if LoadOmmatidiag: 41 | PatternSize = (9, 5) 42 | # PatternSize = (3, 3) 43 | else: 44 | PatternSize = (7, 6) 45 | # Prepare object points, like (0,0,0), (1,0,0), (2,0,0) ...., (6,5,0) 46 | ObjectPoints = numpy.zeros((PatternSize[1] * PatternSize[0], 3), numpy.float32) 47 | ObjectPoints[:, :2] = numpy.mgrid[0:PatternSize[0], 48 | 0:PatternSize[1]].T.reshape(-1, 2) 49 | 50 | # Arrays to store object points and image points from all the images. 51 | # 3d point in real world space 52 | RealWorldPoints = [] 53 | # 2d points in image plane. 54 | ImagePoints = [] 55 | 56 | if not ImageList: 57 | print 'Download left*.jpg from http://git.io/MDUBRw and save these', \ 58 | 'images in the same folder as this script' 59 | exit('FilesNotFound') 60 | 61 | plt.figure('Original images', figsize=[12, 11]) 62 | for counter, FileName in enumerate(ImageList): 63 | print 'processing %s...' % FileName 64 | Image = cv2.imread(FileName) 65 | # Image = cv2.GaussianBlur(Image, (11, 11), 0) 66 | Image_BW = cv2.cvtColor(Image, cv2.COLOR_BGR2GRAY) 67 | 68 | # Find the chess board corners. 69 | Found, Corners = cv2.findChessboardCorners(Image_BW, PatternSize) 70 | 71 | # If found, refine the image points, add them to their lists and draw 72 | # the chessboard corners on the images 73 | if Found: 74 | print 'Found pattern!' 75 | # Find more precise corner points. The fist tuple influences the side 76 | # length of the search window. The second tuple is the dead region in 77 | # the middle of the search zone, see http://is.gd/xm6SXi 78 | cv2.cornerSubPix(Image_BW, Corners, (30, 30), (-1, -1), criteria) 79 | ImagePoints.append(Corners) 80 | RealWorldPoints.append(ObjectPoints) 81 | cv2.drawChessboardCorners(Image, PatternSize, Corners, Found) 82 | plt.imsave(FileName[:-4] + '_pattern.png', Image) 83 | print 'Saving found pattern image as %s' % FileName[:-4] + '_pattern.png' 84 | plt.subplot(3, 4, 11 - counter + 1) 85 | plt.imshow(ndimage.rotate(Image, 270), cmap='gray', 86 | interpolation='nearest') 87 | ImageTitle = os.path.basename(FileName) 88 | if Found: 89 | ImageTitle = '\n'.join(('Pattern found on', ImageTitle)) 90 | plt.title(ImageTitle) 91 | plt.axis('off') 92 | 93 | if not ImagePoints: 94 | print 'I was not able to find a pattern on any image, maybe try ' \ 95 | 'another "PatternSize"...' 96 | exit('PatternNotFound') 97 | 98 | print '\nApplying undistortion parameters found on %s images to %s ' \ 99 | 'Orignals\n' % (len(ImagePoints), len(OriginalsList)) 100 | 101 | # Calibrate camera if points are found 102 | RMS, CameraMatrix, DistortionCoefficients, rvecs, tvecs = \ 103 | cv2.calibrateCamera(RealWorldPoints, ImagePoints, Image_BW.shape, None, 104 | None) 105 | 106 | # Display images 107 | plt.figure('Undistorted images', figsize=[12, 11.2]) 108 | for counter, FileName in enumerate(OriginalsList): 109 | print 'undistorting %s...' % FileName 110 | Image = cv2.imread(FileName) 111 | NewCameraMatrix, ROI = \ 112 | cv2.getOptimalNewCameraMatrix(CameraMatrix, DistortionCoefficients, 113 | (Image.shape[1], Image.shape[0]), 1, 114 | (Image.shape[1], Image.shape[0])) 115 | 116 | # Undistort images 117 | UndistortedImage = cv2.undistort(Image, CameraMatrix, 118 | DistortionCoefficients, None, 119 | NewCameraMatrix) 120 | plt.imsave(FileName[:-4] + '_undistort.png', UndistortedImage) 121 | 122 | # The images are rotated in respect to the "viewing plane" of the user, 123 | # we thus plot them in the "from bottom right to top left" order 124 | plt.subplot(3, 4, 11 - counter + 1) 125 | # and to show them "correctly", we need to rotate the images 270° 126 | clip = 65 127 | plt.imshow(ndimage.rotate(UndistortedImage[clip:-clip, clip:-clip], 270), 128 | cmap='gray', interpolation='nearest') 129 | plt.axis('off') 130 | plt.gca().text(512, 640, FileName.split('-')[-1].split('.')[0], 131 | verticalalignment='center', horizontalalignment='center', 132 | bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 10}) 133 | plt.subplots_adjust(wspace=0, hspace=0) 134 | plt.suptitle(' '.join(['Undistorted images, clipped by %s pixels on each ' 135 | 'side' % clip])) 136 | plt.axis('off') 137 | 138 | print "RMS:", RMS 139 | print "camera matrix:\n\t", CameraMatrix 140 | print "distortion coefficients: ", DistortionCoefficients 141 | print 'Checkerboard pattern found on', len(ImagePoints), 'images' 142 | 143 | plt.show() 144 | -------------------------------------------------------------------------------- /aptina/NoiseVsExposure.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script to visualize the "Noise vs. Exposure" data from DevWare 3 | 4 | According to section 2.2.4.7 of the DevWareX help (http://is.gd/mt7FyF) we get 5 | signal levels and different kinds of noise measurements by using the "Sensor 6 | Control" > "Diagnostics" > "Noise vs. Exposure" tool. 7 | 8 | The analyis report gives 9 | - Signal 10 | - RMS Dyn (temporal noise), Avg Dyn (temporal noise) 11 | - FPN (fixed pattern noise), Col FPN (columnwise FPN), Row FPN (rowwise FPN) 12 | - Col Dyn (columnwise temporal noise) and Row Dyn (rowwise temporal noise). 13 | 14 | See the wiki page linkes above to see how the values are calulated. 15 | """ 16 | 17 | import glob 18 | import os 19 | import numpy 20 | import matplotlib.pyplot as plt 21 | 22 | 23 | def AskUser(Blurb, Choices): 24 | """ Ask for input. Based on function in MasterThesisIvan.ini """ 25 | print(Blurb) 26 | for Counter, Item in enumerate(sorted(Choices)): 27 | print ' * [' + str(Counter) + ']:', Item 28 | Selection = [] 29 | while Selection not in range(len(Choices)): 30 | try: 31 | Selection = int(input(' '.join(['Please enter the choice you', 32 | 'want [0-' + 33 | str(len(Choices) - 1) + 34 | ']:']))) 35 | except SyntaxError: 36 | print 'You actually have to select *something*' 37 | if Selection not in range(len(Choices)): 38 | print 'Try again with a valid choice' 39 | print 'You selected', sorted(Choices)[Selection] 40 | return sorted(Choices)[Selection] 41 | 42 | 43 | Root = '/afs/psi.ch/project/EssentialMed/Images/NoiseVsExposure' 44 | DataFiles = [os.path.basename(i) for i in 45 | glob.glob(os.path.join(Root, '*.txt'))] 46 | 47 | # Which plot do we show? 48 | whichone = DataFiles.index(AskUser('Which file should I show you?', DataFiles)) 49 | # If no manual selection, we can do 50 | # for whichone in range(len(DataFiles)): 51 | # in a loop... 52 | 53 | # Tell what we do 54 | Sensor = DataFiles[whichone][:-4].split('_')[0] 55 | Lens = DataFiles[whichone][:-4].split('_')[1] 56 | FramesPerSample = DataFiles[whichone][:-4].split('_')[2] 57 | MaximumExposure = DataFiles[whichone][:-4].split('_')[3] 58 | Decades = DataFiles[whichone][:-4].split('_')[4] 59 | SamplesPerDecade = DataFiles[whichone][:-4].split('_')[5] 60 | 61 | print 'We are showing the data from the', Sensor, 'CMOS with the', Lens, \ 62 | 'lens. The analysis was done with', FramesPerSample, \ 63 | 'frames per sample,', MaximumExposure, 'ms maximum exposure over', \ 64 | Decades, 'decades with', SamplesPerDecade, 'samples per decade.' 65 | print 66 | print 'If the exposure has not been recorded in "log scale" (you will see', \ 67 | 'it in the plots), the "Decades" correspond to the "minimal exposure"', \ 68 | 'and the "samples per decade" correspond to the "numbers of samples".' 69 | 70 | # Generate figure title, so we can distinguish the output 71 | Title = Sensor, Lens, FramesPerSample, 'Frames per Sample', \ 72 | MaximumExposure, 'ms Maximum Exposure', Decades, 'Decades', \ 73 | SamplesPerDecade, 'Samples/Decade' 74 | 75 | # Load the data from the file 76 | File = os.path.join(Root, DataFiles[whichone]) 77 | Data = numpy.loadtxt(File, skiprows=3) 78 | # First line gives the full range. Read it with the snippet based on 79 | # http://stackoverflow.com/a/1904455 80 | with open(File, 'r') as f: 81 | FullRange = int(f.readline().split('=')[1]) 82 | 83 | # Plot the data 84 | Labels = ['Exposure time [ms]', 'Signal', 'RMS Dyn (temporal noise)', 85 | 'Avg Dyn (temporal noise)', 'FPN (fixed pattern noise)', 86 | 'columnwise FPN', 'rowwise FPN', 'columnwise temporal noise', 87 | 'rowwise temporal noise'] 88 | # The title of the plot is split over all the suplots, otherwise it destroys 89 | # the layout due to its long length 90 | plt.figure(' '.join(Title), figsize=(16, 9)) 91 | # Signal 92 | ax = plt.subplot(131) 93 | plt.plot(Data[:, 0], Data[:, 1], 'o-', label=Labels[1]) 94 | plt.axhline(FullRange, linestyle='--', label='Full range') 95 | plt.xlabel(Labels[0]) 96 | plt.ylabel(Labels[1]) 97 | plt.title(' '.join(Title[:2])) 98 | box = ax.get_position() 99 | ax.set_position([box.x0, box.y0 + box.height * 0.25, 100 | box.width, box.height * 0.75]) 101 | plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1)) 102 | 103 | # We want to fit some of the data, thus make a linspace to fit over 104 | PolyfitRange = numpy.linspace(min(Data[:, 0]) - max(Data[:, 0]), 105 | 2 * max(Data[:, 0]), 200) 106 | fit = 9 107 | 108 | # Fixed pattern noise 109 | ax = plt.subplot(132) 110 | maxy = 0 111 | for i in range(4, 7): 112 | plt.plot(Data[:, 0], Data[:, i], 'o-', label=Labels[i]) 113 | maxy = max(max(Data[:, i]), maxy) 114 | plt.plot(Data[:, 0], (Data[:, 1] / max(Data[:, 1])) * max(Data[:, 4]), '--', 115 | label='"Signal" scaled to max(FPN)') 116 | polynomial = numpy.poly1d(numpy.polyfit(Data[:, 0], Data[:, 4], fit)) 117 | plt.plot(PolyfitRange, polynomial(PolyfitRange), '--', 118 | label='Polynomial fit (' + str(fit) + ') of FPN') 119 | plt.xlim([min(Data[:, 0]), max(Data[:, 0])]) 120 | plt.ylim([0, maxy * 1.1]) 121 | plt.xlabel(Labels[0]) 122 | plt.ylabel('FPN') 123 | plt.title(' '.join(Title[2:6])) 124 | box = ax.get_position() 125 | ax.set_position([box.x0, box.y0 + box.height * 0.25, 126 | box.width, box.height * 0.75]) 127 | plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1)) 128 | 129 | # Temporal noise 130 | ax = plt.subplot(133) 131 | maxy = 0 132 | for i in [2, 3, 7, 8]: 133 | plt.plot(Data[:, 0], Data[:, i], 'o-', label=Labels[i]) 134 | maxy = max(max(Data[:, i]), maxy) 135 | plt.plot(Data[:, 0], Data[:, 1] / max(Data[:, 1]) * max(Data[:, 2]), '--', 136 | label='"Signal" scaled to max(RMS Dyn)') 137 | polynomial = numpy.poly1d(numpy.polyfit(Data[:, 0], Data[:, 2], fit)) 138 | plt.plot(PolyfitRange, polynomial(PolyfitRange), '--', 139 | label='Polynomial fit (' + str(fit) + ') of RMS Dyn') 140 | plt.xlim([min(Data[:, 0]), max(Data[:, 0])]) 141 | plt.ylim([0, maxy * 1.1]) 142 | plt.xlabel(Labels[0]) 143 | plt.ylabel('Dyn') 144 | plt.title(' '.join(Title[6:])) 145 | box = ax.get_position() 146 | ax.set_position([box.x0, box.y0 + box.height * 0.25, 147 | box.width, box.height * 0.75]) 148 | plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1)) 149 | plt.savefig(os.path.join(Root, DataFiles[whichone][:-4] + '.png'), 150 | Transparent=True, bbox_inches='tight') 151 | plt.draw() 152 | plt.show() 153 | 154 | # ~ # Polynomial fit stuff 155 | # ~ plt.figure() 156 | # ~ x = Data[:, 0] 157 | # ~ y = Data[:, 9] 158 | # ~ xp = numpy.linspace(min(x)-max(x), 2*max(x), 222) 159 | # ~ 160 | # ~ plt.plot(x, y, '-x', label='original') 161 | # ~ for i in range(3,10,2): 162 | # ~ polynomial = numpy.poly1d(numpy.polyfit(x, y, i)) 163 | # ~ plt.plot(xp, polynomial(xp), '--', label=str(i)) 164 | # ~ plt.legend(loc='best') 165 | # ~ plt.xlim([min(x), max(x)]) 166 | # ~ plt.ylim([min(y), max(y)]) 167 | # ~ plt.draw() 168 | # ~ plt.show() 169 | -------------------------------------------------------------------------------- /Analysis/TarToArchive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | """ 4 | Script to `tar` each experiment folder and send it to ftp://archivftp.psi.ch/ 5 | for archival. 6 | 7 | The small bash script I wrote got much too complicated, thus I'm switching 8 | to a python script. The detection of folders and other things are based on 9 | DetectWhichImageIsRadiography.py 10 | """ 11 | 12 | import os 13 | import time 14 | import subprocess 15 | 16 | import functions 17 | 18 | 19 | # Setup 20 | # If Manual selection is true, the user is asked to select one of the 21 | # experiment IDs manually, otherwise the script just goes through all the IDs 22 | # it finds in the starting folder 23 | ManualSelection = False 24 | 25 | # Where shall we start? 26 | RootFolder = '/afs/psi.ch/project/EssentialMed/MasterArbeitBFH/XrayImages' 27 | case = 3 28 | StartingFolder = [] 29 | if case == 1: 30 | # Look through all folders 31 | StartingFolder = RootFolder 32 | elif case == 2: 33 | # Look for images of only one scintillator 34 | Scintillators = ('AppScinTech-HE', 'Pingseng', 'Hamamatsu', 'Toshiba') 35 | ChosenScintillator = functions.AskUser( 36 | 'Which scintillator do you want to look at?', Scintillators) 37 | StartingFolder = os.path.join(RootFolder, ChosenScintillator) 38 | elif case == 3: 39 | # Ask for what to do 40 | Scintillators = ('AppScinTech-HE', 'Pingseng', 'Hamamatsu', 'Toshiba') 41 | Sensors = ('AR0130', 'AR0132', 'MT9M001') 42 | Lenses = ('Computar-11A', 'Framos-DSL219D-650-F2.0', 43 | 'Framos-DSL224D-650-F2.0', 'Framos-DSL311A-NIR-F2.8', 44 | 'Framos-DSL949A-NIR-F2.0', 'Lensation-CHR4020', 45 | 'Lensation-CHR6020', 'Lensation-CM6014N3', 'Lensation-CY0614', 46 | 'TIS-TBL-6C-3MP', '') 47 | ChosenScintillator = functions.AskUser( 48 | 'Which scintillator do you want to look at?', Scintillators) 49 | ChosenSensor = functions.AskUser( 50 | 'Which sensor do you want to look at?', Sensors) 51 | ChosenLens = functions.AskUser( 52 | 'Which lens do you want to look at? ("empty" = "all")', 53 | Lenses) 54 | StartingFolder = os.path.join(RootFolder, ChosenScintillator, 55 | ChosenSensor, ChosenLens) 56 | 57 | # Look for all folders matching the naming convention 58 | Experiment, ExperimentID = functions.get_experiment_list(StartingFolder) 59 | print 'I found', len(Experiment), 'experiment IDs in', StartingFolder 60 | 61 | AnalyisList = [] 62 | if ManualSelection: 63 | # Ask the user which experimentID to show 64 | # Concatenate the list for display purposes: 65 | # http://stackoverflow.com/a/22642307/323100 66 | Choice = functions.AskUser('Which experiment do you want to archive?', 67 | ExperimentID) 68 | AnalyisList.append(ExperimentID.index(Choice)) 69 | print 70 | else: 71 | AnalyisList = range(len(Experiment)) 72 | 73 | # Go through each selected experiment 74 | for Counter, SelectedExperiment in enumerate(AnalyisList): 75 | # Inform the user and start logging 76 | print 80 * '-' 77 | print str(Counter + 1) + '/' + str(len(AnalyisList)) + \ 78 | ': Archiving experiment', ExperimentID[SelectedExperiment] 79 | # See if DarkDeleter.py was already run on this experiment 80 | DarkDeleterLog = os.path.join( 81 | os.path.dirname(Experiment[SelectedExperiment]), 82 | ExperimentID[SelectedExperiment] + '.deletion.log') 83 | if os.path.isfile(DarkDeleterLog): 84 | DoArchive = False 85 | # Did we really delete them? If not, we can repeat the analysis 86 | for line in open(DarkDeleterLog, 'r'): 87 | # The last line of the log file tells us if we did it or not... 88 | if 'Set "ReallyRemove"' in line: 89 | print 'We have a deletion log file, but did not actually', \ 90 | 'delete the files. Proceeding...' 91 | DoArchive = True 92 | else: 93 | DoArchive = True 94 | if not DoArchive: 95 | # If we removed some files it doesn't make sense to archive again 96 | print 97 | print '\tWe already ran DarkDeleter.py on experiment', \ 98 | ExperimentID[SelectedExperiment] 99 | print '\tWe thus do not archive it again.' 100 | print '\tTake a look at', os.path.join( 101 | os.path.dirname(Experiment[SelectedExperiment])[len( 102 | StartingFolder):], ExperimentID[SelectedExperiment] + 103 | '.archive.log'), 'for more info' 104 | print 105 | else: 106 | # Archive it! 107 | logfile = functions.myLogger( 108 | os.path.dirname(Experiment[SelectedExperiment]), 109 | ExperimentID[SelectedExperiment] + '.archive.log') 110 | logfile.info('Archival log file for Experiment ID %s, archived on ' 111 | '%s', ExperimentID[SelectedExperiment], 112 | time.strftime('%d.%m.%Y at %H:%M:%S')) 113 | logfile.info('\nMade with "%s" at Revision %s', os.path.basename( 114 | __file__), functions.get_git_hash()) 115 | logfile.info(80 * '-') 116 | # Tar the selected folder 117 | TarCommand = ['tar', '-czf', Experiment[SelectedExperiment] + 118 | '.tar.gz', '-C', 119 | os.path.dirname(Experiment[SelectedExperiment]), 120 | os.path.basename(Experiment[SelectedExperiment])] 121 | print 'Packing', ExperimentID[SelectedExperiment] 122 | logfile.info('Packing the original files with') 123 | logfile.info('---') 124 | logfile.info(' '.join(TarCommand)) 125 | logfile.info('---') 126 | packit = subprocess.Popen(TarCommand, stdout=subprocess.PIPE) 127 | output, error = packit.communicate() 128 | if output: 129 | print output 130 | if error: 131 | print error 132 | time.sleep(0.5) 133 | # FTP the file to the PSI archive 134 | # We use the bookmark feature of 'lftp' to access the password. It's in 135 | # ~/.lftp/bookmarks... 136 | LFTPcommand = 'lftp -e \"mkdir -p ' + \ 137 | os.path.dirname( 138 | Experiment[SelectedExperiment][len(RootFolder) + 1:]) + \ 139 | ';put ' + str(Experiment[SelectedExperiment]) + '.tar.gz -o ' + \ 140 | os.path.join( 141 | os.path.dirname( 142 | Experiment[SelectedExperiment][len(RootFolder) + 1:]), 143 | ExperimentID[SelectedExperiment] + '.tar.gz') + ';bye\" Ivan' 144 | print 'Transferring', \ 145 | ExperimentID[SelectedExperiment] + '.tar.gz to archive' 146 | print LFTPcommand 147 | logfile.info('Transferring %s to the PSI archive with', ExperimentID[ 148 | SelectedExperiment] + '.tar.gz') 149 | logfile.info('---') 150 | logfile.info(LFTPcommand) 151 | logfile.info('---') 152 | os.system(LFTPcommand) 153 | time.sleep(0.5) 154 | print 'Deleting archival file', \ 155 | os.path.basename(Experiment[SelectedExperiment]) + '.tar.gz' 156 | os.remove(Experiment[SelectedExperiment] + '.tar.gz') 157 | time.sleep(0.5) 158 | 159 | print 160 | print 'Archival of', StartingFolder, 'finished' 161 | -------------------------------------------------------------------------------- /MTF_reader_and_plotter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to calculate the MTF from a real image. 5 | 6 | Based on /afs/EssentialMed/Dev/MTF.py 7 | """ 8 | 9 | import matplotlib.pylab as plt 10 | import numpy as np 11 | import os 12 | 13 | plt.ion() 14 | 15 | # SETUP 16 | SelectStartPointManually = False 17 | SelectEdgeManually = False 18 | PolynomialOrder = 5 19 | 20 | # Images 21 | ImagePath = '/afs/psi.ch/project/EssentialMed/Images' 22 | ImageDir = '11-MTF' 23 | 24 | Camera = 'iPhone' 25 | # Camera = 'tiscam' 26 | # Camera = 'Elphel' 27 | 28 | if Camera == 'iPhone': 29 | # use iPhone images 30 | ImageFile = 'iPhone_with_xray_film.jpg' 31 | ImageFile = 'iPhone_with_xray_film_hdr.jpg' 32 | ImageFile = 'iPhone_with_xray_film_window.jpg' 33 | ImageFile = 'iPhone_with_xray_film_window_hdr.jpg' 34 | elif Camera == 'tiscam': 35 | # 'The imaging source' camera images from different objectives 36 | Objective = 9 # 3,6 or 9 37 | if Objective == 3: 38 | ObjectiveDir = 3.6 39 | ImageFile = 'shot0099.png' # visually the best one 40 | elif Objective == 6: 41 | ObjectiveDir = 6.0 42 | ImageFile = 'shot0364.png' # visually the best one 43 | elif Objective == 9: 44 | ObjectiveDir = 9.6 45 | ImageFile = 'shot0072.png' # visually the best one 46 | Camera = Camera + '_' + str(ObjectiveDir) 47 | elif Camera == 'Elphel': 48 | # Elphel images 49 | ImageFile = 'image.jpg' 50 | else: 51 | print 'I do not know what to do, exiting' 52 | exit() 53 | 54 | 55 | def rgb2gray(rgb): 56 | ''' 57 | convert an image from rgb to grayscale 58 | http://stackoverflow.com/a/12201744/323100 59 | ''' 60 | return np.dot(rgb[..., :3], [0.299, 0.587, 0.144]) 61 | 62 | ImageToLoad = os.path.join(ImagePath, ImageDir, Camera, ImageFile) 63 | 64 | ImageRGB = plt.imread(ImageToLoad) 65 | Image = rgb2gray(ImageRGB) 66 | 67 | 68 | plt.imshow(np.fft.fft2(Image)) 69 | # plt.imshow(Image) 70 | plt.ioff() 71 | plt.show() 72 | 73 | exit() 74 | 75 | 76 | def MTF(edgespreadfunction): 77 | ''' 78 | Compute the modulation transfer function (MTF). 79 | 80 | The MTF is defined as the FFT of the line spread function. 81 | The line spread function is defined as the derivative of the edge spread 82 | function. The edge spread function are the values along an edge, ideally a 83 | knife-edge test target. See an explanation here: http://is.gd/uSC5Ve 84 | ''' 85 | linespreadfunction = np.diff(edgespreadfunction) 86 | return np.abs(np.fft.fft(linespreadfunction)) 87 | 88 | 89 | def LSF(edgespreadfunction): 90 | ''' 91 | Compute the modulation transfer function (MTF). 92 | 93 | The MTF is defined as the FFT of the line spread function. 94 | The line spread function is defined as the derivative of the edge spread 95 | function. The edge spread function are the values along an edge, ideally a 96 | knife-edge test target. See an explanation here: http://is.gd/uSC5Ve 97 | ''' 98 | return np.abs(np.diff(edgespreadfunction)) 99 | 100 | 101 | def polynomialfit(data, order): 102 | ''' 103 | calculate the polynomial fit of an input for a defined degree 104 | ''' 105 | x, y = range(len(data)), data 106 | coefficients = np.polyfit(x, y, order) 107 | return np.polyval(coefficients, x) 108 | 109 | ImageToLoad = os.path.join(ImagePath, ImageDir, Camera, ImageFile) 110 | print 'reading', ImageToLoad 111 | 112 | # Read the image and convert it to grayscale rightaway 113 | ImageRGB = plt.imread(ImageToLoad) 114 | Image = rgb2gray(ImageRGB) 115 | 116 | ImageWidth = Image.shape[0] 117 | ImageHeight = Image.shape[1] 118 | print 'The image we loaded is', ImageWidth, 'by', ImageHeight, \ 119 | 'pixels big. That is', round(ImageWidth * ImageHeight / 1e6, 3), 'MPx.' 120 | 121 | plt.subplot(221) 122 | plt.imshow(ImageRGB, origin='lower') 123 | plt.title('Pick point for drawing\n horizontal and vertical profile') 124 | if SelectStartPointManually: 125 | PickPoint = plt.ginput(1) 126 | else: 127 | if Camera == 'iPhone': 128 | PickPoint = [[1500, 1000]] 129 | elif Camera[:6] == 'tiscam': 130 | # Select middle of image... 131 | PickPoint = [[ImageHeight / 2, ImageWidth / 2]] 132 | elif Camera == 'Elphel': 133 | PickPoint = [[ImageHeight / 2, ImageWidth / 2]] 134 | plt.title('Original image') 135 | Horizon = int(PickPoint[0][1]) 136 | Vertigo = int(PickPoint[0][0]) 137 | if SelectStartPointManually: 138 | print 'You selected horizontal line', Horizon, 'and vertical line', Vertigo 139 | else: 140 | print 'I selected horizontal line', Horizon, 'and vertical line', Vertigo 141 | plt.hlines(Horizon, 0, ImageHeight, 'r') 142 | plt.vlines(Vertigo, 0, ImageWidth, 'b') 143 | plt.draw() 144 | plt.subplot(223) 145 | HorizontalProfile = Image[Horizon, :] 146 | plt.plot(HorizontalProfile, 'r') 147 | plt.title('Horizontal Profile') 148 | # plt.xlim(0, ImageHeight) 149 | # plt.ylim(0, 256) 150 | plt.subplot(222) 151 | VerticalProfile = Image[:, Vertigo] 152 | plt.plot(VerticalProfile, range(ImageWidth), 'b') 153 | # plt.xlim(0, 256) 154 | # plt.ylim(0, ImageWidth) 155 | plt.title('Vertical Profile') 156 | plt.draw() 157 | 158 | print 'The horizontal profile (red) goes from', min(HorizontalProfile), 'to',\ 159 | max(HorizontalProfile) 160 | print 'The vertical profile (blue) goes from', min(VerticalProfile), 'to',\ 161 | max(VerticalProfile) 162 | 163 | # Set range of the region we want to look at to 'Edgerange', about 10% of Image 164 | # width 165 | EdgeRange = int(round(Image.shape[0] * .05 / 10) * 10) 166 | 167 | plt.figure(figsize=(16, 9)) 168 | plt.subplot(311) 169 | plt.plot(VerticalProfile) 170 | if SelectEdgeManually: 171 | plt.title('Select approximate middle of knife edge') 172 | EdgePosition = plt.ginput(1) 173 | plt.title('Vertical Profile\n(zoom reguion selected manually, width = ' + 174 | str(EdgeRange) + ' px, approx. 5% of image)') 175 | else: 176 | EdgePosition = [[LSF(VerticalProfile).argmax(), np.nan]] 177 | plt.title('Vertical Profile\n(zoom region selected automatically, width ' + 178 | '= ' + str(EdgeRange) + ' px, approx. 5% of image)') 179 | plt.axvspan(EdgePosition[0][0] - EdgeRange, EdgePosition[0][0] + EdgeRange, 180 | facecolor='r', alpha=0.5) 181 | 182 | plt.subplot(312) 183 | plt.plot(LSF(VerticalProfile)) 184 | plt.axvspan(EdgePosition[0][0] - EdgeRange, EdgePosition[0][0] + EdgeRange, 185 | facecolor='r', alpha=0.5) 186 | plt.title('LSF') 187 | 188 | # plt.subplot(413) 189 | # plt.plot(MTF(VerticalProfile)) 190 | # plt.title('MTF') 191 | 192 | plt.subplot(3, 3, 7) 193 | plt.plot(VerticalProfile) 194 | plt.xlim(EdgePosition[0][0] - EdgeRange, EdgePosition[0][0] + EdgeRange) 195 | plt.title('Zoomed Edge') 196 | 197 | plt.subplot(3, 3, 8) 198 | plt.plot(LSF(VerticalProfile)) 199 | plt.xlim(EdgePosition[0][0] - EdgeRange, EdgePosition[0][0] + EdgeRange) 200 | plt.title('Zoomed LSF') 201 | 202 | plt.subplot(3, 3, 9) 203 | plt.plot(MTF(VerticalProfile), alpha=0.5) 204 | plt.plot(polynomialfit(MTF(VerticalProfile), PolynomialOrder), linewidth=5) 205 | plt.xlim(0, len(MTF(VerticalProfile)) / 2) 206 | plt.title('MTF with polynomial fit of order ' + str(PolynomialOrder) + 207 | '\nwith a minimum at :' + 208 | str(round(min(polynomialfit(MTF(VerticalProfile), PolynomialOrder)), 209 | 3))) 210 | 211 | plt.ioff() 212 | plt.show() 213 | -------------------------------------------------------------------------------- /AngularOpening.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Calculate the angular opening of the lens, including the shades that we have 5 | to build in between. 6 | """ 7 | from __future__ import division 8 | import optparse 9 | import sys 10 | import os 11 | import numpy 12 | import matplotlib.pyplot as plt 13 | from matplotlib.patches import Wedge 14 | from matplotlib.patches import Rectangle 15 | 16 | os.system('clear') 17 | 18 | # Use Pythons Optionparser to define and read the options, and also 19 | # give some help to the user 20 | parser = optparse.OptionParser() 21 | usage = "usage: %prog [options] arg" 22 | parser.add_option('-d', dest='Distance', type='float', default=134, 23 | metavar='123', help='Scintillator-CMOS distance [mm]. ' 24 | 'Default = %default mm') 25 | parser.add_option('-f', dest='FOV', type='float', default=450 / 3, 26 | metavar='430', help='Desired field of view [mm]. Default = ' 27 | '%default mm') 28 | parser.add_option('-o', dest='Overlap', type='float', default=2, 29 | metavar='16', help='Overlap between the images [%]. Default ' 30 | '= %default %') 31 | parser.add_option('-p', dest='ParaventLength', type='float', default=100, 32 | metavar='123', help='Length of the paravents. Default = ' 33 | '%default mm') 34 | parser.add_option('-l', dest='LensLength', type='float', default=16.8, 35 | metavar='11.3', help='Length of the lens. Default = ' 36 | '%default mm') 37 | parser.add_option('-b', dest='BackFocalLength', type='float', default=6.5, 38 | metavar='9.0', help='Back focal length of the lens. Default ' 39 | '= %default mm') 40 | parser.add_option('-s', dest='SaveImage', default=True, action='store_true', 41 | help='Write output, (Default: %default)') 42 | (options, args) = parser.parse_args() 43 | 44 | # TBL 6 C 3MP specifications, as from TIS and copied here: http://cl.ly/YQ4Z 45 | # FOV = 145 mm without overlap 46 | # LensLengtht = 10 mm 47 | # BackFocalLength = 6.5 mm 48 | # Measured FOV at a distance of 13 cm is 135 x 105 mm 49 | 50 | # show the help if the needed parameters (distance and FOV) are not given 51 | if options.Distance is None or options.FOV is None: 52 | parser.print_help() 53 | print '' 54 | print 'Example:' 55 | print 'The command below shows the configuration of a detector with ' 56 | print 'an optics with an opening angle of 78° used to get a field' 57 | print 'of view of 50 cm:' 58 | print '' 59 | print sys.argv[0], '-a 78 -f 50' 60 | print '' 61 | sys.exit(1) 62 | print '' 63 | 64 | # tan(\alpha/2) = (FOV/2) / Distance 65 | # Distance = (FOV/2)/tan(\alpha/2) 66 | 67 | print 'We calculate with a CMOS-Scintillator distance of', options.Distance, \ 68 | 'mm.' 69 | print 'With a back focal length of', options.BackFocalLength, \ 70 | 'mm and a lens length of', options.LensLength, 'mm we have a distance of',\ 71 | options.Distance - options.BackFocalLength - options.LensLength, \ 72 | 'mm from the front of the lens to the scintillator.' 73 | 74 | print 'The FOV is corrected with an overlap of', options.Overlap, '% from', \ 75 | options.FOV, 'mm to', 76 | options.FOV = options.FOV * (1 + (options.Overlap / 100)) 77 | print options.FOV, 'mm.' 78 | 79 | print 'For a visible FOV of', options.FOV, 'mm at a distance of', \ 80 | options.Distance, 'mm we get a calculated opening angle of the lens of', 81 | OpeningAngle = numpy.rad2deg(numpy.arctan((options.FOV / 2) / 82 | options.Distance)) * 2 83 | print round(OpeningAngle, 1), 'degrees' 84 | 85 | plt.figure(figsize=(5, 15)) 86 | for Displacement in (0, - options.FOV / (1 + options.Overlap / 100), 87 | options.FOV / (1 + options.Overlap / 100)): 88 | # Central axis 89 | plt.axhline(Displacement, color='k', linestyle='--') 90 | 91 | # CMOS 92 | cmoscolor = 'b' 93 | plt.plot((0, 0), (Displacement + 3, Displacement - 3), linewidth='5', 94 | color=cmoscolor) 95 | 96 | # Lens 97 | rect = Rectangle((options.BackFocalLength, Displacement - 14 / 2), 98 | options.LensLength, 14, facecolor="#aaaaaa") 99 | plt.gca().add_patch(rect) 100 | 101 | # Opening angle, based on CMOS 102 | wedge = Wedge((0, Displacement), options.Distance * 0.309, 103 | -OpeningAngle / 2, OpeningAngle / 2, fill=True, color='r', 104 | alpha=0.125) 105 | plt.gca().add_patch(wedge) 106 | plt.plot((0, options.Distance), 107 | (Displacement, Displacement + options.FOV / 2), color='k', 108 | linestyle='--', alpha=0.25) 109 | plt.plot((0, options.Distance), 110 | (Displacement, Displacement - options.FOV / 2), color='k', 111 | linestyle='--', alpha=0.25) 112 | 113 | # Scintillator FOV 114 | screencolor = 'k' 115 | plt.plot([options.Distance, options.Distance], 116 | [Displacement + (options.FOV / 2), 117 | Displacement - (options.FOV / 2)], linewidth='6', 118 | color=screencolor) 119 | screencolor = 'g' 120 | plt.plot([options.Distance, options.Distance], 121 | [Displacement + (options.FOV / 2), 122 | Displacement - (options.FOV / 2)], linewidth='4', 123 | color=screencolor) 124 | 125 | # FOV drawn from center of lens 126 | beamcolor = 'r' 127 | plt.plot([options.BackFocalLength + options.LensLength, 128 | options.Distance], [Displacement, Displacement + options.FOV / 129 | 2], beamcolor) 130 | plt.plot([options.BackFocalLength + options.LensLength, 131 | options.Distance], [Displacement, Displacement - options.FOV / 132 | 2], beamcolor) 133 | 134 | # Paravents. Position calculated back from overlap 135 | paraventcolor = 'k' 136 | plt.plot([0, options.ParaventLength], 137 | [Displacement - (options.FOV / (1 + options.Overlap / 100) / 2), 138 | Displacement - (options.FOV / (1 + options.Overlap / 100) / 2)], 139 | linewidth='5', color=paraventcolor) 140 | 141 | # Paravent blocking, 142 | beamcolor = 'g' 143 | plt.plot([options.BackFocalLength + options.LensLength, options.Distance], 144 | [Displacement, Displacement + options.FOV / 2], beamcolor) 145 | plt.plot([options.BackFocalLength + options.LensLength, options.Distance], 146 | [Displacement, Displacement - options.FOV / 2], beamcolor) 147 | 148 | # Nice plotting 149 | 150 | plt.title('Angular opening: ' + str(round(OpeningAngle, 2)) + '\nFOV size: ' + 151 | str(options.FOV) + ' mm (including overlap of ' + 152 | str(options.Overlap) + ' %)\nWorking Distance: ' + 153 | str('%.2f' % options.Distance) + ' mm\nParavent length: ' + 154 | str('%.2f' % options.ParaventLength) + ' mm') 155 | plt.xlabel('Distance [mm]') 156 | plt.axis('equal') 157 | 158 | if options.SaveImage: 159 | SaveName = 'Paravents_' + str(str('%.2f' % OpeningAngle)) + '_wd_' + \ 160 | str('%.2f' % options.Distance) + 'mm_FOV_' + \ 161 | str('%.2f' % options.FOV) + 'mm' 162 | FigureName = ''.join([SaveName, '.png']) 163 | plt.savefig(FigureName) 164 | print 'Figure saved to ' + FigureName 165 | 166 | plt.show() 167 | --------------------------------------------------------------------------------