You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

127 lines
4.8 KiB

  1. #!/usr/bin/python
  2. # Spectral envelope preprocessor.
  3. # Requires two streams as input: the original raw data, and sinefit data.
  4. import nilmtools.filter
  5. import nilmdb.client
  6. from numpy import *
  7. import scipy.fftpack
  8. import scipy.signal
  9. from matplotlib import pyplot as p
  10. import bisect
  11. def main(argv = None):
  12. # Set up argument parser
  13. f = nilmtools.filter.Filter()
  14. parser = f.setup_parser("Spectral Envelope Preprocessor", skip_paths = True)
  15. group = parser.add_argument_group("Prep options")
  16. group.add_argument("-c", "--column", action="store", type=int,
  17. help="Column number (first data column is 1)")
  18. group.add_argument("-n", "--nharm", action="store", type=int, default=4,
  19. help="number of odd harmonics to compute")
  20. exc = group.add_mutually_exclusive_group()
  21. exc.add_argument("-r", "--rotate", action="store", type=float,
  22. help="rotate FFT output by this many degrees")
  23. exc.add_argument("-R", "--rotate-rad", action="store", type=float,
  24. help="rotate FFT output by this many radians")
  25. group.add_argument("srcpath", action="store",
  26. help="Path of raw input, e.g. /foo/raw")
  27. group.add_argument("sinepath", action="store",
  28. help="Path of sinefit input, e.g. /foo/sinefit")
  29. group.add_argument("destpath", action="store",
  30. help="Path of prep output, e.g. /foo/prep")
  31. # Parse arguments
  32. try:
  33. args = f.parse_args(argv)
  34. except nilmtools.filter.MissingDestination as e:
  35. rec = "float32_%d" % (e.parsed_args.nharm * 2)
  36. print "Source is %s (%s)" % (e.src.path, e.src.layout)
  37. print "Destination %s doesn't exist" % (e.dest.path)
  38. print "You could make it with a command like:"
  39. print " nilmtool -u %s create %s %s" % (e.dest.url, e.dest.path, rec)
  40. raise SystemExit(1)
  41. # Check arguments
  42. if args.column is None or args.column < 1:
  43. parser.error("need a column number >= 1")
  44. if args.nharm < 1 or args.nharm > 32:
  45. parser.error("number of odd harmonics must be 1-32")
  46. if args.rotate is not None:
  47. rotation = args.rotate * 2.0 * pi / 360.0
  48. else:
  49. rotation = args.rotate_rad or 0.0
  50. # Check the sine fit stream
  51. client_sinefit = nilmdb.client.Client(args.url)
  52. sinefit = nilmtools.filter.get_stream_info(client_sinefit, args.sinepath)
  53. if not sinefit:
  54. raise Exception("sinefit data not found")
  55. if sinefit.layout != "float32_3":
  56. raise Exception("sinefit data type is " + sinefit.layout
  57. + "; expected float32_3")
  58. # Check and set metadata in prep stream
  59. f.check_dest_metadata({ "prep_raw_source": f.src.path,
  60. "prep_sinefit_source": sinefit.path,
  61. "prep_column": args.column })
  62. # Run the processing function on all data
  63. f.process_numpy(process, args = (client_sinefit, sinefit.path, args.column,
  64. args.nharm, rotation))
  65. def process(data, interval, args, insert_function, final):
  66. (client, sinefit_path, column, nharm, rotation) = args
  67. rows = data.shape[0]
  68. data_timestamps = data[:,0]
  69. processed = 0
  70. out = zeros((1, nharm * 2 + 1))
  71. # Pull out sinefit data for the entire time range of this block
  72. for sinefit_line in client.stream_extract(sinefit_path,
  73. data[0, 0], data[rows-1, 0]):
  74. # Extract sinefit data to get zero crossing timestamps
  75. (t_min, f0, A, C) = [ float(x) for x in sinefit_line.split() ]
  76. t_max = t_min + 1e6 / f0
  77. # Find the indices of data that correspond to (t_min, t_max)
  78. idx_min = bisect.bisect_left(data_timestamps, t_min)
  79. idx_max = bisect.bisect_left(data_timestamps, t_max)
  80. if idx_min >= idx_max:
  81. # something's wonky; ignore this period
  82. continue
  83. if idx_max >= len(data_timestamps):
  84. # max is likely past the end of our chunk, so stop
  85. # processing this chunk now.
  86. break
  87. # Perform FFT over those indices
  88. N = idx_max - idx_min
  89. d = data[idx_min:idx_max, column]
  90. F = scipy.fftpack.fft(d) / N
  91. # If we wanted more harmonics than we have, pad with zeros
  92. if N < (nharm * 2):
  93. F = r_[F, zeros(nharm * 2 - N)]
  94. # Fill output data
  95. out[0, 0] = t_min
  96. for k in range(nharm):
  97. Fk = F[2 * k + 1] * e**(rotation * 1j * (k+1))
  98. out[0, 2 * k + 1] = -imag(Fk) # Pk
  99. out[0, 2 * k + 2] = real(Fk) # Qk
  100. # Insert it and continue
  101. insert_function(out)
  102. processed = idx_max
  103. print "Processed", processed, "of", rows, "rows"
  104. return processed
  105. if __name__ == "__main__":
  106. main()