diff --git a/changelog.md b/changelog.md index f7447159840584b0f21b0c0455b4ddf7935ea2b0..cf6edb6d808d20c44f857f73e3de264f8a211466 100644 --- a/changelog.md +++ b/changelog.md @@ -4,8 +4,9 @@ Changes in tpcclib This is a concise list of changes; commit messages can be found in the [Git repository](https://gitlab.utu.fi/vesoik/tpcclib.git). -## version 0.8.1 (2023-06-30, in development) +## version 0.8.1 (2023-08-18, in development) +- Added application flat2nii. - Updates in test scripts. ## version 0.8.0 (2023-06-29) diff --git a/v2/simimg/CMakeLists.txt b/v2/simimg/CMakeLists.txt index 935588dcd29522a1fcf16ef11f83670b36f9a6a4..d460593a1a9d86e0220487bd9e63f659c55a9199 100644 --- a/v2/simimg/CMakeLists.txt +++ b/v2/simimg/CMakeLists.txt @@ -43,6 +43,7 @@ link_directories (${CMAKE_BINARY_DIR}/v2/libtpcimage) # Add executable that is built from the source file(s) # The extensions are automatically found. +add_executable (flat2nii flat2nii.c) add_executable (hist2svg hist2svg.c) add_executable (simboxes simboxes.c) add_executable (simcirc simcirc.c) @@ -50,6 +51,7 @@ add_executable (simelli simelli.c) add_executable (tac2nii tac2nii.c) # Link the executable to the libraries. +target_link_libraries (flat2nii tpcimage tpcdcm tpcecat tpcextensions m) target_link_libraries (hist2svg tpctacmod tpcift tpctac tpcextensions m) target_link_libraries (simboxes tpcift tpccsv tpcextensions m) target_link_libraries (simcirc tpcift tpccsv tpcextensions m) @@ -59,6 +61,7 @@ target_link_libraries (tac2nii tpcimage tpcdcm tpcecat tpctac tpcift tpccsv tpce # Install the executable(s) install ( PROGRAMS + ${CMAKE_CURRENT_BINARY_DIR}/flat2nii${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_CURRENT_BINARY_DIR}/hist2svg${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_CURRENT_BINARY_DIR}/simboxes${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_CURRENT_BINARY_DIR}/simcirc${CMAKE_EXECUTABLE_SUFFIX} @@ -79,19 +82,25 @@ set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BIN # # Simple tests # +add_test(flat2niiUsage flat2nii "--help") add_test(hist2svgUsage hist2svg "--help") add_test(simboxesUsage simboxes "--help") add_test(simcircUsage simcirc "--help") add_test(simelliUsage simelli "--help") add_test(tac2niiUsage tac2nii "--help") set_tests_properties( - hist2svgUsage simboxesUsage simcircUsage simelliUsage tac2niiUsage + flat2niiUsage hist2svgUsage simboxesUsage simcircUsage simelliUsage tac2niiUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: *" ) # # Run test scripts # +add_test ( + NAME flat2niiTests + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test/flat2nii + COMMAND bash "./test_flat2nii.sh" +) add_test ( NAME hist2svgTests WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test/hist2svg diff --git a/v2/simimg/flat2nii.c b/v2/simimg/flat2nii.c new file mode 100644 index 0000000000000000000000000000000000000000..b230b9baeaba0262dd5f49febde4ba3d18da20b9 --- /dev/null +++ b/v2/simimg/flat2nii.c @@ -0,0 +1,318 @@ +/** @file flat2nii.c + * @brief Create NIfTI format PET image with contents from binary flat file. + * @copyright (c) Turku PET Centre + * @author Vesa Oikonen + */ +/// @cond +/*****************************************************************************/ +#include "tpcclibConfig.h" +/*****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <math.h> +#include <string.h> +#include <time.h> +/*****************************************************************************/ +#include "tpcextensions.h" +//#include "tpcift.h" +//#include "tpccsv.h" +//#include "tpctac.h" +#include "tpcdcm.h" +#include "tpcecat.h" +#include "tpcnifti.h" +#include "tpcimage.h" +/*****************************************************************************/ + +/*****************************************************************************/ +static char *info[] = { + "Create a 4D PET image file in NIfTI 1S format, with contents from", + "the 32-bit float values in flat binary file.", + "Flat file must contain the pixel values in order t>z>y>x.", + " ", + "Usage: @P [Options] flatfile xdim ydim zdim tdim imagefile", + " ", + "Options:", + " -pxlsize=<x,y,z>", + " Pixel sizes in x, y, and z dimensions in mm; by default 1 mm each.", + " -stdoptions", // List standard options like --help, -v, etc + " ", + "Examples:", + " @P -pxlsize=2.5,2.5,3.0 data.bin 128 128 63 25 output", + " ", + "See also: img2flat, tac2nii, nii_ehdr, img2tif, simboxes, simcirc, pxl2mask", + " ", + "Keywords: image, NIfTI, simulation, software testing", + 0}; +/*****************************************************************************/ + +/*****************************************************************************/ +/* Turn on the globbing of the command line, since it is disabled by default in + mingw-w64 (_dowildcard=0); in MinGW32 define _CRT_glob instead, if necessary; + In Unix&Linux wildcard command line processing is enabled by default. */ +/* +#undef _CRT_glob +#define _CRT_glob -1 +*/ +int _dowildcard = -1; +/*****************************************************************************/ + +/*****************************************************************************/ +/** + * Main + */ +/*****************************************************************************/ +int main(int argc, char **argv) +{ + int ai, help=0, version=0, verbose=1; + char flatfile[FILENAME_MAX], dbname[FILENAME_MAX]; + int dimx=0, dimy=0, dimz=0, dimt=0; + double pxlsize[3]={1.0,1.0,1.0}; + int ret=0; + + + /* + * Get arguments + */ + if(argc==1) {tpcPrintUsage(argv[0], info, stderr); return(1);} + flatfile[0]=dbname[0]=(char)0; + + /* Options */ + for(ai=1; ai<argc; ai++) if(*argv[ai]=='-') { /* options */ + char *cptr=argv[ai]+1; if(*cptr=='-') cptr++; if(cptr==NULL) continue; + if(strncasecmp(cptr, "PXLSIZE=", 8)==0) { + if(atofList(cptr+8, ",", pxlsize, 3)<3) + {fprintf(stderr, "Error: invalid pixel sizes.\n"); return(1);} + if(!(pxlsize[0]>0.0) || !(pxlsize[1]>0.0) || !(pxlsize[2]>0.0)) + {fprintf(stderr, "Error: invalid pixel sizes.\n"); return(1);} + continue; + } + if(tpcProcessStdOptions(argv[ai], &help, &version, &verbose)==0) continue; + fprintf(stderr, "Error: invalid option '%s'.\n", argv[ai]); + return(1); + } else break; // tac name argument may start with '-' + + TPCSTATUS status; statusInit(&status); + statusSet(&status, __func__, __FILE__, __LINE__, TPCERROR_OK); + status.verbose=verbose-1; + + /* Print help or version? */ + if(help==2) {tpcHtmlUsage(argv[0], info, ""); return(0);} + if(help) {tpcPrintUsage(argv[0], info, stdout); return(0);} + if(version) {tpcPrintBuild(argv[0], stdout); return(0);} + + /* Process other arguments, starting from the first non-option */ + ret=0; + if(ai<argc) {strlcpy(flatfile, argv[ai], FILENAME_MAX); ai++;} + if(ai<argc) {if(atoiCheck(argv[ai++], &dimx)) ret++;} + if(ai<argc) {if(atoiCheck(argv[ai++], &dimy)) ret++;} + if(ai<argc) {if(atoiCheck(argv[ai++], &dimz)) ret++;} + if(ai<argc) {if(atoiCheck(argv[ai++], &dimt)) ret++;} + if(ret || dimx<1 || dimy<1 || dimz<1 || dimt<0) { + fprintf(stderr, "Error: invalid dimension.\n"); + return(1); + } + if(ai<argc) {strlcpy(dbname, argv[ai], FILENAME_MAX); ai++;} + if(ai<argc) {fprintf(stderr, "Error: too many arguments.\n"); return(1);} + + /* Is something missing or wrong? */ + if(!dbname[0]) { + fprintf(stderr, "Error: missing command-line argument; use option --help\n"); + return(1); + } + + /* In verbose mode print arguments and options */ + if(verbose>1) { + printf("flatfile := %s\n", flatfile); + printf("dbname := %s\n", dbname); + printf("dimx := %d\n", dimx); + printf("dimy := %d\n", dimy); + printf("dimz := %d\n", dimz); + printf("dimt := %d\n", dimt); + printf("sizex := %g\n", pxlsize[0]); + printf("sizey := %g\n", pxlsize[1]); + printf("sizez := %g\n", pxlsize[2]); + fflush(stdout); + } + + /* Make NIfTI filenames */ + if(verbose>1) printf("Make NIfTI file names\n"); + char hdrfile[FILENAME_MAX], imgfile[FILENAME_MAX], siffile[FILENAME_MAX]; + if(niftiCreateFNames(dbname, hdrfile, imgfile, siffile, IMG_FORMAT_NIFTI_1S)) { + fprintf(stderr, " Error: invalid NIfTI name %s\n", dbname); + return(1); + } + + + /* Allocate memory for float data */ + if(verbose>1) printf("Allocate memory for flat data\n"); + size_t pxlNr=(size_t)dimz*dimy*dimx*dimt; + float *fdata; + fdata=(float*)calloc(pxlNr, sizeof(float)); + if(fdata==NULL) { + fprintf(stderr, "Error: out of memory.\n"); + return(2); + } + + /* + * Read the binary file + */ + { + FILE *fp; + if(verbose>1) printf("Opening input datafile %s\n", flatfile); + if((fp=fopen(flatfile, "rb")) == NULL) { + fprintf(stderr, "Error: cannot open file %s\n", flatfile); + free(fdata); return(3); + } + float *fptr=fdata; + size_t rnr=fread((char*)fptr, 4, pxlNr, fp); + if(rnr==0) { + fprintf(stderr, "Error: cannot read file %s\n", flatfile); + fclose(fp); free(fdata); return(3); + } + if(rnr<pxlNr) { + fprintf(stderr, "Error: binary file does not contain data for all pixels.\n"); + fclose(fp); free(fdata); return(3); + } + /* Check that we are at the end of the binary file; feof() does not help here */ + char buf[10]; + rnr=fread(buf, 4, 1, fp); + if(rnr!=0) { + fprintf(stderr, "Error: mismatching matrix size and binary data.\n"); + fclose(fp); free(fdata); return(3); + } + fclose(fp); + } + + /* Find the range of pixel values */ + float pxlmin, pxlmax; pxlmin=pxlmax=nanf(""); + for(size_t i=0; i<pxlNr; i++) if(!isfinite(pxlmin) || fdata[i]<pxlmin) pxlmin=fdata[i]; + for(size_t i=0; i<pxlNr; i++) if(!isfinite(pxlmax) || fdata[i]>pxlmax) pxlmax=fdata[i]; + if(verbose>1) { + printf(" pxlmin := %g\n", pxlmin); + printf(" pxlmax := %g\n", pxlmax); + } + + + /* + * Set NIfTI header contents + */ + if(verbose>1) printf("Fill NIfTI header\n"); + NIFTI_DSR dsr; + dsr.n=1; + /* Set NIfTI byte order to current machines byte order */ + dsr.byte_order=endianLittle(); + /* Initiate header structures with zeroes */ + memset(&dsr.h1, 0, sizeof(NIFTI_1_HEADER)); + memset(&dsr.e, 0, sizeof(NIFTI_EXTENDER)); + /* Set header */ + dsr.h1.sizeof_hdr=NIFTI1_HEADER_SIZE; + strcpy(dsr.h1.data_type, ""); + strlcpy(dsr.h1.db_name, dbname, 17); + dsr.h1.extents=16384; // not used in NIfTI, but required for Analyze compatibility + dsr.h1.regular='r'; // not used in NIfTI, but required for Analyze compatibility + dsr.h1.dim_info='\0'; // MRI slice ordering + /* Image dimension */ + for(int i=0; i<8; i++) dsr.h1.dim[i]=1; + dsr.h1.dim[0]=4; + dsr.h1.dim[1]=dimx; + dsr.h1.dim[2]=dimy; + dsr.h1.dim[3]=dimz; + dsr.h1.dim[4]=dimt; + dsr.h1.intent_p1=0.0; + dsr.h1.intent_p2=0.0; + dsr.h1.intent_p3=0.0; + dsr.h1.intent_code=NIFTI_INTENT_NONE; + dsr.h1.datatype=NIFTI_DT_FLOAT; // data as floats, so no need to scale + dsr.h1.bitpix=32; + dsr.h1.slice_start=0; + for(int i=0; i<8; i++) dsr.h1.pixdim[i]=0.0; + // https://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/qsform.html + dsr.h1.pixdim[0]=1.0; // Set to either 1.0 or -1.0 + dsr.h1.pixdim[1]=pxlsize[0]; // pixel size in x dimension + dsr.h1.pixdim[2]=pxlsize[1]; // pixel size in y dimension + dsr.h1.pixdim[3]=pxlsize[2]; // pixel size in z dimension + dsr.h1.vox_offset=352; // Would be 0 for 1D format + dsr.h1.scl_slope=1.0; // no need to scale pixel values + dsr.h1.scl_inter=0.0; // no need to scale pixel values + dsr.h1.slice_end=0; + dsr.h1.slice_code=0; + dsr.h1.xyzt_units=NIFTI_UNITS_MM+NIFTI_UNITS_SEC; + dsr.h1.cal_max=pxlmax; + dsr.h1.cal_min=pxlmin; + dsr.h1.slice_duration=0.0; + dsr.h1.toffset=0.0; + dsr.h1.glmax=dsr.h1.cal_max; // unused in NIfTI + dsr.h1.glmin=0; // unused in NIfTI + strlcpy(dsr.h1.descrip, "flat2nii", 80); + strcpy(dsr.h1.aux_file, ""); + dsr.h1.qform_code=0; + dsr.h1.sform_code=0; + dsr.h1.quatern_b=0; + dsr.h1.quatern_c=0; + dsr.h1.quatern_d=0; + dsr.h1.qoffset_x=0; + dsr.h1.qoffset_y=0; + dsr.h1.qoffset_z=0; + for(int i=0; i<4; i++) dsr.h1.srow_x[i]=0; + for(int i=0; i<4; i++) dsr.h1.srow_y[i]=0; + for(int i=0; i<4; i++) dsr.h1.srow_z[i]=0; + strcpy(dsr.h1.intent_name, ""); + strcpy(dsr.h1.magic, "n+1"); // Would be "ni1" for 1D format + /* Extension is left as 0 0 0 0 */ + + + /* + * Write NIfTI header + */ + if(verbose>1) printf("Writing NIfTI header\n"); + /* Delete previous NIfTI */ + /* It does not need to be valid NIfTI format, just that the file names match */ + if(fileExist(hdrfile)) remove(hdrfile); + if(fileExist(imgfile)) remove(imgfile); + //if(fileExist(siffile)) remove(siffile); + /* Write NIfTI header */ + if(niftiWriteHeader(hdrfile, &dsr, verbose-1)) { + fprintf(stderr, "Error: cannot write header.\n"); + free(fdata); + return(11); + } + + /* + * Write NIfTI matrix data + */ + if(verbose>1) printf("Writing NIfTI image data\n"); + FILE *fp=fopen(imgfile, "r+b"); + if(fp==NULL) { + fprintf(stderr, "Error: cannot open %s for write.\n", imgfile); + free(fdata); + if(fileExist(hdrfile)) remove(hdrfile); + if(fileExist(imgfile)) remove(imgfile); + return(12); + } + /* Move file pointer to the place of matrix data start */ + if(fseeko(fp, (size_t)dsr.h1.vox_offset, SEEK_SET)!=0) { + fprintf(stderr, "Error: invalid file write position.\n"); + fclose(fp); free(fdata); + if(fileExist(hdrfile)) remove(hdrfile); + if(fileExist(imgfile)) remove(imgfile); + return(13); + } + /* Write data */ + if(fwrite(fdata, sizeof(float), pxlNr, fp) != pxlNr) { + fprintf(stderr, "Error: cannot write image matrix.\n"); + fclose(fp); free(fdata); + if(fileExist(hdrfile)) remove(hdrfile); + if(fileExist(imgfile)) remove(imgfile); + return(14); + } + fclose(fp); free(fdata); + if(verbose>0) printf("written %s\n", imgfile); + + return(0); +} +/*****************************************************************************/ + +/*****************************************************************************/ +/// @endcond +/*****************************************************************************/ diff --git a/v2/simimg/test/flat2nii/test_flat2nii.sh b/v2/simimg/test/flat2nii/test_flat2nii.sh new file mode 100644 index 0000000000000000000000000000000000000000..731103a2dba9e148b4430823506bc170c91f5a8f --- /dev/null +++ b/v2/simimg/test/flat2nii/test_flat2nii.sh @@ -0,0 +1,168 @@ +#!/bin/bash +#: Title : test_flat2nii +#: Date : 2023-08-18 +#: Author : "Vesa Oikonen" <vesa.oikonen@utu.fi> +#: Options : None + +# Set the name of executable to test +if [ -n "${OS+1}" ] && [ "$OS" = "Windows_NT" ]; then +EXT=.exe; +else +EXT=; +fi +PROGRAM=../../flat2nii$EXT; +if [ ! -f $PROGRAM ]; then + printf "Failed: executable does not exist.\n" + exit 1 +fi + +printf "=====================================================================\n" +printf "creating test data for %s\n" $PROGRAM +printf "=====================================================================\n" + + +if [ ! -f flat1.txt ] || [ ! -f flat1.bin ]; then + printf "\n creating flat1.txt \n" + printf "frame 1 plane 1\n" + printf "0 0 0 0 0\n" > flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "frame 1 plane 2\n" + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "frame 1 plane 3\n" + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "0 0 0 0 0\n" >> flat1.txt + printf "frame 2 plane 1\n" + printf "1 0 0 0 0\n" >> flat1.txt + printf "0 1 0 0 0\n" >> flat1.txt + printf "0 0 1 0 0\n" >> flat1.txt + printf "0 0 0 1 0\n" >> flat1.txt + printf "0 0 0 0 1\n" >> flat1.txt + printf "0 0 0 0 1\n" >> flat1.txt + printf "frame 2 plane 2\n" + printf "2 2 0 0 0\n" >> flat1.txt + printf "2 2 2 0 0\n" >> flat1.txt + printf "0 2 2 2 0\n" >> flat1.txt + printf "0 0 2 2 2\n" >> flat1.txt + printf "0 0 0 2 2\n" >> flat1.txt + printf "0 0 0 0 2\n" >> flat1.txt + printf "frame 2 plane 3\n" + printf "3 3 0 0 0\n" >> flat1.txt + printf "3 3 3 0 0\n" >> flat1.txt + printf "0 3 3 3 0\n" >> flat1.txt + printf "0 0 3 3 3\n" >> flat1.txt + printf "0 0 0 3 3\n" >> flat1.txt + printf "0 0 0 0 3\n" >> flat1.txt + + printf "\n creating flat1.bin \n" + asc2flat flat1.txt flat1.bin + if [ $? -ne 0 ] ; then printf "Failed!\n" ; exit 1 ; fi +fi + + + +printf "=====================================================================\n" +printf "testing %s\n" $PROGRAM +printf "=====================================================================\n" + + +printf "\n\n 1.0.0 \n" +printf " Test case: Make image from flat file. \n" +printf " Expected result: Correct image created. \n\n" + +rm -f output.nii + +$PROGRAM flat1.bin 5 6 3 2 output +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +img2tif -s output.nii +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +img2flat --silent output.nii output.bin +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +diff flat1.bin output.bin +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + +nii_lhdr output.nii > header.txt +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +iftisval header.txt pixdim "1 1 1 1 0 0 0 0" +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + + +printf "\n\n 1.0.1 \n" +printf " Test case: Same but set pixel size and overwrite existing image. \n" +printf " Expected result: Correct image created. \n\n" + +$PROGRAM -pxlsize=2.2,2.3,2.5 flat1.bin 5 6 3 2 output +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" + +nii_lhdr output.nii > header.txt +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +iftisval header.txt pixdim "1 2.2 2.3 2.5 0 0 0 0" +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + + + +printf "\n\n 2.0.0 \n" +printf " Test case: Missing argument. \n" +printf " Expected result: Error. \n\n" + +$PROGRAM flat1.bin 5 6 3 2 1>stdout.txt 2>&1 +if [ $? -eq 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +iftisval stdout.txt Error "missing command-line argument; use option --help" +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + +$PROGRAM flat1.bin 5 6 3 output 1>stdout.txt 2>&1 +if [ $? -eq 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +iftisval stdout.txt Error "invalid dimension." +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + + + +printf "\n\n 2.1.0 \n" +printf " Test case: Mismatching matrix size. \n" +printf " Expected result: Error. \n\n" + +$PROGRAM flat1.bin 5 6 3 3 output 1>stdout.txt 2>&1 +if [ $? -eq 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +iftisval stdout.txt Error "binary file does not contain data for all pixels." +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + +$PROGRAM flat1.bin 5 6 3 1 output 1>stdout.txt 2>&1 +if [ $? -eq 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n ok \n" +iftisval stdout.txt Error "mismatching matrix size and binary data." +if [ $? -ne 0 ] ; then printf "\nFailed!\n" ; exit 1 ; fi +printf "\n passed. \n" + + +printf "\n===================================================================\n" +printf " All passed! \n" +printf "===================================================================\n" +exit 0 +