diff --git a/README.md b/README.md index 892af198fd60b87200647db4639f033123113e86..32f1ba5406e30f3fe51a672105ecf9e58aa58467 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + # VIPS Documentation Tor-Einar Skog, NIBIO -Last edit: 2022-09-22 +Last edit: 2023-02-21 ## About this project Since VIPS consists of multiple subsystems, this project is a starting point for VIPS documentation, describing the system as a whole. Documentation to each subsystem is linked from here. diff --git a/illustrations/1000000000000231000000EA513D951D10FB401A.png b/illustrations/1000000000000231000000EA513D951D10FB401A.png new file mode 100644 index 0000000000000000000000000000000000000000..f340ecc7275d9dedc76f6b723d540aa74ebb8545 Binary files /dev/null and b/illustrations/1000000000000231000000EA513D951D10FB401A.png differ diff --git a/illustrations/100000000000023E00000127794CBD73893684FD.png b/illustrations/100000000000023E00000127794CBD73893684FD.png new file mode 100644 index 0000000000000000000000000000000000000000..7d596b29914b61e050e025902228bb9159c9b6dc Binary files /dev/null and b/illustrations/100000000000023E00000127794CBD73893684FD.png differ diff --git a/illustrations/1000020100000133000000BB86A5F55961F51482.png b/illustrations/1000020100000133000000BB86A5F55961F51482.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5ac8bbb91ab1f92a6b81e4c119ba9150a6a97b Binary files /dev/null and b/illustrations/1000020100000133000000BB86A5F55961F51482.png differ diff --git a/illustrations/10000201000001C600000114CFEBC2611F68F805.png b/illustrations/10000201000001C600000114CFEBC2611F68F805.png new file mode 100644 index 0000000000000000000000000000000000000000..5a617b04c92d01f32a8a4ba079fdc6b3146a8ee5 Binary files /dev/null and b/illustrations/10000201000001C600000114CFEBC2611F68F805.png differ diff --git a/illustrations/1000020100000C87000003AB1EE1DCC0A9D89A5A.png b/illustrations/1000020100000C87000003AB1EE1DCC0A9D89A5A.png new file mode 100644 index 0000000000000000000000000000000000000000..636bf7f3b206f709c94f4aa93175a065413d1b1f Binary files /dev/null and b/illustrations/1000020100000C87000003AB1EE1DCC0A9D89A5A.png differ diff --git a/illustrations/2000000200000141000000D2526EAC50AF7907D2.eps b/illustrations/2000000200000141000000D2526EAC50AF7907D2.eps new file mode 100644 index 0000000000000000000000000000000000000000..28dbd2ff887463aabb804c632973336db2cdcfe0 --- /dev/null +++ b/illustrations/2000000200000141000000D2526EAC50AF7907D2.eps @@ -0,0 +1,905 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner +%%Title: LogoRundet_sh_med_farge.eps +%%CreationDate: Fri Apr 1 14:48:54 2011 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%Pages: 1 +%%BoundingBox: 14 14 334 223 +%%EndComments +%%BeginProlog +% Use own dictionary to avoid conflicts +10 dict begin +%%EndProlog +%%Page: 1 1 +% Translate for offset +14.173228346456694 14.173228346456694 translate +% Translate to begin of first scanline +0 208.08000000000004 translate +319.68000000000006 -208.08000000000004 scale +% Image geometry +444 289 8 +% Transformation matrix +[ 444 0 0 289 0 0 ] +% Strings to hold RGB-samples per scanline +/rstr 444 string def +/gstr 444 string def +/bstr 444 string def +{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop} +true 3 +%%BeginData: 18257 ASCII Bytes +colorimage +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcDYJoK@d*XT*e~> +JcDYJoRDGlXT*e~> +JcDYJoML2>XT*e~> +JcDqRjZS1pZi>O~> +JcDqRjaVj]Zi>O~> +JcDqRj\^U/Zi>O~> +JcE%Ui]VkmZi>O~> +JcE%UidZOZZi>O~> +JcE%Ui_b:,Zi>O~> +JcE1Yh`ZPjZN#F~> +JcE1Yhg^4WZN#F~> +JcE1Yhbet)ZN#F~> +JcE7[hE?GiZ2]=~> +JcE7[hLC+VZ2]=~> +JcE7[hGJk(Z2]=~> +JcE=]h*$>hYlB4~> +JcE=]h1("UYlB4~> +JcE=]h,/b'YlB4~> +JcEC_gHC,fYlB4~> +JcEC_gOFeSYlB4~> +JcEC_gJNP%YlB4~> +JcEIag-(#eYQ'+~> +JcEIag4+\RYQ'+~> +JcEIag/3G$YQ'+~> +JcELbg-(#eY5a"~> +JcELbg4+\RY5a"~> +JcELbg/3G$Y5a"~> +JcEOcg-(#eXoEn~> +JcEOcg4+\RXoEn~> +JcEOcg/3G$XoEn~> +JcERdffaodXoEn~> +JcERdfmeSQXoEn~> +JcERdfhm>#XoEn~> +JcEUeffaodXT*e~> +JcEUefmeSQXT*e~> +JcEUefhm>#XT*e~> +JcEXfffaodX8d\~> +JcEXffmeSQX8d\~> +JcEXffhm>#X8d\~> +JcE[gffaodWrIS~> +JcE[gfmeSQWrIS~> +JcE[gfhm>#WrIS~> +JcE^hfKFfcWrIS~> +JcE^hfRJJPWrIS~> +JcE^hfMR5"WrIS~> +JcEaifKFfcWW.J~> +JcEaifRJJPWW.J~> +JcEaifMR5"WW.J~> +JcEdjfKFfcW;hA~> +JcEdjfRJJPW;hA~> +JcEdjfMR5"W;hA~> +JcEgkf0+]bW;hA~> +JcEgkf7/AOW;hA~> +JcEgkf27,!W;hA~> +JcEgkfKFfcVuM8~> +JcEgkfRJJPVuM8~> +JcEgkfMR5"VuM8~> +JcEjlfKFfcVZ2/~> +JcEjlfRJJPVZ2/~> +JcEjlfMR5"VZ2/~> +JcEmmfKFfcV>l&~> +JcEmmfRJJPV>l&~> +JcEmmfMR5"V>l&~> +JcEpnf0+]bV>l&~> +JcEpnf7/AOV>l&~> +JcEpnf27,!V>l&~> +JcEpnfKFfcV#Pr~> +JcEpnfRJJPV#Pr~> +JcEpnfMR5"V#Pr~> +JcEsofKFfcU]5i~> +JcEsofRJJPU]5i~> +JcEsofMR5"U]5i~> +JcF!pfKFfcUAo`~> +JcF!pfRJJPUAo`~> +JcF!pfMR5"UAo`~> +JcF!pfKFfcUAo`~> +JcF!pfRJJPUAo`~> +JcF!pfMR5"UAo`~> +JcF$qfKFfcU&TW~> +JcF$qfRJJPU&TW~> +JcF$qfMR5"U&TW~> +JcF'rfKFfcT`9N~> +JcF'rfRJJPT`9N~> +JcF'rfMR5"T`9N~> +JcF*sfKFfcTDsE~> +JcF*sfRJJPTDsE~> +JcF*sfMR5"TDsE~> +JcF*sfKFfcTDsE~> +JcF*sfRJJPTDsE~> +JcF*sfMR5"TDsE~> +JcF-tfKFfcT)X<~> +JcF-tfRJJPT)X<~> +JcF-tfMR5"T)X<~> +JcF0ufKFfcSc=3~> +JcF0ufRJJPSc=3~> +JcF0ufMR5"Sc=3~> +JcF4!fKFfcSH"*~> +JcF4!fRJJPSH"*~> +JcF4!fMR5"SH"*~> +JcF4!fKFfcSH"*~> +JcF4!fRJJPSH"*~> +JcF4!fMR5"SH"*~> +JcF7"fKFfcS,\!~> +JcF7"fRJJPS,\!~> +JcF7"fMR5"S,\!~> +JcF:#fKFfcRf@m~> +JcF:#fRJJPRf@m~> +JcF:#fMR5"Rf@m~> +JcF:#ffaodRK%d~> +JcF:#fmeSQRK%d~> +JcF:#fhm>#RK%d~> +JcF=$fKFfcRK%d~> +JcF=$fRJJPRK%d~> +JcF=$fMR5"RK%d~> +JcF@%fKFfcR/_[~> +JcF@%fRJJPR/_[~> +JcF@%fMR5"R/_[~> +JcFC&fKFfcQiDR~> +JcFC&fRJJPQiDR~> +JcFC&fMR5"QiDR~> +JcFC&ffaodQN)I~> +JcFC&fmeSQQN)I~> +JcFC&fhm>#QN)I~> +JcFF'fKFfcQN)I~> +JcFF'fRJJPQN)I~> +JcFF'fMR5"QN)I~> +JcFI(fKFfcQ2c@~> +JcFI(fRJJPQ2c@~> +JcFI(fMR5"Q2c@~> +JcFI(ffaodPlH7~> +JcFI(fmeSQPlH7~> +JcFI(fhm>#PlH7~> +JcFL)ffaodPQ-.~> +JcFL)fmeSQPQ-.~> +JcFL)fhm>#PQ-.~> +JcFO*fKFfcPQ-.~> +JcFO*fRJJPPQ-.~> +JcFO*fMR5"PQ-.~> +JcFR+fKFfcP5g%~> +JcFR+fRJJPP5g%~> +JcFR+fMR5"P5g%~> +JcFR+ffaodOoKq~> +JcFR+fmeSQOoKq~> +JcFR+fhm>#OoKq~> +JcFU,ffaodOT0h~> +JcFU,fmeSQOT0h~> +JcFU,fhm>#OT0h~> +JcFX-fKFfcOT0h~> +JcFX-fRJJPOT0h~> +JcFX-fMR5"OT0h~> +JcF[.fKFfcO8j_~> +JcF[.fRJJPO8j_~> +JcF[.fMR5"O8j_~> +JcF[.ffaodNrOV~> +JcF[.fmeSQNrOV~> +JcF[.fhm>#NrOV~> +JcF^/fKFfcNrOV~> +JcF^/fRJJPNrOV~> +JcF^/fMR5"NrOV~> +JcFa0fKFfcNW4M~> +JcFa0fRJJPNW4M~> +JcFa0fMR5"NW4M~> +JcFa0ffaodN;nD~> +JcFa0fmeSQN;nD~> +JcFa0fhm>#N;nD~> +JcFd1ffaodMuS;~> +JcFd1fmeSQMuS;~> +JcFd1fhm>#MuS;~> +JcFg2fKFfcMuS;~> +JcFg2fRJJPMuS;~> +JcFg2fMR5"MuS;~> +JcFj3fKFfcMZ82~> +JcFj3fRJJPMZ82~> +JcFj3fMR5"MZ82~> +JcFj3ffaodM>r)~> +JcFj3fmeSQM>r)~> +JcFj3fhm>#M>r)~> +JcFm4ffaodM#Vu~> +JcFm4fmeSQM#Vu~> +JcFm4fhm>#M#Vu~> +JcFp5fKFfcM#Vu~> +JcFp5fRJJPM#Vu~> +JcFp5fMR5"M#Vu~> +JcFp5ffaodL];l~> +JcFp5fmeSQL];l~> +JcFp5fhm>#L];l~> +JcF[.iB;blLAuc~> +JcF[.iI?FYLAuc~> +JcF[.iDG1+LAuc~> +JcFR+jZS1pL&ZZ~> +JcFR+jaVj]L&ZZ~> +JcFR+j\^U/L&ZZ~> +JcFL)k<4CrL&ZZ~> +JcFL)kC8'_L&ZZ~> +JcFL)k>?g1L&ZZ~> +JcFF'l90^uK`?Q~> +JcFF'l@4BbK`?Q~> +JcFF'l;<-4K`?Q~> +JcFC&lofq"KE$H~> +JcFC&m!jTdKE$H~> +JcFC&lqr?6KE$H~> +JcF@%mQH.$K)^?~> +JcF@%mXKffK)^?~> +JcF@%mSSQ8K)^?~> +JcF:#n3)@&K)^?~> +JcF:#n:-#hK)^?~> +JcF:#n54c:K)^?~> +JcF:#nNDI'JcC6~> +JcF:#nUH,iJcC6~> +JcF:#nPOl;JcC6~> +!<;LNJcFp5o0%[)l2SO^g&HR~> +!<;M;JcFp5o7)>kl2SPKg&HR~> +!<;LbJcFp5o21)=l2SOrg&HR~> +!<;4FJcG0<V*2QmZT[7HJ,~> +!<;53JcG0<V165ZZ[^p5J,~> +!<;4ZJcG0<V,=u,ZVfZ\J,~> +s8V.CJcG9?T0:<rX$,PDJ,~> +s8V/0JcG9?T7=u_X+041J,~> +s8V.WJcG9?T2E`1X&7sXJ,~> +rr;"AJcG?AS3>0tVEO,BJ,~> +rr;#.JcG?AS:AiaVLRe/J,~> +rr;"UJcG?AS5IT3VGZOVJ,~> +rr:n>JcGECRm#4"U-7c@J,~> +rr:o+JcGECRt&ldU4;G-J,~> +rr:nRJcGECRo.W6U/C1TJ,~> +rVtb<JcGHDR6B1%SiuE>J,~> +rVtc)JcGHDR=EigSq$)+J,~> +rVtbPJcGHDR8MT9Sl+hRJ,~> +r;YV:JcGNFQTa(&Rm$0=J,~> +r;YW'JcGNFQ[d`hRt'i*J,~> +r;YVNJcGNFQVlK:Ro/SQJ,~> +r;YS9JcGQGQ9F%'R6C!<J,~> +r;YT&JcGQGQ@I]iR=FZ)J,~> +r;YSMJcGQGQ;QH;R8NDPJ,~> +qu>G7JcGTHPs+%)QTag;J,~> +qu>H$JcGTHQ%.]kQ[eK(J,~> +qu>GKJcGTHPu6H=QVm5OJ,~> +qZ#>6JcGWIPWe"*Ps+X:J,~> +qZ#?#JcGWIP^hZlQ%/<'J,~> +qZ#>JJcGWIPYpE>Pu7&NJ,~> +qZ#;5JcGZJP!.n+P<JI9J,~> +qZ#<"JcGZJP(2QmPCN-&J,~> +qZ#;IJcGZJP#:<?P>UlMJ,~> +q>]24JcGZJP!.t-OZi:8J,~> +q>]3!JcGZJP(2WoOals%J,~> +q>]2HJcGZJP#:BAO\t]LJ,~> +q#B&2JcG`LOZhq.O$3+7J,~> +q#B&tJcG`LOalTpO+6d$J,~> +q#B&FJcG`LO\t?BO&>NKJ,~> +q#B&2JcG`LOZhq.O$3+7J,~> +q#B&tJcG`LOalTpO+6d$J,~> +q#B&FJcG`LO\t?BO&>NKJ,~> +p]&r1JcGcMO?Mn/NBQq6J,~> +p]&rsJcGcMOFQQqNIUU#J,~> +p]&rEJcGcMOAY<CND]?JJ,~> +pA`i0Jc>`MO$2k0M`pb5J,~> +pA`irJc>`MO+6NrMgtF"J,~> +pA`iDJc>`MO&>9DMc'0IJ,~> +p&E`/JcC`nmJi)-p&BO~> +p&E`qJcCa[mJi)op&BO~> +p&E`CJcCa-mJi)Ap&BO~> +p&E].K)^fnn,J8.p&BO~> +p&E]pK)^g[n,J8pp&BO~> +p&E]BK)^g-n,J8Bp&BO~> +o`*T-K`?rnnGe>.pA]X~> +o`*ToK`?s[nGe>ppA]X~> +o`*TAK`?s-nGe>BpA]X~> +oDdN-K`?rnnGe>.pA]X~> +oDdNoK`?s[nGe>ppA]X~> +oDdNAK`?s-nGe>BpA]X~> +oDdK,L&[#no)FM/pA]X~> +oDdKnL&[$[o)FMqpA]X~> +oDdK@L&[$-o)FMCpA]X~> +o)IB+LB!,oo)FM/pA]X~> +o)IBmLB!-\o)FMqpA]X~> +o)IB?LB!-.o)FMCpA]X~> +nc.<+LB!)noDaV0pA]X~> +nc.<mLB!*[oDaVrpA]X~> +nc.<?LB!*-oDaVDpA]X~> +nc.9*\,QFke,P1dp&Bb0p]#a~> +nc.9l\,QGXe,P2Qp&Bbrp]#a~> +nc.9>\,QG*e,P2#p&BbDp]#a~> +nGh0)\c;XleGk:ep&Bb0p]#a~> +nGh0k\c;YYeGk;Rp&Bbrp]#a~> +nGh0=\c;Y+eGk;$p&BbDp]#a~> +n,M*)])V[kf)LFepA]k1p]#a~> +n,M*k])V\Xf)LGRpA]ksp]#a~> +n,M*=])V\*f)LG$pA]kEp]#a~> +n,M'(]`7jlf)LFepA]k1p]#a~> +n,M'j]`7kYf)LGRpA]ksp]#a~> +n,M'<]`7k+f)LG$pA]kEp]#a~> +mf1s'^&Rplf`-Req#?%2p]#a~> +mf1si^&RqYf`-SRq#?%tp]#a~> +mf1s;^&Rq+f`-S$q#?%Fp]#a~> +mJkm'^An!lf`-Req#?%2p]#a~> +mJkmi^An"Yf`-SRq#?%tp]#a~> +mJkm;^An"+f`-S$q#?%Fp]#a~> +m/Pd&_#O-lgAcafq#?%2p]#a~> +m/Pdh_#O.YgAcbSq#?%tp]#a~> +m/Pd:_#O.+gAcb%q#?%Fp]#a~> +m/Pa%_Z09lOoNTfq>]G;Rf@m~> +m/Pag_Z0:YOoNUSq>]H(Rf@m~> +m/Pa9_Z0:+OoNU%q>]GORf@m~> +li5[%_uK<kM>u'gq>]J<RK%d~> +li5[g_uK=XM>u(Tq>]K)RK%d~> +li5[9_uK=*M>u(&q>]JPRK%d~> +lMoR$`;fBki;`g?WrL[6q>]M=R/_[~> +lMoRf`;fCXi;`h,WrL\#q>]N*R/_[~> +lMoR8`;fC*i;`gSWrL[Jq>]MQR/_[~> +lMoO#`rGNkirB!@VZ5@5qZ#Y?QiDR~> +lMoOe`rGOXirB"-VZ5A"qZ#Z,QiDR~> +lMoO7`rGO*irB!TVZ5@IqZ#YSQiDR~> +l2TI#a8bQjjo>6AV>o:5qZ#Y?QiDR~> +l2TIea8bRWjo>7.V>o;"qZ#Z,QiDR~> +l2TI7a8bR)jo>6UV>o:IqZ#YSQiDR~> +kl9@"aoCZikl:NCU]9.5qZ#Y?QiDR~> +kl9@daoC[Vkl:O0U]9/"qZ#Z,QiDR~> +kl9@6aoC[(kl:NWU]9.IqZ#YSQiDR~> +kl9=!b5^]hm/QlEUAs%4qu>eAQN)I~> +kl9=cb5^^Um/Qm2UAs&!qu>f.QN)I~> +kl9=5b5^^'m/QlYUAs%Hqu>eUQN)I~> +kPs7!bQ$]fnGi5GU&Wt4qu>eAQN)I~> +kPs7cbQ$^SnGi64U&Wu!qu>f.QN)I~> +kPs75bQ$^%nGi5[U&WtHqu>eUQN)I~> +k5X-uc2Zcdq#BnJT`<n4qu>eAQN)I~> +k5X.bc2ZdQq#Bo7T`<o!qu>f.QN)I~> +k5X.4c2Zd#q#Bn^T`<nHqu>eUQN)I~> +k5X*tci;*MTE!h4qu>eAQN)I~> +k5X+aci;+:TE!i!qu>f.QN)I~> +k5X+3ci;*aTE!hHqu>eUQN)I~> +jo=$tci;*MTE!e3r;YnBQN)I~> +jo=%aci;+:TE!eur;Yo/QN)I~> +jo=%3ci;*aTE!eGr;YnVQN)I~> +jT!psdJq9NT)[_3r;YkAQiDR~> +jT!q`dJq:;T)[_ur;Yl.QiDR~> +jT!q2dJq9bT)[_Gr;YkUQiDR~> +j8[gre,RHOT)[_3r;YkAQiDR~> +j8[h_e,RI<T)[_ur;Yl.QiDR~> +j8[h1e,RHcT)[_Gr;YkUQiDR~> +j8[greGmNOSc@Y3r;YkAQiDR~> +j8[h_eGmO<Sc@Yur;Yl.QiDR~> +j8[h1eGmNcSc@YGr;YkUQiDR~> +ir@^qec3WPSc@Y3r;Yh@R/_[~> +ir@_^ec3X=Sc@Yur;Yi-R/_[~> +ir@_0ec3WdSc@YGr;YhTR/_[~> +iW%UpfDifQSc@Y3r;Ye?RK%d~> +iW%V]fDig>Sc@Yur;Yf,RK%d~> +iW%V/fDifeSc@YGr;YeSRK%d~> +iW%Upf`/lQSc@V2rVth>S,\!~> +iW%V]f`/m>Sc@VtrVti+S,\!~> +iW%V/f`/leSc@VFrVthRS,\!~> +i;_LogAf&RSH%P2rVtV8U&TW~> +i;_M\gAf'?SH%PtrVtW%U&TW~> +i;_M.gAf&fSH%PFrVtVLU&TW~> +huDCnh#G5SSH%P2rVsi"\,Us~> +huDD[h#G6@SH%PtrVsid\,Us~> +huDD-h#G5gSH%PFrVsi6\,Us~> +huDCnh#G5SSH%P2rVrcYec1.~> +huDD[h#G6@SH%PtrVrdFec1.~> +huDD-h#G5gSH%PFrVrcmec1.~> +hZ):mhZ(DTSH%P2rVr!Cli2J~> +hZ);ZhZ(EASH%PtrVr"0li2J~> +hZ);,hZ(DhSH%PFrVr!Wli2J~> +h>c1li;^SUSH%P2rVqg>nGe"~> +h>c2Yi;^TBSH%PtrVqh+nGe"~> +h>c2+i;^SiSH%PFrVqgRnGe"~> +h#H+liW$YUSH%P2rVqa<o)F4~> +h#H,YiW$ZBSH%PtrVqb)o)F4~> +h#H,+iW$YiSH%PFrVqaPo)F4~> +h#H(kir?bVSH%P2rVq[:o`'F~> +h#H)Xir?cCSH%PtrVq\'o`'F~> +h#H)*ir?bjSH%PFrVq[No`'F~> +g],tjjSuqWSH%P2rVqU8pA]X~> +g],uWjSurDSH%PtrVqV%pA]X~> +g],u)jSuqkSH%PFrVqULpA]X~> +gAfnjjo<"WSH%P2r;VL7p]#a~> +gAfoWjo<#DSH%Ptr;VM$p]#a~> +gAfo)jo<"kSH%PFr;VLKp]#a~> +gAfkikPr1XSH%P2r;VI6q#>j~> +gAflVkPr2ESH%Ptr;VJ#q#>j~> +gAfl(kPr1lSH%PFr;VIJq#>j~> +g&Kbhkl8:YSH%M1rVqO6q>Ys~> +g&KcUkl8;FSH%MsrVqP#q>Ys~> +g&Kc'kl8:mSH%MErVqOJq>Ys~> +f`0\hl2S@YSH%M1rVqL5qYu'~> +f`0]Ul2SAFSH%MsrVqM"qYu'~> +f`0]'l2S@mSH%MErVqLIqYu'~> +f`0Ygli4OZSH%M1rVqI4qu;0~> +f`0ZTli4PGSH%MsrVqJ!qu;0~> +f`0Z&li4OnSH%MErVqIHqu;0~> +fDjPfmJj^[SH%M1rVqI4qu;0~> +fDjQSmJj_HSH%MsrVqJ!qu;0~> +fDjQ%mJj^oSH%MErVqIHqu;0~> +f)OJfmf0d[SH%M1r;V@3r;V9~> +f)OKSmf0eHSH%Msr;V@ur;V9~> +f)OK%mf0doSH%MEr;V@Gr;V9~> +f)OGen,Km\SH%M1r;V=2rVqB~> +f)OHRn,KnISH%Msr;V=trVqB~> +f)OH$n,KmpSH%MEr;V=FrVqB~> +ec4>dnc.6)!<;"@SH%M1r;V=2rVqB~> +ec4?Qnc.6k!<;#-SH%Msr;V=trVqB~> +ec4?#nc.6=!<;"TSH%MEr;V=FrVqB~> +eGn8do)I<)!<;"@SH%M1qu;72rVqB~> +eGn9Qo)I<k!<;#-SH%Msqu;7trVqB~> +eGn9#o)I<=!<;"TSH%MEqu;7FrVqB~> +e,S/co`*N+s8V%@SH%M1qu;41rr7K~> +e,S0Po`*Nms8V&-SH%Msqu;4srr7K~> +e,S0"o`*N?s8V%TSH%MEqu;4Err7K~> +e,S,bp&EZ-rr:q?SH%M1qYu.1rr7K~> +e,S-Op&EZorr:r,SH%MsqYu.srr7K~> +e,S-!p&EZArr:qSSH%MEqYu.Err7K~> +df8&bpA`c.rVth>SH%M1qYu+0s8RT~> +df8'OpA`cprVti+SH%MsqYu+rs8RT~> +df8'!pA`cBrVthRSH%MEqYu+Ds8RT~> +dJqraq#Ar/rVth>SH%M1q>Z%0s8RT~> +dJqsNq#ArqrVti+SH%Msq>Z%rs8RT~> +dJqruq#ArCrVthRSH%MEq>Z%Ds8RT~> +dJqo`qZ#/1r;Y_=SH%M1q>Z%0s8RT~> +dJqpMqZ#/sr;Y`*SH%Msq>Z%rs8RT~> +dJqotqZ#/Er;Y_QSH%MEq>Z%Ds8RT~> +d/Vi`qZ#22qu>V<SH%M1q#>q/!<7Q~> +d/VjMqZ#2tqu>W)SH%Msq#>qq!<7Q~> +d/VitqZ#2Fqu>VPSH%MEq#>qC!<7Q~> +ci;`_r;YD4qZ#M;SH%M1p]#k/!<7Q~> +ci;aLr;YE!qZ#N(SH%Msp]#kq!<7Q~> +ci;`sr;YDHqZ#MOSH%MEp]#kC!<7Q~> +ci;]^rr:S5qZ#M;SH%M1pA]e/!<7Q~> +ci;^Krr:T"qZ#N(SH%MspA]eq!<7Q~> +ci;]rrr:SIqZ#MOSH%MEpA]eC!<7Q~> +cMuW^s8U\6q>]D:SH%M1p&B_/!<7Q~> +cMuXKs8U]#q>]E'SH%Msp&B_q!<7Q~> +cMuWrs8U\Jq>]DNSH%MEp&B_C!<7Q~> +c2Y%3q#B;9SH%M1o)FM/!<7Q~> +c2Y%uq#B<&SH%Mso)FMq!<7Q~> +c2Y%Gq#B;MSH%MEo)FMC!<7Q~> +c2Y(4p]'28SH%P2n,J5-J,~> +c2Y)!p]'3%SH%Ptn,J5oJ,~> +c2Y(Hp]'2LSH%PFn,J5AJ,~> +bl>"4p]'28SH%P2li2r-J,~> +bl>#!p]'3%SH%Ptli2roJ,~> +bl>"Hp]'2LSH%PFli2rAJ,~> +bQ"t5pAa)7SH%P2gAda-J,~> +bQ"u"pAa*$SH%PtgAdaoJ,~> +bQ"tIpAa)KSH%PFgAdaAJ,~> +b5\q6p&Eu6SH%P2^Akc-J,~> +b5\r#p&F!#SH%Pt^AkcoJ,~> +b5\qJp&EuJSH%PF^AkcAJ,~> +b5\t7o`*l5SH%P2UAre-J,~> +b5\u$o`*m"SH%PtUAreoJ,~> +b5\tKo`*lISH%PFUAreAJ,~> +aoAn7o`*l5SH%P2QiH#-J,~> +aoAo$o`*m"SH%PtQiH#oJ,~> +aoAnKo`*lISH%PFQiH#AJ,~> +aT&k8oDdc4SH%P2PlKf-J,~> +aT&l%oDdd!SH%PtPlKfoJ,~> +aT&kLoDdcHSH%PFPlKfAJ,~> +aT&n9o)IZ3SH%P2PQ0`-J,~> +aT&o&o)IZuSH%PtPQ0`oJ,~> +aT&nMo)IZGSH%PFPQ0`AJ,~> +a8`k:nc.Q2Sc@V2P5jZ-J,~> +a8`l'nc.QtSc@VtP5jZoJ,~> +a8`kNnc.QFSc@VFP5jZAJ,~> +`rEe:nc.Q2Sc@V2P5jZ-J,~> +`rEf'nc.QtSc@VtP5jZoJ,~> +`rEeNnc.QFSc@VFP5jZAJ,~> +`rEh;nGhH1Sc@V2OoOT-J,~> +`rEi(nGhHsSc@VtOoOToJ,~> +`rEhOnGhHESc@VFOoOTAJ,~> +`W*e<n,M?0Sc@V2OoOT-J,~> +`W*f)n,M?rSc@VtOoOToJ,~> +`W*ePn,M?DSc@VFOoOTAJ,~> +`;db=mf26/Sc@V2OoOT-J,~> +`;dc*mf26qSc@VtOoOToJ,~> +`;dbQmf26CSc@VFOoOTAJ,~> +`;db=mf26/T)[\2OoOT-J,~> +`;dc*mf26qT)[\tOoOToJ,~> +`;dbQmf26CT)[\FOoOTAJ,~> +_uI_>mJl-.TE!e3OT4K,J,~> +_uI`+mJl-pTE!euOT4KnJ,~> +_uI_RmJl-BTE!eGOT4K@J,~> +_Z.\?m/Q$-TE!e3OT4K,J,~> +_Z.],m/Q$oTE!euOT4KnJ,~> +_Z.\Sm/Q$ATE!eGOT4K@J,~> +_>hV?m/Q$-T`<k3OT4K,J,~> +_>hW,m/Q$oT`<kuOT4KnJ,~> +_>hVSm/Q$AT`<kGOT4K@J,~> +_>hY@li5p,U&Wq3OT4K,J,~> +_>hZ-li5pnU&WquOT4KnJ,~> +_>hYTli5p@U&WqGOT4K@J,~> +_#MVAlMog+UAs"3OT4K,J,~> +_#MW.lMogmUAs"uOT4KnJ,~> +_#MVUlMog?UAs"GOT4K@J,~> +^]2SBl2T^*V#T.3OT4K,J,~> +^]2T/l2T^lV#T.uOT4KnJ,~> +^]2SVl2T^>V#T.GOT4K@J,~> +^]2SBl2T^*V>o43OoOQ,J,~> +^]2T/l2T^lV>o4uOoOQnJ,~> +^]2SVl2T^>V>o4GOoOQ@J,~> +^AlPCkl9U)W;kF3OoOQ,J,~> +^AlQ0kl9UkW;kFuOoOQnJ,~> +^AlPWkl9U=W;kFGOoOQ@J,~> +^&QMDkPsL(XT-a4OoON+J,~> +^&QN1kPsLjXT-b!OoONmJ,~> +^&QMXkPsL<XT-aHOoON?J,~> +^&QPEk5XC'ZiA64PlK`+J,~> +^&QQ2k5XCiZiA7!PlK`mJ,~> +^&QPYk5XC;ZiA6HPlK`?J,~> +]`6JEk5XC'o`'h4jo:H+J,~> +]`6K2k5XCio`'i!jo:HmJ,~> +]`6JYk5XC;o`'hHjo:H?J,~> +]DpGFjo=:&o`'h4kl6Z+J,~> +]DpH3jo=:ho`'i!kl6ZmJ,~> +]DpGZjo=::o`'hHkl6Z?J,~> +])UDGjT"1%o`'k5l2Q]*J,~> +])UE4jT"1go`'l"l2Q]lJ,~> +])UD[jT"19o`'kIl2Q]>J,~> +])UGHj8\($o`'k5li2i*J,~> +])UH5j8\(fo`'l"li2ilJ,~> +])UG\j8\(8o`'kIli2i>J,~> +\c:AHj8\($o`'k5mJhu*J,~> +\c:B5j8\(fo`'l"mJhulJ,~> +\c:A\j8\(8o`'kImJhu>J,~> +\Gt>Iir@t#o`'k5mf/)+!<7Q~> +\Gt?6ir@teo`'l"mf/)m!<7Q~> +\Gt>]ir@t7o`'kImf/)?!<7Q~> +\GtAJiW%k"o`'n6n,J,*!<7Q~> +\GtB7iW%kdo`'o#n,J,l!<7Q~> +\GtA^iW%k6o`'nJn,J,>!<7Q~> +\,Y>Ki;_b!o`'n6nGe2*!<7Q~> +\,Y?8i;_bco`'o#nGe2l!<7Q~> +\,Y>_i;_b5o`'nJnGe2>!<7Q~> +[f>8Ki;_b!o`'n6nc+8*!<7Q~> +[f>98i;_bco`'o#nc+8l!<7Q~> +[f>8_i;_b5o`'nJnc+8>!<7Q~> +[f>;LhuD\!oDah6nc+8*s8RT~> +[f><9huD\coDai#nc+8ls8RT~> +[f>;`huD\5oDahJnc+8>s8RT~> +[K#8MhZ)RuoDah6o)F>*s8RT~> +[K#9:hZ)SboDai#o)F>ls8RT~> +[K#8ahZ)S4oDahJo)F>>s8RT~> +[/]5Nh>cItoDak7nc+5)s8RT~> +[/]6;h>cJaoDal$nc+5ks8RT~> +[/]5bh>cJ3oDakKnc+5=s8RT~> +[/]5Nh>cLuo)Fb6o)F;)s8RT~> +[/]6;h>cMbo)Fc#o)F;ks8RT~> +[/]5bh>cM4o)FbJo)F;=s8RT~> +ZiB2Oh#HCto)Fe7o)F;)rr7K~> +ZiB3<h#HDao)Ff$o)F;krr7K~> +ZiB2ch#HD3o)FeKo)F;=rr7K~> +ZN'/Pg]-:so)Fe7o)F;)rr7K~> +ZN'0=g]-;`o)Ff$o)F;krr7K~> +ZN'/dg]-;2o)FeKo)F;=rr7K~> +Z2a,QgAg4snc+_7o)F;)rVqB~> +Z2a->gAg5`nc+`$o)F;krVqB~> +Z2a,egAg52nc+_Ko)F;=rVqB~> +Z2a,QgAg7tnGeV6o)F;)rVqB~> +Z2a->gAg8anGeW#o)F;krVqB~> +Z2a,egAg83nGeVJo)F;=rVqB~> +YlF)Rg&L.snGeY7o)F;)r;V9~> +YlF*?g&L/`nGeZ$o)F;kr;V9~> +YlF)fg&L/2nGeYKo)F;=r;V9~> +YQ+&Sf`1(sn,JS7nc+2(r;V9~> +YQ+'@f`1)`n,JT$nc+2jr;V9~> +YQ+&gf`1)2n,JSKnc+2<r;V9~> +YQ+)TfDk"smf/J6nc+5)qu;0~> +YQ+*AfDk#`mf/K#nc+5kqu;0~> +YQ+)hfDk#2mf/JJnc+5=qu;0~> +Y5e#TfDk"smf/M7nc+5)qYu'~> +Y5e$AfDk#`mf/N$nc+5kqYu'~> +Y5e#hfDk#2mf/MKnc+5=qYu'~> +XoIuUf)OqsmJiG7nGe,(qYu'~> +XoJ!Bf)Or`mJiH$nGe,jqYu'~> +XoIuif)Or2mJiGKnGe,<qYu'~> +XoJ#Vec4ksm/NA7n,J&(q>Ys~> +XoJ$Cec4l`m/NB$n,J&jq>Ys~> +XoJ#jec4l2m/NAKn,J&<q>Ys~> +XT.uWeGnesli3;7mf.u(q#>j~> +XT/!DeGnf`li3<$mf.ujq#>j~> +XT.ukeGnf2li3;Kmf.u<q#>j~> +X8hoWeGnhtlMm88m/Mf'p]#a~> +X8hpDeGnialMm9%m/Mfip]#a~> +X8hokeGni3lMm8Lm/Mf;p]#a~> +X8hrXe,Seukl7)7li2c(p&BO~> +X8hsEe,Sfbkl7*$li2cjp&BO~> +X8hrle,Sf4kl7)Kli2c<p&BO~> +WrMoYdf8_ukPq&8l2QT'o`'F~> +WrMpFdf8`bkPq'%l2QTio`'F~> +WrMomdf8`4kPq&Ll2QT;o`'F~> +WW2iYdf8f"jo:o8kPpE&oDa=~> +WW2jFdf8fdjo:p%kPpEhoDa=~> +WW2imdf8f6jo:oLkPpE:oDa=~> +W;lfZdJr`"jSto:jSt3&nGe"~> +W;lgGdJr`djStp'jSt3hnGe"~> +W;lfndJr`6jStoNjSt3:nGe"~> +W;li[d/W`$iW#`;i;\m%mJh\~> +W;ljHd/W`fiW#a(i;\mgmJh\~> +W;liod/W`8iW#`Oi;\m9mJh\~> +VuH`[ci<`&hZ']@f`/3>bl<1~> +VuHaHci<`hhZ'^-f`/4+bl<1~> +VuH`oci<`:hZ']Tf`/3Rbl<1~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdji]VkmT)X<~> +JcEdjidZOZT)X<~> +JcEdji_b:,T)X<~> +JcEdjj#qtnSc=3~> +JcEdjj*uX[Sc=3~> +JcEdjj&(C-Sc=3~> +JcEdjj#qtnSc=3~> +JcEdjj*uX[Sc=3~> +JcEdjj&(C-Sc=3~> +JcEdjj#qtnSc=3~> +JcEdjj*uX[Sc=3~> +JcEdjj&(C-Sc=3~> +JcEdjj?8(oSH"*~> +JcEdjjF;a\SH"*~> +JcEdjjACL.SH"*~> +JcEdjj?8(oSH"*~> +JcEdjjF;a\SH"*~> +JcEdjjACL.SH"*~> +JcEdjjZS1pS,\!~> +JcEdjjaVj]S,\!~> +JcEdjj\^U/S,\!~> +JcEdjjun:qRf@m~> +JcEdjk'qs^Rf@m~> +JcEdjk#$^0Rf@m~> +JcEdjjun:qRf@m~> +JcEdjk'qs^Rf@m~> +JcEdjk#$^0Rf@m~> +JcEdjk<4CrRK%d~> +JcEdjkC8'_RK%d~> +JcEdjk>?g1RK%d~> +JcEdjkWOLsR/_[~> +JcEdjk^S0`R/_[~> +JcEdjkYZp2R/_[~> +JcEdjkrjUtQiDR~> +JcEdjl$n9aQiDR~> +JcEdjku!$3QiDR~> +JcEdjl90^uQN)I~> +JcEdjl@4BbQN)I~> +JcEdjl;<-4QN)I~> +JcEdjlTKh!Q2c@~> +JcEdjl[OKcQ2c@~> +JcEdjlVW65Q2c@~> +JcEdjlofq"PlH7~> +JcEdjm!jTdPlH7~> +JcEdjlqr?6PlH7~> +JcEdjmQH.$P5g%~> +JcEdjmXKffP5g%~> +JcEdjmSSQ8P5g%~> +JcEdjmlc7%OoKq~> +JcEdjmsfogOoKq~> +JcEdjmnnZ9OoKq~> +JcEdjnNDI'O8j_~> +JcEdjnUH,iO8j_~> +JcEdjnPOl;O8j_~> +JcEdjo0%[)NW4M~> +JcEdjo7)>kNW4M~> +JcEdjo21)=NW4M~> +JcEdjp-"!,MZ82~> +JcEdjp4%YnMZ82~> +JcEdjp/-D@MZ82~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +JcC<$JcE^hJ,~> +%%EndData +showpage +%%Trailer +end +%%EOF diff --git a/illustrations/vipslogo_512.png b/illustrations/vipslogo_512.png new file mode 100644 index 0000000000000000000000000000000000000000..f4e416ef1adf3fa60969a5cfb1fd6387ee867ae3 Binary files /dev/null and b/illustrations/vipslogo_512.png differ diff --git a/input_data.md b/input_data.md new file mode 100644 index 0000000000000000000000000000000000000000..1f9a61d8712a34be947a5e5f426076240cec47a1 --- /dev/null +++ b/input_data.md @@ -0,0 +1,3 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + +# VIPS Model input data \ No newline at end of file diff --git a/model_development_java.md b/model_development_java.md new file mode 100644 index 0000000000000000000000000000000000000000..b21795748e0e55833ef9780d6dddf520f7a88085 --- /dev/null +++ b/model_development_java.md @@ -0,0 +1,698 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + +# Developing a model using Java +This page builds on [A step-by-step introduction to implementing prediction models in VIPS](model_implementation.md) + +What you need to develop a VIPS model is basically: + +- A decent coding environment (IDE) like NetBeans, Eclipse or + IntelliJ. +- A library of VIPS classes called [VIPSCommon](https://gitlab.nibio.no/VIPS/VIPSCommon) +- A testing framework like Junit. This is normally bundled with your + IDE (see above) + +The normal workflow is that you have some [correctly formatted weather data](weather_data.md) in a file that you put on the project's +classpath, you mix this with the other configuration data and develop +the model based on these input data. You must have one main class that +implements the Model interface, which is available in the VIPSCommon.jar +library. The test framework can be used to test single methods that are +part of the algorithms or you can test the complete model. + +When you're happy with how the model works you can test deploy it to the +VIPSCore server (TODO: Document this) + +## Implementing a forecasting model + + +In this project, we are going to implement a forecasting model for a +virtual fungus called «Fungus pilosus flavis» (please bear with me, any +phytopatologists who might read this). Let\'s say that it there is a +forecasting model for it that states that + +- There is no infection risk until you have reached 500 day degrees + (celcius) +- After that, the risk multiplies by 2 for each consecutive hour of + leaf wetness (starting at 1 on the first hour). When reaching the + threshold of 24, there is serious risk of infection, and measures + should be taken. + +We'll be using NetBeans IDE for this example, but the process should be +transferable to other IDEs. NetBeans can be downloaded from here: +<https://netbeans.org/downloads/> Select either the Java EE version or +the one with everything. Follow the install instructions and start +Netbeans. + +### Creating a new NetBeans project for the forecasting model + +1. Start the NetBeans application and remove the `Start Page`. Select + `File -> New Project` and select project type `Java Application`. + Put it somewhere that you'll remember. +2. NetBeans sets up the project structure for you, but you should + create a package. Right click `Source Packages` and select `New -> + Java Package`. Name it whatever you want, e.g. + `com.acme.vips.funguspilosusflavis` +3. Create the main model class: Right click on the package and select + `New -\> Java Class`. Name it e.g. `FungusPilosusFlavisModel` +4. The next thing you should do is to add the basic dependencency for + the model: The library called `VIPSCommon`. Right click on `Libraries` + and select `Add JAR/Folder`. Locate the file (it should be included + with this documentation) and add it. + +Now you should be ready to code. + +### Working on the model class + + +1. To make sure that the file is compliant with the VIPS Model + specifications, change this + ```java + public class FungusPilosusFlavisModel { + ``` + to this + ```java + public class FungusPilosusFlavisModel implements Model{ + ``` +2. NetBeans complains. Click on the light bulb that pops up in the + gutter, and select `Add import for no.bioforsk.vips.model.Model` +3. NetBeans complains again. Click on the light bulb and select + `Implement all abstract methods` +4. NetBeans creates all the methods that are part of the Model + **interface**. Please note that these methods at the moment do not + do anything, except cause an error (throw an + UnsupportedOperationException) if they are called. The + implementation of the methods is entirely up to you. + +Now the class is ready to be programmed and do some calculations. + +[]{#anchor-8}Create the method for finding when 500 day degrees has been passed +------------------------------------------------------------------------------- + +To find out when 500 day degrees (since some date) have passed, you need +the mean temperature of each day. All weather observations in VIPS are +represented by an instance of the object WeatherObservation. This object +has a few important properties: + +- ElementMeasurementTypeId: Rain, mean temperature, leaf wetness etc. +- TimeMeasured +- LogInterval: Hourly, Daily, Monthly measurement +- Value: the numerical value of the weather observation + +We need a list of one WeatherObservation with mean temperature per day. +So we could start by writing this method: + +[]{#anchor-9}[]{#anchor-10}public Date +getDateWhenDayDegreeLimitHasPassed(List\<WeatherObservation\> +observations) + +{ + +} + +NetBeans complains, because it can\'t find the definition of +WeatherObservation. Click on the light bulb and select «Add import for +no.bioforsk.vips.entity.WeatherObservation», and then «Add import for +java.util.Date»). NetBeans still complains, but that\'s because the +method does not return anything yet. So a simple approach could be: + +1. Loop through all the WeatherObservation objects, and add the value + to the total day degree sum as we do so +2. When the threshold of 500 has been reached, return the date of that + WeatherObservation object. + +Sample code for this could be: + +*public Date +getDateWhenDayDegreeLimitHasPassed(List\<WeatherObservation\> +observations){* + +// Make sure the observations are in chronological order + +Collections.sort(observations); + +// Initalize the day degree counter + +Double dayDegrees = 0.0; + +// Iterate through the list of observations + +for(WeatherObservation obs:observations) + +{ + +// Make sure it\'s only daily temperature observations that are used + +if(obs.getLogIntervalId() + +.equals(WeatherObservation.LOG\_INTERVAL\_ID\_1D) + +&& obs.getElementMeasurementTypeId() + +.equals(WeatherElements.TEMPERATURE\_MEAN)) + +{ + +// Add to dayDegree sum + +dayDegrees += obs.getValue(); + +// If threshold is reached, return the date of the current temperature + +// measurement + +if(dayDegrees \>= 500.0) + +{ + +return obs.getTimeMeasured(); + +} + +} + +} + +// We have finished looping through the observations, and dayDegrees has + +// not passed 500. So we can\'t return a Date, we must return NULL +(nothing) + +return null; + +} + +[]{#anchor-11}Creating the method to calculate the infection risk +----------------------------------------------------------------- + +We can operate on hourly weather data for leaf wetness and calculate the +infection risk. Data in will be a list of weather observations (leaf +wetness, hourly). Output data will be a dictionary with timestamp as +key, and the infection risk as value. So for instance for 24th July 2014 +14:00 UTC there will be only one value. + +An example of a solution can be: + +public Map\<Date, Integer\> getInfectionRisk(List\<WeatherObservation\> +observations) + +{ + +// Create the map with dates and infection risk values + +Map\<Date, Integer\> riskMap = new HashMap\<\>(); + +// Make sure the observations are in chronological order + +Collections.sort(observations); + +// Counter for consecutive hours of leaf wetness + +Integer consecutiveHoursOfLeafWetness = 0; + +// Loop through the list of observations + +for(WeatherObservation obs:observations) + +{ + +// We define a lower threshold for leaf wetnes to be 10mins/hour + +if(obs.getValue() \> 10.0) + +{ + +// Leaf wetness registered, add to consecutive hours counter + +consecutiveHoursOfLeafWetness++; + +} + +else + +{ + +// No leaf wetness, reset counter + +consecutiveHoursOfLeafWetness = 0; + +} + +// We set the risk value + +riskMap.put(obs.getTimeMeasured(), consecutiveHoursOfLeafWetness \* 2); + +} + +// Return the map with all values + +return riskMap; + +} + +[]{#anchor-12}How can we be sure that these methods work? Testing to the rescue +------------------------------------------------------------------------------- + +In order to ensure that the methods work, we should test them. Ok, maybe +these methods are so simple that they do not need testing. But in most +models you will have complex calculations, and for that you need testing +to ensure correctness. + +For Java, the most common method is to use a testing framework called +Junit (which is part of a larger family of testing frameworks for +different programming languages called «xUnit», +see:<http://en.wikipedia.org/wiki/XUnit>). To set up a test for the +model class, right click on it (In the «Projects» tab and select «Tools +-\> Create/Update tests». Click OK in the dialog box. A test class is +created for you, and all public methods in the model class have gotten +their corresponding test methods. When you run the test, which you can +do by right clicking the model class and select «Test File» or just hit +\[CTRL-F6\], you will see that all tests fail. This is by design. It's +now up to you to select which test methods to keep. + +A weather data file is included in the documentation. Copy this into the +«test» folder + +### []{#anchor-13}How testing works in JUnit + +Let\'s begin with writing a very simple test method: + +\@Test + +public void HelloTest() + +{ + +String expected = \"Hello Test!\"; + +String result = \"HellOOO Test!\"; + +Assert.assertEquals(expected,result); + +} + +The method has \@Test before the declaration. This is a so called +annotation, which helps JUnit find which methods in the class are +actually meant to be methods to run during a test. The test consists of +two string variables (expected and result). These variables are asserted +(or expected) to be equal, and this is tested using the +Assert.assertEquals method in JUnit. Of course the two strings are not +equal, so the test will fail. You can try that by simply entering the +key combination \[ALT\] + \[F6\], which runs all tests for the current +project. You should see this in the lower part of NetBeans: + + + +Now you can try to make the test pass. You can do that by setting the +strings to have the same value. Run the test again (\[ALT\] + \[F6\]), +then you should see that the test passes: + + + +So now we can add a test for one of the methods that we created in the +FungusPilosusFlavisModel class. Let\'s start with testing if +getDateWhenDayDegreeLimitHasPassed can find the correct date for when we +have passed 500 day degrees. We use the data from the file +«JSONWeatherdata.json» (you\'ll find it under «Other Test Sources»). +These data are quite easy to import into a spreadsheet. By doing that, +you can add the temperatures, and find that at July 8th 2012, the day +degrees have reached a value of 509.5. So we can test this in the +following way. + +```java +@Test +public void []{#anchor-14}canFindDateWhenDayDegreeLimitPassed() +{ + FungusPilosusFlavisModel instance = new FungusPilosusFlavisModel(); + + // We create the expected date + Calendar cal = + Calendar.getInstance(TimeZone.getTimeZone("Europe/Oslo")); + cal.set(2012, Calendar.JULY, 8, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 0); + Date expected = cal.getTime(); + + ModelConfiguration config = new WeatherDataFileReader().getModelConfigurationWithWeatherData("/JSONWeatherData.json", "FUNGUSPILO"); + + List<WeatherObservation> observations = (List<WeatherObservation>)config.getConfigParameter("observations"); + + Date result = instance.getDateWhenDayDegreeLimitHasPassed(observations); + + assertEquals(expected,result); + +} +``` + +If you run the tests now, you\'ll (hopefully) see that both tests +passed. Both tests? Yes, you have two tests: + +- helloTest +- canFindDateWhenDayDegreeLimitPassed + +Each time you change the program and compile it, these tests are run. +This means that if you change something in the program (either +intentionally or unintentionally) that makes these tests fail, **you +will be informed**. This might not seem so relevant for such a simple +program, but believe me, it will save your day when the code gets big +and ugly. Also, writing tests helps you think of how the program works. +If you write the tests first, you will think more clearly about the +problem, in my experience. + +**Exercise: Write a test for getInfectionRisk()** + +### Putting it together + + +We now have the most important methods created (and successfully +tested). What we need to do now is to get data in (set configuration, +get weather data etc) and get the results out in the expected format. + +#### Data in + +Input data are sendt in a large lump called a ModelConfiguration. It\'s +a key based store of many different kind of objects, in principle almost +any Java object: Numbers, strings, dates, WeatherObservations. This +configuration object is sent to the model through the method +setConfiguration. So to get the weather data, we need to extract them +from the configuration object in that method. An example of how to do +this is as follows: + +First, at the top of the class, declare the object that holds the +weather data: + +```java + List<WeatherObservation> observations; +``` +This list will stay empty (NULL) until setConfiguration does something +about it. So let\'s do that, e.g.: + +```java + @Override + public void setConfiguration(ModelConfiguration config) throws ConfigValidationException { + + // We use an object mapper, because we don\'t know if the objects inside + // the ModelConfiguration are simply instances of HashMaps or instances + // of proper classes. Usually (when sent over the wire using REST), they + // are the former. + + ObjectMapper mapper = new ObjectMapper(); + + // Get the observation list + this.observations = mapper.convertValue(config.getConfigParameter("observations"), + new TypeReference\<List\<WeatherObservation\>\>(){}); + + // After this we should do validation of the data, but that's for another lesson + + } +``` +So now we have the weather data in a list, and we can start using them + +#### Data out + +Data out are sent as a list of Result objects. The method to get the +data is called `getResult()`, surprisingly. An example of this method +could be: + +```java + @Override + public List<Result> getResult() throws ModelExcecutionException { + + // Initialize the list of results + List<Result> results = new ArrayList<>(); + + // Ensure that the observations are in chronoligical order + Collections.sort(this.observations); + + // Which date did day degree sum exceed 500? + Date dayDegreeLimitReachedDate = this.getDateWhenDayDegreeLimitHasPassed(this.observations); + + // Get infection risk for the whole period + Map<Date, Integer\> uncontrolledInfectionRisk = this.getInfectionRisk(this.observations); + + // Get all dates from the map of infection risk + List<Date> dateList = new ArrayList(uncontrolledInfectionRisk.keySet()); + Collections.sort(dateList); + + // Loop through dates + for(Date currentDate:dateList) + { + // Create a new result object + Result result = new ResultImpl(); + // Set the timestamp on it + result.setResultValidTime(currentDate); + // If we're after the date of day degree sum \> 500, use the infectionrisk + if(currentDate.compareTo(dayDegreeLimitReachedDate) >= 0) + { + // Set infection risk + result.setValue(this.getModelId().toString(), FungusPilosusFlavisModel.CONTROLLED_INFECTION_RISK, uncontrolledInfectionRisk.get(currentDate).toString()); + } + else + { + // Set infection risk to 0 + result.setValue(this.getModelId().toString(), FungusPilosusFlavisModel.CONTROLLED_INFECTION_RISK, "0"); + } + + // Set the warning status + // If controlled infection risk \< 64, status is NO RISK + // Otherwise it's HIGH RISK + result.setWarningStatus(uncontrolledInfectionRisk.get(currentDate) < 64 ? Result.WARNING_STATUS_NO_RISK :Result.WARNING_STATUS_HIGH_RISK); + + // Add result to list + results.add(result); + } + // We're done! + return results; + } +``` + +Netbeans will complain, because you haven\'t defined the global constant +CONTROLLED_INFECTION_RISK. You can define it at the top of the class: + +```java +public final static String CONTROLLED_INFECTION_RISK = "CONTROLLED_INFECTION_RISK"; +``` + +How do we know that `getResult()` works? Let's test it! We can start with +writing this test method: + +```java +@Test +public void modelWorksAsExpected() +{ + try { + FungusPilosusFlavisModel instance = new FungusPilosusFlavisModel(); + + // Get weather observations from file + ModelConfiguration config = this.getConfiguration("/JSONWeatherData.json"); + instance.setConfiguration(config); + List<Result> results = instance.getResult(); + + } catch (ConfigValidationException | ModelExcecutionException ex) { + fail(ex.getMessage()); + } +} +``` + +This test does not (yet) test the results, it only tests that the method +doesn\'t fail when run. If you try to run the tests, you\'ll see that it +actually fails. This is because the method `getModelId()` has not been +implemented correctly. Fix that by adding the modelId to the class (at +the top): + +```java +public final static ModelId modelId = new ModelId("FUNGUSPILO"); +``` + +The string `"FUNGUSPILO"` is now the unique ID for this model. The ID must +be a string of 10 characters. It could be anything (as long as it\'s 10 +characters long), but it\'s recommended that it is some sort of +abbreviation that gives a human reader a clue about which model it is. + +Running the test again should make all tests pass. + +One behavior that could be tested, is that the infection risk is always +0 and the warning status == LOW when the result is from before the day +when day degree sum exceeds 500. We put that into our test: + +```java +@Test + +public void modelWorksAsExpected() +{ + + try { + + FungusPilosusFlavisModel instance = new FungusPilosusFlavisModel(); + + // Get weather observations from file + + ModelConfiguration config = + this.getConfiguration("/JSONWeatherData.json"); + + instance.setConfiguration(config); + + List<WeatherObservation> observations = + (List<WeatherObservation>)config.getConfigParameter("observations"); + + Date dayDegreeLimit = + instance.getDateWhenDayDegreeLimitHasPassed(observations); + + // Check that all results before date for day degree limit are zero + + List<Result> results = instance.getResult(); + + for(Result result:results) + { + if(result.getResultValidTime().compareTo(dayDegreeLimit) \<= 0) + { + + Assert.assertEquals( + "0", + + result.getValue(FungusPilosusFlavisModel.modelId.toString(), + FungusPilosusFlavisModel.CONTROLLED\_INFECTION\_RISK) + + ); + + Assert.assertEquals(Result.WARNING\_STATUS\_NO\_RISK, + result.getWarningStatus()); + + } + + } + + } catch (ConfigValidationException \| ModelExcecutionException ex) { + + fail(ex.getMessage()); + + } + +} +``` + +**Exercise**: Write a test (or add to the one above) that checks that the +infection risk is calculated correctly **after** the day when day degree +sum exceeds 500. + +#### Implementing the meta information methods + + +So, now you have a forecasting model that produces the expected results. +When this model is deployed to the VIPS core runtime, it is discovered +automatically and added to the list of available models. In order for +other systems (like VIPSLogic or another client) to be able to query and +show information about the model, it needs to implement the methods that +provide documentation: + +- getModelName() - the name of the model. For instance «Fungus pilosus + flavis model» +- getLicense() - Open Source? Proprietary? Your pick +- getCopyright() - For instance «(c) 2014 Bioforsk» +- getModelDescription() - Detailed description of how the model works, + from a biological perspective +- getModelUsage() - How to configure the model (what parameters are + needed, what values may they have and so on) +- getSampleConfig() - A sample JSON configuration file. + +Most of these methods have two versions: One takes language into +account, one doesn\'t. Translation in model documentation is part of a +presently unwritten chapter. For now, you can do this if you want as a +general pattern: + +```java + @Override + public String getModelName() { + return this.getModelName(Model.DEFAULT_LANGUAGE); + } + + @Override + public String getModelName(String language) { + return "Fungus pilosus flavis model"; + } +``` + +This way you only need to change the implementation one place, in the +language dependent method. + +## Learning from an existing model's code + +So far we have done the very basics of the most important parts of the +model implementation. There\'s a lot more to it than this. For further +study, we recommend that you take a look at the version of [NIBIO's +forecasting model for apple scab](https://gitlab.nibio.no/VIPS/Model_APPLESCABM). + +## Even more functionality +### Translation utilities + +You can extend the no.nibio.vips.i18n.I18nImpl class for easy +translation of text that the class provides. In that case, provide a set +of properties files (which makes up a resource bundle) that you can +specify in the model constructor, e.g. like this: + +```java +public AppleScabModel() +{ + // Setting the file name of the resource bundle + super("no.nibio.vips.model.applescabmodel.texts"); + this.weatherUtil = new WeatherUtil(); + this.modelUtil = new ModelUtil(); +} +``` + +NetBeans has good support for this, read more about resource bundles +here +<https://docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html> + +To get a translation from the resource bundles, follow this example: + +```java +@Override +public String getModelName() { + return this.getModelName(Model.DEFAULT_LANGUAGE); + +} + +@Override +public String getModelName(String language) { + return this.getText("name", language); +} +``` +### Including images in the description + +You can include images in the description text for the model. The images +are embedded to the text as \<img/\> tags with the image base64 encoded +inside the src-attribute. For instance: + +```html +<img +src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAc)[...]" alt="Illustration 1A" title="Illustration 1A"/> +``` + +The \<img/\> tags are created from template tags that you insert into +the document. These tags looks like this: + +``` +{{filename=\"/path/to/filename.jpg\" description=\"Sample description\" float=\"\[CSSFloat property\]\"}} +``` + +The path is relative to the path in your jarfile. + +To enable this functionality, use the ModelUtil class from VIPSCommon. +For instance like this: + +```java +@Override +public String getModelDescription(String language) { + try + { + return + this.modelUtil.getTextWithBase64EncodedImages(this.getText("description", + language), this.getClass()); + } + catch(IOException ex) + { + return this.getText("description", language); + } +} + +``` + + diff --git a/model_development_python.md b/model_development_python.md new file mode 100644 index 0000000000000000000000000000000000000000..bc0abf67269c07f5207901e183fc02cd81926ff0 --- /dev/null +++ b/model_development_python.md @@ -0,0 +1,4 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + +# Developing a model using Python +This page builds on [A step-by-step introduction to implementing prediction models in VIPS](model_implementation.md) \ No newline at end of file diff --git a/model_implementation.md b/model_implementation.md new file mode 100644 index 0000000000000000000000000000000000000000..4666376c6b1c0ef9fcbaf44435a62efcc34ea190 --- /dev/null +++ b/model_implementation.md @@ -0,0 +1,79 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + +# A step-by-step introduction to implementing prediction models in VIPS + +Tor-Einar Skog, Senior developer, NIBIO + +Updated: 2023-02-21 + +## What you will learn +This document describes how to implement and test a forecasting model +that can be used on the VIPS platform. + +## Prerequisites + +- You should be familiar with how the VIPS system works. It is + recommended that you read [this](README.md) +- You should have a basic understanding either of + - the Java programming language. + - the Python programming language +- You should be somewhat familiar with using an IDE (Integrated Development Enviroment), such as (depending on your choice of programming language): + - NetBeans (Java) + - IntelliJ (Java) + - Eclipse (Java/Python ++) + - PyCharm (Python) + - MS Visual Studio Code (Python/Java ++) +- You need the library for your programming language, either + - For Java: [VIPSCommon](https://gitlab.nibio.no/VIPS/VIPSCommon) + - For Python: [VIPSCore-Python-Common](https://gitlab.nibio.no/VIPS/vipscore-python-common) +- It is also recommended to have an existing VIPS Model implementation as a reference, +for instance + - For Java: [Carrot rust fly temperature model](https://gitlab.nibio.no/VIPS/Model_PSILARTEMP) + - For Python: [Reference model](https://gitlab.nibio.no/VIPS/models/python/referencemodel) + +## Model design in VIPS + +### The structure of a model + + + + +A model is conceptually illustrated above. You have a +set of input data in some form, you have the analysis/algorithms +happening inside the model, and the model returns a set of results. In +VIPS, certain design requirements must be met: + +- The model must be programmed either in + - **Java** or another language that can + run on the Java Virtual Machine. This includes JavaScript and R. + - **Python v3** +- The model must fulfill the Model design contranct + - **Java**: the Model interface, which is in the [VIPSCommon](https://gitlab.nibio.no/VIPS/VIPSCommon) JAR + - **Python**: The VIPSModel Abstract Base Class, which is in the [VIPSCore-Python-Common](https://gitlab.nibio.no/VIPS/vipscore-python-common) package +- The model must be packaged as an independent component + - **Java**: JAR (Java Archive) file + - **Python**: PIP package +- The **input data** must be in a [specific format](input_data.md) +- **Results** must be returned in a [specific format](result_data.md) +- The model must provide its own description and usage information in + at least English and aditionally in any preferred language + +When a model meets these requirements, it can be deployed to the +- **Java** [VIPS Core runtime server](https://gitlab.nibio.no/VIPS/VIPSCore) +- **Python** [VIPSCore-Python runtime server](https://gitlab.nibio.no/VIPS/VIPSCore-Python) + +...and be made available without any more +configuration. The model can be called over HTTP/REST from any +authenticated client on the Internet. + +### Developing a model +[Developing a model using Java](model_development_java.md) + +[Developing a model using Python](model_development_python.md) + +### Developing a model using R +Renjin is a JVM implementation of R, which makes it possible to run R +scripts on the JVM and pass data between Java and R. Read more here: +<http://www.renjin.org/> + +We have an actual implementation of a model in R, the [Leaf blotch model for wheat](https://gitlab.nibio.no/VIPS/model_leafblotch) \ No newline at end of file diff --git a/result_data.md b/result_data.md new file mode 100644 index 0000000000000000000000000000000000000000..adcfd258ccbf04fb8830a1a000c2424f731ba060 --- /dev/null +++ b/result_data.md @@ -0,0 +1,3 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + +# Specification of VIPS model result data diff --git a/weather_data.md b/weather_data.md index 9b1934d7e1a172320b6850dde41be38192ecd657..57f69e89277a87bc2852582e87aa42f1e896939b 100644 --- a/weather_data.md +++ b/weather_data.md @@ -1,3 +1,5 @@ +<img src="illustrations/vipslogo_512.png" alt="VIPS Logo" height="250"/> + # Specification of external weather data source for VIPS ## Preface