diff --git a/debug/cf b/debug/cf index 5dbde7f30..b4b4cd7c1 100644 --- a/debug/cf +++ b/debug/cf @@ -3,7 +3,7 @@ DB=1 #X=-x -ANSI=1 +#ANSI=1 #MEM=1 #NOTUIL=1 #FAST=1 diff --git a/docs/filters.md b/docs/filters.md index adc5ddd99..177d11906 100644 --- a/docs/filters.md +++ b/docs/filters.md @@ -142,6 +142,17 @@ The "-F" option can be used repeatedly as long as the variable name part is different. A different filter id and parameters can be specified for each occurrence. +It can be convenient to specify that the same compression is to be +applied to more than one variable. To support this, two additional +*-F* cases are defined. + +1. ````-F *,...``` means apply the filter to all variables in the dataset. +2. ````-F v1|v2|..,...``` means apply the filter to a multiple variables. + +Note that the characters '*' and '|' are bash reserved characters, +so you will probably need to escape or quote the filter spec in +that environment. + As a rule, any input filter on an input variable will be applied to the equivalent output variable -- assuming the output file type is netcdf-4. It is, however, sometimes convenient to suppress @@ -149,16 +160,19 @@ output compression either totally or on a per-variable basis. Total suppression of output filters can be accomplished by specifying a special case of "-F", namely this. ```` -nccopy -F "none" input.nc output.nc +nccopy -F none input.nc output.nc ```` -Suppression of output filtering for a specific variable can be accomplished -using this format. +The expression ````-F *,none```` is equivalent to ````-F none````. + +Suppression of output filtering for a specific set of variables +can be accomplished using these formats. ```` nccopy -F "var,none" input.nc output.nc +nccopy -F "v1|v2|...,none" input.nc output.nc ```` -where "var" is the fully qualified name of the variable. +where "var" and the "vi" are the fully qualified name of a variable. -The rules for all possible cases of the "-F" flag are defined +The rules for all possible cases of the "-F none" flag are defined by this table. @@ -169,6 +183,7 @@ by this table.
falseunspecifieddefineduse input filter
false-Fvar,noneNAunfiltered
false-Fvar,...NAuse output filter +
falseunspecifiednoneunfiltered
Parameter Encoding {#ParamEncode} diff --git a/nc_test4/Makefile.am b/nc_test4/Makefile.am index 613427dcb..0fe04a332 100644 --- a/nc_test4/Makefile.am +++ b/nc_test4/Makefile.am @@ -136,7 +136,7 @@ tst_put_vars_two_unlim_dim.c tst_empty_vlen_unlim.c \ run_empty_vlen_test.sh ref_hdf5_compat1.nc ref_hdf5_compat2.nc \ ref_hdf5_compat3.nc tst_misc.sh tdset.h5 tst_szip.sh ref_szip.h5 \ ref_szip.cdl tst_filter.sh bzip2.cdl filtered.cdl unfiltered.cdl \ -ref_bzip2.c findplugin.in perftest.sh +ref_bzip2.c findplugin.in perftest.sh unfilteredvv.cdl filteredvv.cdl CLEANFILES = tst_mpi_parallel.bin cdm_sea_soundings.nc bm_chunking.nc \ tst_floats_1D.cdl floats_1D_3.nc floats_1D.cdl tst_*.nc \ diff --git a/nc_test4/filteredvv.cdl b/nc_test4/filteredvv.cdl new file mode 100644 index 000000000..7b276fcd7 --- /dev/null +++ b/nc_test4/filteredvv.cdl @@ -0,0 +1,41 @@ +netcdf filteredvv { +dimensions: + dim0 = 4 ; + dim1 = 4 ; +variables: + float var1(dim0, dim1) ; + var1:_Storage = "chunked" ; + var1:_ChunkSizes = 2, 2 ; + var1:_Endianness = "little" ; + var1:_Filter = "307,9,4" ; + var1:_NoFill = "true" ; + +// global attributes: + :_Format = "netCDF-4" ; +data: + + var1 = + 100, 101, 102, 103, + 104, 105, 106, 107, + 108, 109, 1010, 1011, + 1012, 1013, 1014, 1015 ; + +group: g { + variables: + float var2(dim0, dim1) ; + var2:_Storage = "chunked" ; + var2:_ChunkSizes = 2, 2 ; + var2:_Endianness = "little" ; + var2:_Filter = "307,9,4" ; + var2:_NoFill = "true" ; + + // group attributes: + data: + + var2 = + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15 ; + } // group g +} diff --git a/nc_test4/test_filter_misc.c b/nc_test4/test_filter_misc.c index 086cebedd..66483aba4 100644 --- a/nc_test4/test_filter_misc.c +++ b/nc_test4/test_filter_misc.c @@ -160,7 +160,7 @@ verifyparams(void) static int openfile(void) { - unsigned int* params; + unsigned int* params = NULL; /* Open the file and check it. */ CHECK(nc_open(TESTFILE, NC_NOWRITE, &ncid)); @@ -191,6 +191,8 @@ openfile(void) } if(nerrs > 0) return NC_EFILTER; + if(params) free(params); + /* Verify chunking */ if(!verifychunks()) return 0; diff --git a/nc_test4/tst_filter.sh b/nc_test4/tst_filter.sh index 5bf450e0a..d73c1d094 100755 --- a/nc_test4/tst_filter.sh +++ b/nc_test4/tst_filter.sh @@ -30,7 +30,12 @@ cat $1 \ # Function to extract _Filter attribute from a file # These attributes might be platform dependent getfilterattr() { -sed -e '/var:_Filter/p' -ed <$1 >$2 +case "$1" in +var1) sed -e '/var1:_Filter/p' -ed <$1 >$2 ;; +var2) sed -e '/var2:_Filter/p' -ed <$1 >$2 ;; +var) sed -e '/var:_Filter/p' -ed <$1 >$2 ;; +*) sed -e '/var:_Filter/p' -ed <$1 >$2 ;; +esac } trimleft() { @@ -98,7 +103,10 @@ fi if test "x$NCP" = x1 ; then echo "*** Testing dynamic filters using nccopy" rm -f ./unfiltered.nc ./filtered.nc ./tmp.nc ./filtered.dump ./tst_filter.txt +# Create our input test files ${NCGEN} -4 -lb -o unfiltered.nc ${srcdir}/unfiltered.cdl +${NCGEN} -4 -lb -o unfilteredvv.nc ${srcdir}/unfilteredvv.cdl + echo " *** Testing simple filter application" ${NCCOPY} -M0 -F "/g/var,307,9,4" unfiltered.nc filtered.nc ${NCDUMP} -s filtered.nc > ./tst_filter.txt @@ -107,6 +115,22 @@ sclean ./tst_filter.txt ./filtered.dump diff -b -w ${srcdir}/filtered.cdl ./filtered.dump echo " *** Pass: nccopy simple filter" +echo " *** Testing '*' filter application" +${NCCOPY} -M0 -F "*,307,9,4" unfilteredvv.nc filteredvv.nc +${NCDUMP} -s filteredvv.nc > ./tst_filtervv.txt +# Remove irrelevant -s output +sclean ./tst_filtervv.txt ./filteredvv.dump +diff -b -w ${srcdir}/filteredvv.cdl ./filteredvv.dump +echo " *** Pass: nccopy '*' filter" + +echo " *** Testing 'v|v' filter application" +${NCCOPY} -M0 -F "var1|/g/var2,307,9,4" unfilteredvv.nc filteredvbar.nc +${NCDUMP} -n filteredvv -s filteredvbar.nc > ./tst_filtervbar.txt +# Remove irrelevant -s output +sclean ./tst_filtervbar.txt ./filteredvbar.dump +diff -b -w ${srcdir}/filteredvv.cdl ./filteredvbar.dump +echo " *** Pass: nccopy 'v|v' filter" + echo " *** Testing pass-thru of filters" rm -f ./tst_filter.txt tst_filter2.txt ./tst_filter2.nc # Prevent failure by allowing any chunk size @@ -170,6 +194,8 @@ rm -f ./bzip*.nc ./unfiltered.nc ./filtered.nc ./tst_filter.txt ./tst_filter2.tx rm -f ./test_bzip2.c rm -f ./testmisc.nc rm -f ./tst_filter2.nc +rm -f ./unfilteredvv.nc ./filteredvv.nc ./filteredvbar.nc +rm -f ./tst_filtervv.txt ./tst_filtervbar.txt echo "*** Pass: all selected tests passed" exit 0 diff --git a/nc_test4/unfilteredvv.cdl b/nc_test4/unfilteredvv.cdl new file mode 100644 index 000000000..434681aef --- /dev/null +++ b/nc_test4/unfilteredvv.cdl @@ -0,0 +1,29 @@ +netcdf unfilteredvv { +dimensions: + dim0 = 4 ; + dim1 = 4 ; +variables: + float var1(dim0, dim1) ; + var1:_ChunkSizes = 2, 2 ; +data: + + var1 = + 100, 101, 102, 103, + 104, 105, 106, 107, + 108, 109, 1010, 1011, + 1012, 1013, 1014, 1015 ; + +group: g { + variables: + float var2(dim0, dim1) ; + var2:_ChunkSizes = 2, 2 ; + + data: + + var2 = + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15 ; + } // group g +} diff --git a/ncdump/list.c b/ncdump/list.c index d5f442d76..f2f7031d3 100644 --- a/ncdump/list.c +++ b/ncdump/list.c @@ -36,6 +36,19 @@ List* listnew(void) return l; } +int +listfreeall(List* l) +{ + if(l) { + int i; + for(i=0;i do not apply any output filters unless specified */ #endif @@ -245,7 +244,40 @@ done: } static int -parsefilterspec(const char* optarg0, struct FilterSpec* spec) +parsevarlist(char* vars, List* vlist) +{ + int stat = NC_NOERR; + char* q = NULL; + int nvars = 0; + + /* Special case 1: empty set of vars */ + if(vars == NULL || strlen(vars)==0) {stat = NC_EINVAL; goto done;} + + /* Special case 2: "*" */ + if(strcmp(vars,"*")==0) { + listpush(vlist,strdup("*")); + goto done; + } + + /* Walk delimitng on '|' separators */ + for(q=vars;*q;q++) { + if(*q == '\\') q++; + else if(*q == '|') {*q = '\0'; nvars++;} + /* else continue */ + } + nvars++; /*for last var*/ + /* Rewalk to capture the variables */ + for(q=vars;nvars > 0; nvars--) { + listpush(vlist,strdup(q)); + q += (strlen(q)+1); /* move to next */ + } + +done: + return stat; +} + +static int +parsefilterspec(const char* optarg0, List* speclist) { int stat = NC_NOERR; char* optarg = NULL; @@ -254,50 +286,63 @@ parsefilterspec(const char* optarg0, struct FilterSpec* spec) unsigned int id; char* p = NULL; char* remainder = NULL; + List* vlist = NULL; + int i; + int isnone = 0; - if(optarg0 == NULL || strlen(optarg0) == 0 || spec == NULL) return 0; - - memset(spec,0,sizeof(struct FilterSpec)); - + if(optarg0 == NULL || strlen(optarg0) == 0 || speclist == NULL) return 0; optarg = strdup(optarg0); - - /* Collect the fqn, taking escapes into account */ + /* Delimit the initial set of variables, taking escapes into account */ p = optarg; remainder = NULL; - for(;*p;p++) { - if(*p == '\\') p++; + for(;;p++) { + if(*p == '\0') {remainder = p; break;} else if(*p == ',') {*p = '\0'; remainder = p+1; break;} - else if(*p == '\0') {remainder = p; break;} + else if(*p == '\\') p++; /* else continue */ } - if(strlen(optarg) == 0) {stat = NC_EINVAL; goto done;} /* fqn does not exist */ - /* Make sure leading '/' is in place */ - if(optarg[0]=='/') - spec->fqn = strdup(optarg); - else { - spec->fqn = (char*)malloc(1+strlen(optarg)+1); - strcpy(spec->fqn,"/"); - strcat(spec->fqn,optarg); - } + /* Parse the variable list */ + if((vlist = listnew()) == NULL) {stat = NC_ENOMEM; goto done;} + if((stat=parsevarlist(optarg,vlist))) goto done; - /* Check for special cases */ - if( (remainder == NULL) || - (strncmp(remainder,"none",4) == 0)) { - spec->nofilter = 1; - goto done; - } - - /* Collect the id+parameters */ - if((stat = NC_parsefilterspec(remainder,&id,&nparams,¶ms)) == NC_NOERR) { - if(spec != NULL) { - spec->filterid = id; + if(strcasecmp(remainder,"none") != 0) { + /* Collect the id+parameters */ + if((stat=NC_parsefilterspec(remainder,&id,&nparams,¶ms))) goto done; + } else + isnone = 1; + + /* Construct a spec entry for each element in vlist */ + for(i=0;ifqn = malloc(vlen+1+1); /* make room for nul and possible prefix '/' */ + if(spec->fqn == NULL) {stat = NC_ENOMEM; goto done;} + spec->fqn[0] = '\0'; /* for strlcat */ + if(strcmp(var,"*") != 0 && var[0] != '/') strlcat(spec->fqn,"/",vlen+2); + strlcat(spec->fqn,var,vlen+2); + if(isnone) + spec->nofilter = 1; + else { + spec->filterid = id; spec->nparams = nparams; - spec->params = params; + /* Duplicate the params */ + spec->params = malloc(nparams*sizeof(unsigned int)); + if(spec->params == NULL) {stat = NC_ENOMEM; goto done;} + memcpy(spec->params,params,nparams*sizeof(unsigned int)); } + listpush(speclist,spec); + spec = NULL; } done: + if(params) free(params); + if(vlist) listfreeall(vlist); if(optarg) free(optarg); return stat; } @@ -672,9 +717,10 @@ copy_var_filter(int igrp, int varid, int ogrp, int o_varid, int inkind, int outk VarID vid = {igrp,varid}; VarID ovid = {ogrp,o_varid}; /* handle filter parameters, copying from input, overriding with command-line options */ - struct FilterSpec inspec = {NULL,0,0,0,NULL}, - ospec = {NULL,0,0,0,NULL}, - actualspec = {NULL,0,0,0,NULL}; + struct FilterSpec* ospec = NULL; + struct FilterSpec inspec; + struct FilterSpec nospec; + struct FilterSpec* actualspec = NULL; int i; char* ofqn = NULL; int inputdefined, outputdefined, unfiltered; @@ -689,20 +735,23 @@ copy_var_filter(int igrp, int varid, int ogrp, int o_varid, int inkind, int outk /* Clear the in and out specs */ memset(&inspec,0,sizeof(inspec)); - memset(&ospec,0,sizeof(ospec)); - memset(&actualspec,0,sizeof(actualspec)); + memset(&nospec,0,sizeof(nospec)); + nospec.nofilter = 1; + actualspec = NULL; + ospec = NULL; /* Is there a filter on the output variable */ outputdefined = 0; /* default is no filter defined */ /* Only bother to look if output is netcdf-4 variant */ if(outnc4) { /* See if any output filter spec is defined for this output variable */ - for(i=0;ifqn,"*")==0 || strcmp(spec->fqn,ofqn)==0) { + ospec = spec; + outputdefined = 1; + break; + } } } @@ -726,35 +775,38 @@ copy_var_filter(int igrp, int varid, int ogrp, int o_varid, int inkind, int outk global output input Actual Output suppress filter filter filter ----------------------------------------------- - true undefined NA unfiltered - true 'none' NA unfiltered - true defined NA use output filter - false undefined defined use input filter - false 'none' NA unfiltered - false defined NA use output filter + true undefined NA unfiltered + true 'none' NA unfiltered + true defined NA use output filter + false undefined defined use input filter + false 'none' NA unfiltered + false defined NA use output filter + false undefined undefined unfiltered */ unfiltered = 0; if(suppressfilters && !outputdefined) /* row 1 */ unfiltered = 1; - else if(suppressfilters && outputdefined && ospec.nofilter) /* row 2 */ + else if(suppressfilters && outputdefined && ospec->nofilter) /* row 2 */ unfiltered = 1; else if(suppressfilters && outputdefined) /* row 3 */ actualspec = ospec; else if(!suppressfilters && !outputdefined && inputdefined) /* row 4 */ - actualspec = inspec; - else if(!suppressfilters && outputdefined && ospec.nofilter) /* row 5 */ + actualspec = &inspec; + else if(!suppressfilters && outputdefined && ospec->nofilter) /* row 5 */ unfiltered = 1; else if(!suppressfilters && outputdefined) /* row 6 */ actualspec = ospec; + else if(!suppressfilters && !outputdefined && !inputdefined) /* row 7 */ + actualspec = &nospec; /* Apply actual filter spec if any */ if(!unfiltered) { if((stat=nc_def_var_filter(ovid.grpid,ovid.varid, - actualspec.filterid, - actualspec.nparams, - actualspec.params))) + actualspec->filterid, + actualspec->nparams, + actualspec->params))) goto done; } done: @@ -910,6 +962,7 @@ copy_var_specials(int igrp, int varid, int ogrp, int o_varid, int inkind, int ou int stat = NC_NOERR; int innc4 = (inkind == NC_FORMAT_NETCDF4 || inkind == NC_FORMAT_NETCDF4_CLASSIC); int outnc4 = (outkind == NC_FORMAT_NETCDF4 || outkind == NC_FORMAT_NETCDF4_CLASSIC); + int deflated = 0; /* true iff deflation is applied */ if(!outnc4) return stat; /* Ignore non-netcdf4 files */ @@ -949,6 +1002,7 @@ copy_var_specials(int igrp, int varid, int ogrp, int o_varid, int inkind, int ou then default chunking will be turned on; so do a special check for that. */ if(shuffle_out != 0 || deflate_out != 0) NC_CHECK(nc_def_var_deflate(ogrp, o_varid, shuffle_out, deflate_out, deflate_level_out)); + deflated = deflate_out; } } @@ -970,8 +1024,10 @@ copy_var_specials(int igrp, int varid, int ogrp, int o_varid, int inkind, int ou } } - /* handle other general filters */ - NC_CHECK(copy_var_filter(igrp, varid, ogrp, o_varid, inkind, outkind)); + if(!deflated) { + /* handle other general filters */ + NC_CHECK(copy_var_filter(igrp, varid, ogrp, o_varid, inkind, outkind)); + } return stat; } @@ -2001,10 +2057,6 @@ main(int argc, char**argv) char* inputfile = NULL; char* outputfile = NULL; int c; -#ifdef USE_NETCDF4 - int i; - struct FilterSpec filterspec; -#endif chunkspecinit(); option_chunkspecs = listnew(); @@ -2145,17 +2197,15 @@ main(int argc, char**argv) break; case 'F': /* optional filter spec for a specified variable */ #ifdef USE_NETCDF4 - /* If the arg is "none" then suppress all filters + /* If the arg is "none" or "*,none" then suppress all filters on output unless explicit */ - if(strcmp(optarg,"none")==0) { + if(strcmp(optarg,"none")==0 + || strcasecmp(optarg,"*,none")==0) { suppressfilters = 1; } else { - if(parsefilterspec(optarg,&filterspec) != NC_NOERR) - usage(); - if(nfilterspecs >= (MAX_FILTER_SPECS-1)) - error("too many -F filterspecs\n"); - filterspecs[nfilterspecs] = filterspec; - nfilterspecs++; + if(filterspecs == NULL) + filterspecs = listnew(); + NC_CHECK(parsefilterspec(optarg,filterspecs)); /* Force output to be netcdf-4 */ option_kind = NC_FORMAT_NETCDF4; } @@ -2195,9 +2245,9 @@ main(int argc, char**argv) #ifdef USE_NETCDF4 #ifdef DEBUGFILTER - { int j; - for(i=0;i