Compare commits

..

9 Commits

Author SHA1 Message Date
ce0691d6c4 sineefit: Change sfit4 to fit to \sin instead of \cos
And adjust the period locator accordingly.
Fitting \sin is the same mathematically, it's just conceptually more
straightforward since we're locating zero crossings anyway.
2013-04-27 18:12:20 -04:00
4da658e960 sinefit: move initial estimate into the main iteration loop
Just a little less code.  Same results.
2013-04-27 17:50:23 -04:00
8ab31eafc2 Allow shorthand method for creating an option-less parser.
This is mostly just intended to make a simple filter example shorter.
2013-04-21 16:53:28 -04:00
979ab13bff Force fs to be a float in sfit4 2013-04-17 17:58:15 -04:00
f4fda837ae Bump required nilmdb version to 1.6.0 2013-04-11 11:55:11 -04:00
5547d266d0 filter: Don't include trailing unprocessed data in the inserted intervals 2013-04-11 11:53:17 -04:00
372e977e4a Reverse cleanup order to handle interruptions better 2013-04-10 18:38:41 -04:00
640a680704 Increase default min amplitude in sinefit 2013-04-10 17:09:52 -04:00
2e74e6cd63 Skip over data if we aren't able to process any. Change output format 2013-04-10 17:01:07 -04:00
5 changed files with 48 additions and 24 deletions

View File

@@ -61,7 +61,7 @@ setup(name='nilmtools',
long_description = "NILM Database Tools", long_description = "NILM Database Tools",
license = "Proprietary", license = "Proprietary",
author_email = 'jim@jtan.com', author_email = 'jim@jtan.com',
install_requires = [ 'nilmdb >= 1.5.0', install_requires = [ 'nilmdb >= 1.6.0',
'numpy', 'numpy',
'scipy', 'scipy',
'matplotlib', 'matplotlib',

View File

@@ -238,12 +238,15 @@ def main(argv = None):
timestamp_to_seconds(total))) timestamp_to_seconds(total)))
continue continue
printf(" removing data before %s\n", timestamp_to_human(remove_before)) printf(" removing data before %s\n", timestamp_to_human(remove_before))
if args.yes: # Clean in reverse order. Since we only use the primary stream and not
client.stream_remove(path, None, remove_before) # the decimated streams to figure out which data to remove, removing
for ap in streams[path].also_clean_paths: # the primary stream last means that we might recover more nicely if
printf(" also removing from %s\n", ap) # we are interrupted and restarted.
clean_paths = list(reversed(streams[path].also_clean_paths)) + [ path ]
for p in clean_paths:
printf(" removing from %s\n", p)
if args.yes: if args.yes:
client.stream_remove(ap, None, remove_before) client.stream_remove(p, None, remove_before)
# All done # All done
if not args.yes: if not args.yes:

View File

@@ -67,7 +67,7 @@ def get_stream_info(client, path):
class Filter(object): class Filter(object):
def __init__(self): def __init__(self, parser_description = None):
self._parser = None self._parser = None
self._client_src = None self._client_src = None
self._client_dest = None self._client_dest = None
@@ -78,6 +78,9 @@ class Filter(object):
self.end = None self.end = None
self.interhost = False self.interhost = False
self.force_metadata = False self.force_metadata = False
if parser_description is not None:
self.setup_parser(parser_description)
self.parse_args()
@property @property
def client_src(self): def client_src(self):
@@ -275,6 +278,10 @@ class Filter(object):
Return value of 'function' is the number of data rows processed. Return value of 'function' is the number of data rows processed.
Unprocessed data will be provided again in a subsequent call Unprocessed data will be provided again in a subsequent call
(unless 'final' is True). (unless 'final' is True).
If unprocessed data remains after 'final' is True, the interval
being inserted will be ended at the timestamp of the first
unprocessed data point.
""" """
if args is None: if args is None:
args = [] args = []
@@ -319,7 +326,13 @@ class Filter(object):
# Last call for this contiguous interval # Last call for this contiguous interval
if old_array.shape[0] != 0: if old_array.shape[0] != 0:
function(old_array, interval, args, insert_function, True) processed = function(old_array, interval, args,
insert_function, True)
if processed != old_array.shape[0]:
# Truncate the interval we're inserting at the first
# unprocessed data point. This ensures that
# we'll not miss any data when we run again later.
insert_ctx.update_end(old_array[processed][0])
def main(argv = None): def main(argv = None):
# This is just a dummy function; actual filters can use the other # This is just a dummy function; actual filters can use the other

View File

@@ -3,6 +3,8 @@
# Spectral envelope preprocessor. # Spectral envelope preprocessor.
# Requires two streams as input: the original raw data, and sinefit data. # Requires two streams as input: the original raw data, and sinefit data.
from nilmdb.utils.printf import *
from nilmdb.utils.time import timestamp_to_human
import nilmtools.filter import nilmtools.filter
import nilmdb.client import nilmdb.client
from numpy import * from numpy import *
@@ -106,7 +108,6 @@ def process(data, interval, args, insert_function, final):
# Pull out sinefit data for the entire time range of this block # Pull out sinefit data for the entire time range of this block
for sinefit_line in client.stream_extract(sinefit_path, for sinefit_line in client.stream_extract(sinefit_path,
data[0, 0], data[rows-1, 0]): data[0, 0], data[rows-1, 0]):
def prep_period(t_min, t_max, rot): def prep_period(t_min, t_max, rot):
""" """
Compute prep coefficients from time t_min to t_max, which Compute prep coefficients from time t_min to t_max, which
@@ -163,7 +164,15 @@ def process(data, interval, args, insert_function, final):
break break
processed = idx_max processed = idx_max
print "Processed", processed, "of", rows, "rows" # If we processed no data but there's lots in here, pretend we
# processed half of it.
if processed == 0 and rows > 10000:
processed = rows / 2
printf("%s: warning: no periods found; skipping %d rows\n",
timestamp_to_human(data[0][0]), processed)
else:
printf("%s: processed %d of %d rows\n",
timestamp_to_human(data[0][0]), processed, rows)
return processed return processed
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -25,7 +25,7 @@ def main(argv = None):
help='Maximum valid frequency ' help='Maximum valid frequency '
'(default: approximate frequency * 2))') '(default: approximate frequency * 2))')
group.add_argument('-a', '--min-amp', action='store', type=float, group.add_argument('-a', '--min-amp', action='store', type=float,
default=10.0, default=20.0,
help='Minimum signal amplitude (default: %(default)s)') help='Minimum signal amplitude (default: %(default)s)')
# Parse arguments # Parse arguments
@@ -98,12 +98,12 @@ def process(data, interval, args, insert_function, final):
continue continue
#p.plot(arange(N), this) #p.plot(arange(N), this)
#p.plot(arange(N), A * cos(f0/fs * 2 * pi * arange(N) + phi) + C, 'g') #p.plot(arange(N), A * sin(f0/fs * 2 * pi * arange(N) + phi) + C, 'g')
# Period starts when the argument of cosine is 3*pi/2 degrees, # Period starts when the argument of sine is 0 degrees,
# so we're looking for sample number: # so we're looking for sample number:
# n = (3 * pi / 2 - phi) / (f0/fs * 2 * pi) # n = (0 - phi) / (f0/fs * 2 * pi)
zc_n = (3 * pi / 2 - phi) / (f0 / fs * 2 * pi) zc_n = (0 - phi) / (f0 / fs * 2 * pi)
period_n = fs/f0 period_n = fs/f0
# Add periods to make N positive # Add periods to make N positive
@@ -149,15 +149,15 @@ def sfit4(data, fs):
Output: Output:
Parameters [A, f0, phi, C] to fit the equation Parameters [A, f0, phi, C] to fit the equation
x[n] = A * cos(f0/fs * 2 * pi * n + phi) + C x[n] = A * sin(f0/fs * 2 * pi * n + phi) + C
where n is sample number. Or, as a function of time: where n is sample number. Or, as a function of time:
x(t) = A * cos(f0 * 2 * pi * t + phi) + C x(t) = A * sin(f0 * 2 * pi * t + phi) + C
by Jim Paris by Jim Paris
(Verified to match sfit4.m) (Verified to match sfit4.m)
""" """
N = len(data) N = len(data)
t = linspace(0, (N-1) / fs, N) t = linspace(0, (N-1) / float(fs), N)
## Estimate frequency using FFT (step b) ## Estimate frequency using FFT (step b)
Fc = fft(data) Fc = fft(data)
@@ -182,18 +182,17 @@ def sfit4(data, fs):
i = arccos((Z2*cos(ni2) - Z1*cos(ni1)) / (Z2-Z1)) / n i = arccos((Z2*cos(ni2) - Z1*cos(ni1)) / (Z2-Z1)) / n
# Convert to Hz # Convert to Hz
f0 = i * fs / N f0 = i * float(fs) / N
# Fit it. We'll catch exceptions here and just returns zeros # Fit it. We'll catch exceptions here and just returns zeros
# if something fails with the least squares fit, etc. # if something fails with the least squares fit, etc.
try: try:
# first guess for A0, B0 using 3-parameter fit (step c) # first guess for A0, B0 using 3-parameter fit (step c)
s = zeros(3)
w = 2*pi*f0 w = 2*pi*f0
D = c_[cos(w*t), sin(w*t), ones(N)]
s = linalg.lstsq(D, data)[0]
# Now iterate 6 times (step i) # Now iterate 7 times (step b, plus 6 iterations of step i)
for idx in range(6): for idx in range(7):
D = c_[cos(w*t), sin(w*t), ones(N), D = c_[cos(w*t), sin(w*t), ones(N),
-s[0] * t * sin(w*t) + s[1] * t * cos(w*t) ] # eqn B.16 -s[0] * t * sin(w*t) + s[1] * t * cos(w*t) ] # eqn B.16
s = linalg.lstsq(D, data)[0] # eqn B.18 s = linalg.lstsq(D, data)[0] # eqn B.18
@@ -202,7 +201,7 @@ def sfit4(data, fs):
## Extract results ## Extract results
A = sqrt(s[0]*s[0] + s[1]*s[1]) # eqn B.21 A = sqrt(s[0]*s[0] + s[1]*s[1]) # eqn B.21
f0 = w / (2*pi) f0 = w / (2*pi)
phi = -arctan2(s[1], s[0]) # eqn B.22 phi = arctan2(s[0], s[1]) # eqn B.22 (flipped for sin instead of cos)
C = s[2] C = s[2]
return (A, f0, phi, C) return (A, f0, phi, C)
except Exception as e: except Exception as e: