From 0f75be194ae3810e2bced98ee3527da5f0e57f43 Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Thu, 15 Mar 2018 23:50:13 +0000 Subject: [PATCH 1/8] create .gitattributes to prevent pickle corruption --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9825bcf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.p text eol=lf +*.npy text eol=lf From adbb75ba36528f3a92eaff4bb701aba0231b921b Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Fri, 16 Mar 2018 08:09:00 +0000 Subject: [PATCH 2/8] edited to python 3 - first pass --- bat_eval/cpu_detection.py | 2 +- bat_eval/models/detector.npy | Bin 392121 -> 392114 bytes bat_eval/nms_slow.py | 4 ++-- bat_eval/run_detector.py | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bat_eval/cpu_detection.py b/bat_eval/cpu_detection.py index 8a34452..2be8750 100755 --- a/bat_eval/cpu_detection.py +++ b/bat_eval/cpu_detection.py @@ -32,7 +32,7 @@ def __init__(self, weight_file, params_file): params_file is the path to the network parameters """ - self.weights = np.load(weight_file) + self.weights = np.load(weight_file, encoding='bytes') if not all([weight.dtype==np.float32 for weight in self.weights]): for i in range(self.weights.shape[0]): self.weights[i] = self.weights[i].astype(np.float32) diff --git a/bat_eval/models/detector.npy b/bat_eval/models/detector.npy index 11315ee3e71e0e271d35f1d7afa9fd23303ba5af..57619a8e835e74da622e248e1a1be21fe7326129 100644 GIT binary patch delta 61 zcmdn_U3}Ab@rEsoTnD#vA7uQXw*Aj@#;VBnaz`d0X5L=z$g)9syGAO@ZkFv&GFcvL RZa*=ZW&WS-8539ovjJ&m8w*!d}YC!tebH=JjM&9-kM 0): - print '\nsaving results to', op_file_name_total + print( '\nsaving results to', op_file_name_total) wo.save_to_txt(op_file_name_total, results) else: - print 'no detections to save' + print( 'no detections to save') From de7678592b3f0c4d0e3dc25dc0fefaec7773eaa9 Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Fri, 16 Mar 2018 16:32:47 +0000 Subject: [PATCH 3/8] fixed bugs so it runs but fails --- bat_eval/cpu_detection.py | 2 +- bat_eval/run_detector.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bat_eval/cpu_detection.py b/bat_eval/cpu_detection.py index 2be8750..bb5701c 100755 --- a/bat_eval/cpu_detection.py +++ b/bat_eval/cpu_detection.py @@ -1,7 +1,7 @@ import numpy as np from scipy.ndimage import zoom from scipy.ndimage.filters import gaussian_filter1d -import cPickle as pickle +import pickle import time from spectrogram import Spectrogram diff --git a/bat_eval/run_detector.py b/bat_eval/run_detector.py index ebd9d3a..ea1d3ef 100755 --- a/bat_eval/run_detector.py +++ b/bat_eval/run_detector.py @@ -81,8 +81,8 @@ def run_model(det, audio, file_dur, samp_rate, detection_thresh, max_num_calls=0 save_res = True # load data - data_dir = 'wavs/' # this is the path to your audio files - op_ann_dir = 'results/' # this where your results will be saved + data_dir = '../../Bat Recordings/' # this is the path to your audio files + op_ann_dir = data_dir+'results/' # this where your results will be saved op_file_name_total = op_ann_dir + 'op_file.csv' if not os.path.isdir(op_ann_dir): os.makedirs(op_ann_dir) From 145403ba43379527cb39fafc0febe0010b075464 Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Fri, 16 Mar 2018 16:48:56 +0000 Subject: [PATCH 4/8] working fine and giving results. Needs to be properly tested against known good data --- bat_eval/cnn_helpers.py | 7 ++++--- bat_eval/cpu_detection.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bat_eval/cnn_helpers.py b/bat_eval/cnn_helpers.py index 183c59b..d4f7901 100755 --- a/bat_eval/cnn_helpers.py +++ b/bat_eval/cnn_helpers.py @@ -11,10 +11,11 @@ def aligned_malloc(shape, dtype, alignment=16): alignment is required memory alignment in bytes """ itemsize = np.dtype(dtype).itemsize - extra = alignment / itemsize + extra = alignment // itemsize size = np.prod(shape) + #print(size, type(size), extra, type(extra)) buf = np.empty(size + extra, dtype=dtype) - ofs = (-buf.ctypes.data % alignment) / itemsize + ofs = (-buf.ctypes.data % alignment) // itemsize aa = buf[ofs:ofs+size].reshape(shape) assert (aa.ctypes.data % alignment) == 0 assert (aa.flags['C_CONTIGUOUS']) == True @@ -120,7 +121,7 @@ def fully_connected_as_corr(ip, filters, bias): """ # create DxHxsliding_width views of input - similar to corr2d - sliding_width = filters.shape[0] / np.prod(ip.shape[:2]) + sliding_width = filters.shape[0] // np.prod(ip.shape[:2]) op = view_as_windows(ip, (ip.shape[0],ip.shape[1],sliding_width)) op = op.reshape((np.prod(op.shape[:3]), np.prod(op.shape[3:]))) diff --git a/bat_eval/cpu_detection.py b/bat_eval/cpu_detection.py index bb5701c..008abfd 100755 --- a/bat_eval/cpu_detection.py +++ b/bat_eval/cpu_detection.py @@ -138,7 +138,7 @@ def eval_network_1_dense(self, ip): prob = prob - np.amax(prob, axis=1, keepdims=True) prob = np.exp(prob) prob = prob[:, 1] / prob.sum(1) - prob = np.hstack((prob, np.zeros((ip.shape[1]/4)-prob.shape[0], dtype=np.float32))) + prob = np.hstack((prob, np.zeros((ip.shape[1]//4)-prob.shape[0], dtype=np.float32))) return prob @@ -169,7 +169,7 @@ def eval_network_2_dense(self, ip): prob = prob - np.amax(prob, axis=1, keepdims=True) prob = np.exp(prob) prob = prob[:, 1] / prob.sum(1) - prob = np.hstack((prob, np.zeros((ip.shape[1]/4)-prob.shape[0], dtype=np.float32))) + prob = np.hstack((prob, np.zeros((ip.shape[1]//4)-prob.shape[0], dtype=np.float32))) return prob From 7eed29cb2d43b6e5f39d042904947c1de4741067 Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Mon, 6 Aug 2018 18:49:42 +0100 Subject: [PATCH 5/8] updates and add command line args --- bat_eval/cpu_detection.py | 21 ++++++++------- bat_eval/myskimage.py | 3 +-- bat_eval/run_detector.py | 40 +++++++++++++++++++++++----- bat_train/classifier.py | 6 ++--- bat_train/cls_audio_forest.py | 4 +-- bat_train/cls_cnn.py | 6 ++--- bat_train/create_results.py | 4 +-- bat_train/evaluate.py | 2 +- bat_train/export_detector_weights.py | 8 +++--- bat_train/nms_slow.py | 4 +-- bat_train/random_forest.py | 6 ++--- bat_train/run_comparison.py | 16 +++++------ bat_train/run_detector.py | 16 +++++------ readme.md | 5 ++++ 14 files changed, 88 insertions(+), 53 deletions(-) diff --git a/bat_eval/cpu_detection.py b/bat_eval/cpu_detection.py index 008abfd..fcd7605 100755 --- a/bat_eval/cpu_detection.py +++ b/bat_eval/cpu_detection.py @@ -1,13 +1,12 @@ +import pickle +import warnings import numpy as np from scipy.ndimage import zoom from scipy.ndimage.filters import gaussian_filter1d -import pickle -import time from spectrogram import Spectrogram import cnn_helpers as ch -import warnings warnings.simplefilter("ignore", UserWarning) try: @@ -33,7 +32,7 @@ def __init__(self, weight_file, params_file): """ self.weights = np.load(weight_file, encoding='bytes') - if not all([weight.dtype==np.float32 for weight in self.weights]): + if not all([weight.dtype == np.float32 for weight in self.weights]): for i in range(self.weights.shape[0]): self.weights[i] = self.weights[i].astype(np.float32) @@ -99,10 +98,15 @@ def create_spec(self, audio, sampling_rate): max_freq=self.max_freq, min_freq=self.min_freq) hspec = self.sp.process_spectrogram(hspec, denoise_spec=self.denoise, smooth_spec=self.smooth_spec) - nsize = (np.ceil(hspec.shape[0]/2.0).astype(int), np.ceil(hspec.shape[1]/2.0).astype(int)) - spec = ch.aligned_malloc(nsize, np.float32) - - zoom(hspec, 0.5, output=spec, order=1) + #nsize = (np.ceil(hspec.shape[0]/2.0).astype(int), + # np.ceil(hspec.shape[1]/2.0).astype(int)) + nsize = zoom(hspec, 0.5, order=1).shape #dm edit + spec = ch.aligned_malloc(nsize, np.float32) + try: + zoom(hspec, 0.5, output=spec, order=1) + except Exception as e: + print(e, hspec.shape, nsize) + #print(spec, hspec, nsize,e) return spec @@ -172,4 +176,3 @@ def eval_network_2_dense(self, ip): prob = np.hstack((prob, np.zeros((ip.shape[1]//4)-prob.shape[0], dtype=np.float32))) return prob - diff --git a/bat_eval/myskimage.py b/bat_eval/myskimage.py index 03d07c5..60559c7 100644 --- a/bat_eval/myskimage.py +++ b/bat_eval/myskimage.py @@ -6,7 +6,6 @@ were also copied to as dependencies of "gaussian" function. """ from __future__ import division -import numbers import collections as coll import numpy as np from scipy import ndimage as ndi @@ -41,7 +40,7 @@ def warn(msg): def img_as_float(image): - dtype=np.float32 + dtype = np.float32 force_copy = False """ diff --git a/bat_eval/run_detector.py b/bat_eval/run_detector.py index ea1d3ef..0c9e0f3 100755 --- a/bat_eval/run_detector.py +++ b/bat_eval/run_detector.py @@ -75,15 +75,43 @@ def run_model(det, audio, file_dur, samp_rate, detection_thresh, max_num_calls=0 if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description='Process some bat files.') + parser.add_argument('--threshold', type=float, default=0.9, + help='detection threshold') + parser.add_argument('--datadir', + help='root directory for output') + parser.add_argument('--resultsdir', default='results', + help='directory in which to place the results') + parser.add_argument('--resfile', default='op_file.csv', + help='results file name') + parser.add_argument('--timeexp', default=True, + help='Apply time expansion') + parser.add_argument('--saveres', default=True, + help='Save results files') + + args = parser.parse_args() + print(args) # params - detection_thresh = 0.95 # make this smaller if you want more calls - do_time_expansion = True # if audio is already time expanded set this to False - save_res = True + detection_thresh = 0.9 + if 'threshold' in args: + detection_thresh =args.threshold # make this smaller if you want more calls + do_time_expansion = args.timeexp # if audio is already time expanded set this to False + save_res = args.saveres # load data - data_dir = '../../Bat Recordings/' # this is the path to your audio files - op_ann_dir = data_dir+'results/' # this where your results will be saved - op_file_name_total = op_ann_dir + 'op_file.csv' + data_dir = 'C:\\Users/David/Documents/Bat recordings/Audiomoth/moth4/Stonebarrow/' # this is the path to your audio files + if 'datadir' in args: + data_dir=args.datadir + outdir = 'results3/' + if 'resultsdir' in args: + outdir = args.resultsdir+'/' + op_ann_dir = data_dir + outdir # this where your results will be saved + outfile = 'op_file.csv' + if 'resfile' in args: + outfile = args.resfile + op_file_name_total = op_ann_dir + outfile if not os.path.isdir(op_ann_dir): os.makedirs(op_ann_dir) diff --git a/bat_train/classifier.py b/bat_train/classifier.py index 5419564..a490673 100755 --- a/bat_train/classifier.py +++ b/bat_train/classifier.py @@ -18,7 +18,7 @@ def __init__(self, params_): elif self.params.classification_model == 'segment': self.model = seg.SegmentAudio(self.params) else: - print 'Invalid model specified' + print('Invalid model specified') def save_features(self, files): self.model.save_features(files) @@ -34,9 +34,9 @@ def train(self, files, gt_pos, durations): # hard negative mining if self.params.num_hard_negative_mining > 0 and self.params.classification_model != 'segment': - print '\nhard negative mining' + print( '\nhard negative mining') for hn in range(self.params.num_hard_negative_mining): - print '\thmn round', hn + print( '\thmn round', hn) positions, class_labels = self.do_hnm(files, gt_pos, durations, positions, class_labels) self.model.train(positions, class_labels, files, durations) diff --git a/bat_train/cls_audio_forest.py b/bat_train/cls_audio_forest.py index 817e404..ae4cd09 100755 --- a/bat_train/cls_audio_forest.py +++ b/bat_train/cls_audio_forest.py @@ -37,7 +37,7 @@ def train(self, positions, class_labels, files, durations): # flatten list of lists and set to correct output features = np.vstack(feats) labels = np.vstack(labs) - print 'train size', features.shape + print( 'train size', features.shape) self.forest.train(features, labels, False) def test(self, file_name=None, file_duration=None, audio_samples=None, sampling_rate=None): @@ -67,7 +67,7 @@ def create_or_load_features(self, file_name=None, audio_samples=None, sampling_r features = compute_features(audio_samples, sampling_rate, self.params) else: if self.params.load_features_from_file: - features = np.load(self.params.feature_dir + file_name + '.npy') + features = np.load(self.params.feature_dir + file_name + '.npy', encoding='bytes') else: sampling_rate, audio_samples = wavfile.read(self.params.audio_dir + file_name + '.wav') features = compute_features(audio_samples, sampling_rate, self.params) diff --git a/bat_train/cls_cnn.py b/bat_train/cls_cnn.py index 0494d63..60bc0ed 100755 --- a/bat_train/cls_cnn.py +++ b/bat_train/cls_cnn.py @@ -38,7 +38,7 @@ def train(self, positions, class_labels, files, durations): # flatten list of lists and set to correct output size features = np.vstack(feats) labels = np.vstack(labs).astype(np.uint8)[:,0] - print 'train size', features.shape + print( 'train size', features.shape) # train network input_var = theano.tensor.tensor4('inputs') @@ -90,7 +90,7 @@ def create_or_load_features(self, file_name=None, audio_samples=None, sampling_r features = compute_features(audio_samples, sampling_rate, self.params) else: if self.params.load_features_from_file: - features = np.load(self.params.feature_dir + file_name + '.npy') + features = np.load(self.params.feature_dir + file_name + '.npy', encoding='bytes') else: sampling_rate, audio_samples = wavfile.read(self.params.audio_dir + file_name + '.wav') features = compute_features(audio_samples, sampling_rate, self.params) @@ -120,7 +120,7 @@ def build_cnn(ip_size, input_var, net_type): elif net_type == 'small': net = network_sm(ip_size, input_var) else: - print 'Error: network not defined' + print( 'Error: network not defined') return net def network_big(ip_size, input_var): diff --git a/bat_train/create_results.py b/bat_train/create_results.py index f164630..fa60044 100755 --- a/bat_train/create_results.py +++ b/bat_train/create_results.py @@ -11,7 +11,7 @@ def plot_prec_recall(alg_name, recall, precision, nms_prob=None): # average precision ave_prec = evl.calc_average_precision(recall, precision) - print 'average precision (area) = %.3f ' % ave_prec + print( 'average precision (area) = %.3f ' % ave_prec) # recall at 95% precision desired_precision = 0.95 @@ -20,7 +20,7 @@ def plot_prec_recall(alg_name, recall, precision, nms_prob=None): else: recall_at_precision = 0 - print 'recall at', int(desired_precision*100), '% precision = ', "%.3f" % recall_at_precision + print( 'recall at', int(desired_precision*100), '% precision = ', "%.3f" % recall_at_precision) plt.plot([0, 1.02], [desired_precision, desired_precision], 'b:', linewidth=1) plt.plot([recall_at_precision, recall_at_precision], [0, desired_precision], 'b:', linewidth=1) diff --git a/bat_train/evaluate.py b/bat_train/evaluate.py index 0a85751..76ec1a4 100755 --- a/bat_train/evaluate.py +++ b/bat_train/evaluate.py @@ -12,7 +12,7 @@ def compute_error_auc(op_str, gt, pred, prob): fpr, tpr, thresholds = roc_curve(gt, pred) roc_auc = auc(fpr, tpr) - print op_str, ', class acc = %.3f, ROC AUC = %.3f' % (class_acc, roc_auc) + print( op_str, ', class acc = %.3f, ROC AUC = %.3f' % (class_acc, roc_auc)) #return class_acc, roc_auc diff --git a/bat_train/export_detector_weights.py b/bat_train/export_detector_weights.py index 12440e2..7fee407 100755 --- a/bat_train/export_detector_weights.py +++ b/bat_train/export_detector_weights.py @@ -3,7 +3,7 @@ can use it. """ -import cPickle as pickle +import pickle from lasagne.layers.helper import get_all_param_values, get_output_shape, set_all_param_values import numpy as np import lasagne @@ -13,16 +13,16 @@ save_detector = False -print 'saving detector' +print( 'saving detector') model_dir = 'results/models/' model_file = model_dir + 'test_set_norfolk.mod' -print model_file +print( model_file) mod = pickle.load(open(model_file)) weights = get_all_param_values(mod.model.network['prob']) np.save(model_file[:-4], weights) -print 'weights shape', len(weights) +print( 'weights shape', len(weights)) # save detection params mod_params = {'win_size':0, 'chunk_size':0, 'max_freq':0, 'min_freq':0, diff --git a/bat_train/nms_slow.py b/bat_train/nms_slow.py index d1dfd91..27437f1 100644 --- a/bat_train/nms_slow.py +++ b/bat_train/nms_slow.py @@ -59,8 +59,8 @@ def test_nms(): pos, prob = nms_1d(y, win_size, y.shape[0]) pos_f, prob_f = nms_fast.nms_1d(y, win_size, y.shape[0]) - print 'diff between implementations =', 1-np.isclose(prob_f, prob).mean() - print 'diff between implementations =', 1-np.isclose(pos_f, pos).mean() + print( 'diff between implementations =', 1-np.isclose(prob_f, prob).mean()) + print( 'diff between implementations =', 1-np.isclose(pos_f, pos).mean()) plt.close('all') plt.plot(y) diff --git a/bat_train/random_forest.py b/bat_train/random_forest.py index b021ed7..151b055 100755 --- a/bat_train/random_forest.py +++ b/bat_train/random_forest.py @@ -254,7 +254,7 @@ def optimize_node(self, x_local, y_local, node): ## Parallel training helper - used to train trees in parallel def train_forest_helper(t_id, X, Y, params, seed): - #print 'tree', t_id + #print( 'tree', t_id) np.random.seed(seed) tree = Tree(t_id, params) tree.train(X, Y) @@ -277,9 +277,9 @@ def train(self, X, Y, delete_old_trees): self.trees.extend(Parallel(n_jobs=-1)(delayed(train_forest_helper)(t_id, X, Y, self.params, seeds[t_id]) for t_id in range(self.params.num_trees))) else: - #print 'Standard training' + #print( 'Standard training') for t_id in range(self.params.num_trees): - print 'tree', t_id + print( 'tree', t_id) tree = Tree(t_id, self.params) tree.train(X, Y) self.trees.append(tree) diff --git a/bat_train/run_comparison.py b/bat_train/run_comparison.py index 10cf38a..8a42a42 100755 --- a/bat_train/run_comparison.py +++ b/bat_train/run_comparison.py @@ -39,11 +39,11 @@ def read_baseline_res(baseline_file_name, test_files): os.mkdir(result_dir) if not os.path.isdir(model_dir): os.mkdir(model_dir) - print 'test set:', test_set + print( 'test set:', test_set) plt.close('all') # train and test_pos are in units of seconds - loaded_data_tr = np.load(data_set) + loaded_data_tr = np.load(data_set, encoding='bytes') train_pos = loaded_data_tr['train_pos'] train_files = loaded_data_tr['train_files'] train_durations = loaded_data_tr['train_durations'] @@ -57,7 +57,7 @@ def read_baseline_res(baseline_file_name, test_files): # # CNN - print '\ncnn' + print( '\ncnn') params.classification_model = 'cnn' model = clss.Classifier(params) # train and test @@ -71,7 +71,7 @@ def read_baseline_res(baseline_file_name, test_files): # # random forest - print '\nrandom forest' + print( '\nrandom forest') params.classification_model = 'rf_vanilla' model = clss.Classifier(params) # train and test @@ -83,7 +83,7 @@ def read_baseline_res(baseline_file_name, test_files): # # segment - print '\nsegment' + print( '\nsegment') params.classification_model = 'segment' model = clss.Classifier(params) # train and test @@ -97,7 +97,7 @@ def read_baseline_res(baseline_file_name, test_files): # scanr scanr_bat_results = base_line_dir + 'scanr/test_set_'+ test_set +'_scanr.csv' if os.path.isfile(scanr_bat_results): - print '\nscanr' + print( '\nscanr') scanr_pos, scanr_prob = read_baseline_res(scanr_bat_results, test_files) precision_scanr, recall_scanr = evl.prec_recall_1d(scanr_pos, scanr_prob, test_pos, test_durations, params.detection_overlap, params.window_size) res.plot_prec_recall('scanr', recall_scanr, precision_scanr) @@ -106,7 +106,7 @@ def read_baseline_res(baseline_file_name, test_files): # sonobat sono_bat_results = base_line_dir + 'sonobat/test_set_'+ test_set +'_sono.csv' if os.path.isfile(sono_bat_results): - print '\nsonobat' + print( '\nsonobat') sono_pos, sono_prob = read_baseline_res(sono_bat_results, test_files) precision_sono, recall_sono = evl.prec_recall_1d(sono_pos, sono_prob, test_pos, test_durations, params.detection_overlap, params.window_size) res.plot_prec_recall('sonobat', recall_sono, precision_sono) @@ -115,7 +115,7 @@ def read_baseline_res(baseline_file_name, test_files): # kaleidoscope kal_bat_results = base_line_dir + 'kaleidoscope/test_set_'+ test_set +'_kaleidoscope.csv' if os.path.isfile(kal_bat_results): - print '\nkaleidoscope' + print( '\nkaleidoscope') kal_pos, kal_prob = read_baseline_res(kal_bat_results, test_files) precision_kal, recall_kal = evl.prec_recall_1d(kal_pos, kal_prob, test_pos, test_durations, params.detection_overlap, params.window_size) res.plot_prec_recall('kaleidoscope', recall_kal, precision_kal) diff --git a/bat_train/run_detector.py b/bat_train/run_detector.py index f318cb8..53b7547 100755 --- a/bat_train/run_detector.py +++ b/bat_train/run_detector.py @@ -14,15 +14,15 @@ def read_audio(file_name, do_time_expansion, chunk_size, win_size): try: samp_rate_orig, audio = wavfile.read(file_name) except: - print ' Error reading file' + print( ' Error reading file') return True, None, None, None, None # convert to mono if stereo if len(audio.shape) == 2: - print ' Warning: stereo file. Just taking right channel.' + print( ' Warning: stereo file. Just taking right channel.') audio = audio[:, 1] file_dur = audio.shape[0] / float(samp_rate_orig) - print ' dur', round(file_dur,3), '(secs) , fs', samp_rate_orig + print( ' dur', round(file_dur,3), '(secs) , fs', samp_rate_orig) # original model is trained on time expanded data samp_rate = samp_rate_orig @@ -113,7 +113,7 @@ def run_detector(det, audio, file_dur, samp_rate, detection_thresh): for file_cnt, file_name in enumerate(audio_files): file_name_root = file_name[len(data_dir):] - print '\n', file_cnt+1, 'of', len(audio_files), '\t', file_name_root + print( '\n', file_cnt+1, 'of', len(audio_files), '\t', file_name_root) # read audio file - skip file if cannot read read_fail, audio, file_dur, samp_rate, samp_rate_orig = read_audio(file_name, @@ -127,9 +127,9 @@ def run_detector(det, audio, file_dur, samp_rate, detection_thresh): detection_thresh) toc = time.time() - print ' detection time', round(toc-tic, 3), '(secs)' + print( ' detection time', round(toc-tic, 3), '(secs)') num_calls = len(det_time) - print ' ' + str(num_calls) + ' calls found' + print( ' ' + str(num_calls) + ' calls found') # save results if save_res: @@ -152,7 +152,7 @@ def run_detector(det, audio, file_dur, samp_rate, detection_thresh): # save to large csv if save_res and (len(results) > 0): - print '\nsaving results to', op_file_name_total + print( '\nsaving results to', op_file_name_total) wo.save_to_txt(op_file_name_total, results, np.asarray(['bat'])) else: - print 'no detections to save' + print( 'no detections to save') diff --git a/readme.md b/readme.md index 2e78b9e..0d56743 100644 --- a/readme.md +++ b/readme.md @@ -42,3 +42,8 @@ We are enormously grateful for the efforts and enthusiasm of the amazing iBats a #### License Code, audio data, and annotations are available for research purposes only i.e. non-commercial use. For any other use of the software or data please contact the authors. + +#### Updates by [Dr David Martin](mailto:d.m.a.martin@dundee.ac.uk) 2018 + +1. Moved bat_eval to Python 3 +2. Add .gitattributes to allow download to Windows without breaking models. \ No newline at end of file From b89256e80577e824fdc229635ecef6b132b5cb18 Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Sun, 14 Jun 2020 00:07:28 +0100 Subject: [PATCH 6/8] initial version of bat viewer for Audiomoth and batdetect. Need to add results boxes and zooming on spectrogram --- bat_eval/cpu_detection.py | 2 +- bat_eval/run_detector.py | 8 +- bat_view/batviewer.py | 331 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 bat_view/batviewer.py diff --git a/bat_eval/cpu_detection.py b/bat_eval/cpu_detection.py index fcd7605..6250cce 100755 --- a/bat_eval/cpu_detection.py +++ b/bat_eval/cpu_detection.py @@ -31,7 +31,7 @@ def __init__(self, weight_file, params_file): params_file is the path to the network parameters """ - self.weights = np.load(weight_file, encoding='bytes') + self.weights = np.load(weight_file, encoding='bytes', allow_pickle=True) if not all([weight.dtype == np.float32 for weight in self.weights]): for i in range(self.weights.shape[0]): self.weights[i] = self.weights[i].astype(np.float32) diff --git a/bat_eval/run_detector.py b/bat_eval/run_detector.py index 0c9e0f3..4024f1e 100755 --- a/bat_eval/run_detector.py +++ b/bat_eval/run_detector.py @@ -106,12 +106,14 @@ def run_model(det, audio, file_dur, samp_rate, detection_thresh, max_num_calls=0 data_dir=args.datadir outdir = 'results3/' if 'resultsdir' in args: - outdir = args.resultsdir+'/' - op_ann_dir = data_dir + outdir # this where your results will be saved + outdir = args.resultsdir + op_ann_dir = os.path.join(data_dir , outdir) # this where your results will be saved + + os.makedirs(op_ann_dir, exist_ok=True) outfile = 'op_file.csv' if 'resfile' in args: outfile = args.resfile - op_file_name_total = op_ann_dir + outfile + op_file_name_total = os.path.join(op_ann_dir, outfile) if not os.path.isdir(op_ann_dir): os.makedirs(op_ann_dir) diff --git a/bat_view/batviewer.py b/bat_view/batviewer.py new file mode 100644 index 0000000..3768a9b --- /dev/null +++ b/bat_view/batviewer.py @@ -0,0 +1,331 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Jun 13 11:51:05 2020 +Simple viewer for Batdetect data. +@author: David +""" +import os,struct +import wave, numpy,io,csv +import matplotlib.pyplot as plt +import scipy.signal as sig, numpy as np +import tkinter as tk +import tkinter.filedialog as tkfd +from PIL import Image, ImageTk + +class SpecViewer (tk.Frame): + def __init__(self, master=None): + tk.Frame.__init__(self, master) + self.master=master + self.pack(fill="both", expand=True) + self.createWidgets() + + def createWidgets(self): + menu = tk.Menu(self.master) + self.master.config(menu=menu) + file=tk.Menu(menu) + file.add_command(label="Exit", command=self.client_exit) + menu.add_cascade(label='File', menu=file) + settings=tk.Menu(menu) + settings.add_command(label="Audio Folder", command=self.set_AudioFolder) + settings.add_command(label="Analysis Folder", command=self.set_AnalysisFolder) + settings.add_command(label="Spectrogram Settings", command=self.set_SpectrogramSettings) + settings.add_command(label="show parameters", command = self.showparam) + menu.add_cascade(label="Settings", menu=settings) + #self.menubar = tk.Frame(self, bg='#333') + + #self.quitButton = tk.Button(self.menubar, text='Quit', command=self.quit) + #self.settingsButton(self.menubar, text="Settings", command=self.doSettings) + #self.loadButton = tk.Button(self.menubar, text="Load", command = self.loadSpectrogram) + #self.quitButton.grid() + #self.loadButton.grid(column=1,row=0) + #self.menubar.grid() + self.audioframe=tk.Frame(self,bg='#fff') + self.audioframe.pack(fill="x", expand=True) +# self.audioframe.grid(sticky=tk.W+tk.E) + self.audiofiles_lb = tk.Listbox(self.audioframe) + self.audiofiles_lb.grid(row = 0, column=1, rowspan=4, sticky=tk.N+tk.S) + self.audiofiles_sb = tk.Scrollbar(self.audioframe) + self.audiofiles_sb.grid(row = 0, column=2, rowspan=4, sticky=tk.N+tk.S) + self.audiofiles_lb.config( yscrollcommand = self.audiofiles_sb.set) + self.audiofiles_lb.bind('<>',self.selectAudio) + self.audiofiles_sb.config(command=self.audiofiles_lb.yview) + self.audioframe.grid_columnconfigure(0, weight=1) + self.lbl_temp = tk.Label(self.audioframe, text='No audio file selected') + self.lbl_temp.grid(row=0, column=0, sticky=tk.E+tk.W) + self.audiofiles_lb.insert(tk.END, "No audio files loaded") + # analysis summary. + self.analysisblock= tk.Frame(self.audioframe,bg='#ddb') + self.analysisblock.grid(sticky=tk.N+tk.S+tk.W+tk.E, row=3) + self.analysissummary = tk.Label(self.analysisblock,text='No analysis file loaded') + self.analysissummary.grid(row=0, column=0, columnspan=2, sticky=tk.E+tk.W) + self.analysisthreshold = tk.Label(self.analysisblock, text='Threshold: 0.5') + self.analysisthreshold.grid(row=1, column=0) + self.thresholdslider = tk.Scale(self.analysisblock,from_=0.0, to=1.0, + resolution=0.05, orient=tk.HORIZONTAL, command=self.setthreshold) + self.thresholdslider.set(0.8) + self.thresholdslider.grid(row=1, column=1, sticky=tk.E+tk.W) + + + self.imageframe=tk.Frame(self,bg='#fff') +# self.imageframe.grid(sticky=tk.N+tk.S+tk.W+tk.E) + self.imageframe.pack(fill="both", expand=True) + self.sview=tk.Canvas(self.imageframe, bg='#f00', height=400, width=800) +# self.sview.grid(sticky=tk.N+tk.S+tk.W+tk.E)# + self.sview.pack(fill="both", expand=True) + + def setthreshold(self,evt): + self.analysisthreshold.config(text="Threshold: {}".format(evt)) + if hasattr(self,'wavfilename'): + self.setThresholdSummary() + + def selectAudio (self,evt): + Audiofile = str(self.audiofiles_lb.get(self.audiofiles_lb.curselection())) + if Audiofile in self.audiofiles: + self.loadSpectrogram(Audiofile) + + def showparam (self): + ''' + Prints the height and width of the image window canvas + Returns + ------- + None. + + ''' + self.master.update() + print('Imagesize height {} width {}'.format(self.sview.winfo_height(), self.sview.winfo_width())) + def getspecsize(self): + ''' + Calculates the size of the image for matplotlib to produce + + Returns + ------- + None. + + ''' + self.master.update() + return (self.sview.winfo_width(), self.sview.winfo_height()) + + def client_exit(self): + self.master.destroy() + #exit() + + def set_AudioFolder(self): + ''' + Generates a file dialogue to set the audio folder + + Returns + ------- + None. + + ''' + gafd = tkfd.askdirectory(title="Folder containing Audio files") + self.audiodirectory = gafd + self.audiofiles={} + self.audiofiles_lb.delete(0,"end") + for file in os.listdir(self.audiodirectory): + if file.upper()[-4:]=='.WAV': + self.audiofiles[file[:-4]] ={'audio': file} + self.audiofiles_lb.insert(tk.END,file[:-4]) + + print(self.audiodirectory) + self.lbl_temp.config(text='{} audio files found'.format(len(self.audiofiles))) + + def set_AnalysisFolder(self): + ''' + Generates a file dialogue to set the analysis folder + + Returns + ------- + None. + + ''' + gafd = tkfd.askdirectory(title="Folder containing batdetect analysis (.csv) output files") + self.analysisdirectory = gafd + count = 0 + for file in os.listdir(self.analysisdirectory): + if file[-4:]=='.csv': + if file[:-14] in self.audiofiles: + self.audiofiles[file[:-14]]['analysis'] = file + count += 1 + print(self.analysisdirectory) + print("{} anlaysis files found and linked".format(count)) + + def set_SpectrogramSettings(): + ''' + Launches a settings window + + Returns + ------- + None. + + ''' + def loadSpectrogram(self, fileroot): + ''' + Uses a load dialogue to get an audio file, check if spectrogram exists and create if not. + Produces a spectrogram image + Overlays boxes for the Batdetect identifications. + + Returns + ------- + None. +https://stackoverflow.com/questions/8598673/how-to-save-a-pylab-figure-into-in-memory-file-which-can-be-read-into-pil-image + ''' + if fileroot in self.audiofiles: + self.loadaudiofile(os.path.join(self.audiodirectory, self.audiofiles[fileroot]['audio'])) + self.displayaudioinfo() + self.draw_spectrogram() + self.clear_analysis() + if 'analysis' in self.audiofiles[fileroot]: + print('found analysis') + self.load_analysis(fileroot) + else: + self.observations=None + + + + def loadaudiofile(self, filename): + ''' + Opens filename as a WAV file and retrieves the relevant information + + Parameters + ---------- + filename : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + self.wavfilename = filename + self.wavfile = wave.open(filename) + self.audioparams=self.wavfile.getparams() + self.limits=(0, self.audioparams.nframes/self.audioparams.framerate, 0, self.audioparams.framerate/2) + self.plotlimits = self.limits[:] + data = self.wavfile.readframes(self.audioparams.nframes) + self.audio = struct.unpack('{}h'.format(self.audioparams.nframes),data) + fh = open(self.wavfilename, 'rb') + head = fh.read(500) + self.audiocomment = '' + try: + info = head.find(b'INFOICMT')+12 + iart = head.find(b'IART ') + if info > 12: + self.audiocomment =head[info:iart].replace(b'\x00', b'').decode('utf-8') + # add some re here to extract the pertinent information from the comment + except Exception as e: + print('Error extracting header', e) + # create spectrogram + + + def displayaudioinfo(self): + ''' + Utility method to package audio information for display. + + Returns + ------- + None. + + ''' + self.lbl_temp.grid_forget() + self.lbl_filename = tk.Label(self.audioframe, text=self.wavfilename) + self.lbl_filename.grid(sticky=tk.W+tk.E, column=0, row=0) + paramtxt = "; ".join(["{}: {}".format(p, getattr(self.audioparams, p)) for p in ('nchannels', 'sampwidth', 'framerate', 'nframes')]) + + self.lbl_params = tk.Label(self.audioframe, text=paramtxt) + self.lbl_params.grid(sticky=tk.E+tk.W, column=0, row=1) + self.lbl_comment = tk.Label(self.audioframe, text=self.audiocomment, wraplength=500, justify=tk.LEFT) + self.lbl_comment.grid(sticky=tk.E+tk.W, column=0, row=2) + + def draw_spectrogram(self): + #get size for the canvas + print('foo') + dims = self.getspecsize() + fig=plt.figure(dpi=100, figsize=((dims[0]-10)/100, (dims[1]-10)/100)) + ax=fig.add_subplot(1,1,1) + if hasattr(self, 'specimg'): + self.sview.delete(self.specimg) + txt=self.sview.create_text(dims[0]/2, dims[1]/2, text='Generating Spectrogram. Please wait ... ') + self.sview.pack() + + p,freqs,bins,im = ax.specgram(self.audio, NFFT=1024, Fs=self.audioparams.framerate, noverlap=512) + print('foobar') + self.imbounds=im.get_window_extent().extents + + ax.set_xlim(self.plotlimits[0],self.plotlimits[1]) + ax.set_ylim(self.plotlimits[2],self.plotlimits[3]) + self.fullrange=im.get_window_extent().extents + buf = io.BytesIO() + print('foobar2') + fig.savefig(buf, format='png') + buf.seek(0) + self.specim = Image.open(buf) + print(self.specim) + #self.specim.show() + self.specpim=ImageTk.PhotoImage(self.specim) + print(self.specpim) + + self.specimg =self.sview.create_image(dims[0]//2,dims[1]//2,image=self.specpim) + print(self.specimg) + self.sview.delete(txt) + self.sview.pack() + + def clear_analysis(self): + ''' + Removes all analysis objects from spectrogram + + Returns + ------- + None. + + ''' + self.sview.delete('analysis') + self.sview.pack() + + def load_analysis(self, fileroot): + ''' + Loads csv file with output from batdetect and overlays on the spectrogram + + Parameters + ---------- + fileroot : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + print('loading analysis') + cr = csv.reader(open(os.path.join(app.analysisdirectory, app.audiofiles[fileroot]['analysis']), newline='')) + header=next(cr) + dr =csv.DictReader(open(os.path.join(app.analysisdirectory, app.audiofiles[fileroot]['analysis']), newline=''),fieldnames=header) + next(dr) + self.observations=[] + for obs in dr: + self.observations.append({'start':float(obs['LabelStartTime_Seconds']),'end':float(obs['LabelEndTime_Seconds']),'score':float(obs['DetectorConfidence'])}) + self.setThresholdSummary() + + def setThresholdSummary(self): + if self.observations is not None: + self.analysissummary.config(text='{} bat calls detected ({} above threshold)'.format(len(self.observations), + len([x for x in self.observations if x['score']>=self.thresholdslider.get()]))) + else: + if hasattr(self, 'wavfilename'): + self.analysissummary.config(text='No analysis available for {}'.format(self.wavfilename)) + else: + self.analysissummary.config(text='No soundfile loaded') + def plot_analysis(self): + ''' + Plot the observed calls as boxes for those that are above the threshold + + Returns + ------- + None. + + ''' +#if __name__ == "main": +root = tk.Tk() +app = SpecViewer(root) +app.master.title('Simple Spectrogram viewer') +app.mainloop() \ No newline at end of file From b15da3761ec7ddcb873c24da03996c2002b16bcf Mon Sep 17 00:00:00 2001 From: Dr David Martin Date: Sun, 14 Jun 2020 15:30:50 +0100 Subject: [PATCH 7/8] working for viewing with colormaps --- bat_view/batviewer.py | 225 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 12 deletions(-) diff --git a/bat_view/batviewer.py b/bat_view/batviewer.py index 3768a9b..0b068f7 100644 --- a/bat_view/batviewer.py +++ b/bat_view/batviewer.py @@ -17,7 +17,9 @@ def __init__(self, master=None): tk.Frame.__init__(self, master) self.master=master self.pack(fill="both", expand=True) + self.observations=None self.createWidgets() + self.redrawing=0 def createWidgets(self): menu = tk.Menu(self.master) @@ -40,7 +42,7 @@ def createWidgets(self): #self.loadButton.grid(column=1,row=0) #self.menubar.grid() self.audioframe=tk.Frame(self,bg='#fff') - self.audioframe.pack(fill="x", expand=True) + self.audioframe.pack(fill="x", expand=True, side='top') # self.audioframe.grid(sticky=tk.W+tk.E) self.audiofiles_lb = tk.Listbox(self.audioframe) self.audiofiles_lb.grid(row = 0, column=1, rowspan=4, sticky=tk.N+tk.S) @@ -54,24 +56,141 @@ def createWidgets(self): self.lbl_temp.grid(row=0, column=0, sticky=tk.E+tk.W) self.audiofiles_lb.insert(tk.END, "No audio files loaded") # analysis summary. - self.analysisblock= tk.Frame(self.audioframe,bg='#ddb') + self.analysisblock= tk.Frame(self.audioframe) self.analysisblock.grid(sticky=tk.N+tk.S+tk.W+tk.E, row=3) + self.analysissummary = tk.Label(self.analysisblock,text='No analysis file loaded') - self.analysissummary.grid(row=0, column=0, columnspan=2, sticky=tk.E+tk.W) + self.analysissummary.grid(row=0, column=0, columnspan=6, sticky=tk.E+tk.W) self.analysisthreshold = tk.Label(self.analysisblock, text='Threshold: 0.5') self.analysisthreshold.grid(row=1, column=0) + self.timestart_lbl = tk.Label(self.analysisblock, text="Start time") + self.timestart_lbl.grid(row=1,column =1, sticky=tk.E+tk.W) + self.timestart_sld = tk.Scale(self.analysisblock , from_=0, to=15, resolution=0.5, orient=tk.HORIZONTAL, command=self.settimestart) + self.timestart_sld.set(0) + self.timestart_sld.grid(row=2,column =1, sticky=tk.E+tk.W) + self.timezoom_lbl = tk.Label(self.analysisblock, text="Time window") + self.timezoom_lbl.grid(row=1, column =2, sticky=tk.E+tk.W) + self.timezoom_sld = tk.Scale(self.analysisblock , from_=1, to=15, resolution=0.5, orient=tk.HORIZONTAL, command=self.settimezoom) + self.timezoom_sld.set(15) + self.timezoom_sld.grid(row=2,column =2, sticky=tk.E+tk.W) + self.freqstart_lbl = tk.Label(self.analysisblock, text="Start frequency") + self.freqstart_lbl.grid(row=1,column =3, sticky=tk.E+tk.W) + self.freqstart_sld = tk.Scale(self.analysisblock , from_=0, to=0, resolution=1000, orient=tk.HORIZONTAL, command=self.setfreqstart) + self.freqstart_sld.set(0) + self.freqstart_sld.grid(row=2,column =3, sticky=tk.E+tk.W) + self.freqzoom_lbl = tk.Label(self.analysisblock, text="Frequency window") + self.freqzoom_lbl.grid(row=1,column =4, sticky=tk.E+tk.W) + self.freqzoom_sld = tk.Scale(self.analysisblock , from_=0, to=96000, resolution=1000, orient=tk.HORIZONTAL, command=self.setfreqzoom) + self.freqzoom_sld.set(96000) + self.freqzoom_sld.grid(row=2,column =4, sticky=tk.E+tk.W) + self.imagedraw_btn = tk.Button(self.analysisblock, text="Update Plot", command=self.replot_image) + self.imagedraw_btn.grid(column=5, row=1, rowspan=2,sticky=tk.E+tk.W+tk.N+tk.S) self.thresholdslider = tk.Scale(self.analysisblock,from_=0.0, to=1.0, - resolution=0.05, orient=tk.HORIZONTAL, command=self.setthreshold) + resolution=0.01, orient=tk.HORIZONTAL, command=self.setthreshold) self.thresholdslider.set(0.8) - self.thresholdslider.grid(row=1, column=1, sticky=tk.E+tk.W) - + self.thresholdslider.grid(row=2, column=0, sticky=tk.E+tk.W) + for n in range(5): + self.analysisblock.columnconfigure(n,weight=1) self.imageframe=tk.Frame(self,bg='#fff') # self.imageframe.grid(sticky=tk.N+tk.S+tk.W+tk.E) - self.imageframe.pack(fill="both", expand=True) + self.imageframe.pack(fill="both", expand=True, weight=2) + self.imageframe.bind('', self.resize_image) self.sview=tk.Canvas(self.imageframe, bg='#f00', height=400, width=800) # self.sview.grid(sticky=tk.N+tk.S+tk.W+tk.E)# self.sview.pack(fill="both", expand=True) + + def replot_image(self): + self.resize_image(1) + + def settimestart (self, value): + ''' + Sets the start of the time window. + + Parameters + ---------- + value : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + if hasattr(self, 'wavfilename'): + width=float(self.timezoom_sld.get()) + value =min(float(value), self.limits[1] - width) + self.plotlimits[0]=value + self.plotlimits[1]=value+width + #self.resize_image(1) + + + def settimezoom (self, value): + ''' + Sets the size of the time window. + + Parameters + ---------- + value : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + value=float(value) + if hasattr(self, 'wavfilename'): + self.plotlimits[0] = min(self.plotlimits[0], self.limits[1]-value) + self.plotlimits[1]=self.plotlimits[0]+value + self.timestart_sld.config(to=self.limits[1]-value) + if self.plotlimits[0] != self.timestart_sld.get(): + self.timestart_sld.set(self.plotlimits[0]) + #self.resize_image(1) + + def setfreqstart (self, value): + ''' + Sets the start of the frequency window. + + Parameters + ---------- + value : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + if hasattr(self, 'wavfilename'): + width=int(self.freqzoom_sld.get()) + value =min(float(value), self.limits[3] - width) + self.plotlimits[2]=value + self.plotlimits[3]=value+width + #self.resize_image(1) + + def setfreqzoom (self, value): + ''' + Sets the size of the frequency window. + + Parameters + ---------- + value : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + value=int(value) + if hasattr(self, 'wavfilename'): + self.plotlimits[2] = min(self.plotlimits[2], self.limits[3]-value) + self.plotlimits[3]=self.plotlimits[1]+value + self.freqstart_sld.config(to=self.limits[3]-value) + if self.plotlimits[2] != self.freqstart_sld.get(): + self.freqstart_sld.set(self.plotlimits[2]) + #self.resize_image(1) def setthreshold(self,evt): self.analysisthreshold.config(text="Threshold: {}".format(evt)) @@ -148,7 +267,8 @@ def set_AnalysisFolder(self): self.audiofiles[file[:-14]]['analysis'] = file count += 1 print(self.analysisdirectory) - print("{} anlaysis files found and linked".format(count)) + self.lbl_temp.config(text='{} audio files found. {} analysis files found and linked.'.format(len(self.audiofiles),count)) + print("{} analysis files found and linked".format(count)) def set_SpectrogramSettings(): ''' @@ -159,6 +279,24 @@ def set_SpectrogramSettings(): None. ''' + def resize_image(self,event): + ''' + Method bound to the event of the outer frame + + Returns + ------- + None. + + ''' + if hasattr(self, 'wavfilename'): + self.sview.delete('all') + self.draw_spectrogram() + self.clear_analysis() + self.plot_analysis() + dims = self.lbl_comment.winfo_width() + self.lbl_comment.config(wraplength=int(dims*0.9)) + self.lbl_comment.grid() + def loadSpectrogram(self, fileroot): ''' Uses a load dialogue to get an audio file, check if spectrogram exists and create if not. @@ -180,7 +318,7 @@ def loadSpectrogram(self, fileroot): self.load_analysis(fileroot) else: self.observations=None - + self.plot_analysis() def loadaudiofile(self, filename): @@ -200,8 +338,15 @@ def loadaudiofile(self, filename): self.wavfilename = filename self.wavfile = wave.open(filename) self.audioparams=self.wavfile.getparams() - self.limits=(0, self.audioparams.nframes/self.audioparams.framerate, 0, self.audioparams.framerate/2) + self.limits=[0, self.audioparams.nframes/self.audioparams.framerate, 0, self.audioparams.framerate/2] self.plotlimits = self.limits[:] + self.timestart_sld.set(0) + self.timezoom_sld.config(to=self.limits[1]) + self.timezoom_sld.set(self.limits[1]) + self.freqstart_sld.set(0) + self.freqzoom_sld.config(to=self.limits[3]) + self.freqzoom_sld.set(self.limits[3]) + data = self.wavfile.readframes(self.audioparams.nframes) self.audio = struct.unpack('{}h'.format(self.audioparams.nframes),data) fh = open(self.wavfilename, 'rb') @@ -234,7 +379,7 @@ def displayaudioinfo(self): self.lbl_params = tk.Label(self.audioframe, text=paramtxt) self.lbl_params.grid(sticky=tk.E+tk.W, column=0, row=1) - self.lbl_comment = tk.Label(self.audioframe, text=self.audiocomment, wraplength=500, justify=tk.LEFT) + self.lbl_comment = tk.Label(self.audioframe, text=self.audiocomment, justify=tk.LEFT) self.lbl_comment.grid(sticky=tk.E+tk.W, column=0, row=2) def draw_spectrogram(self): @@ -279,7 +424,9 @@ def clear_analysis(self): None. ''' + print('clearing analysis') self.sview.delete('analysis') + self.markers=[] self.sview.pack() def load_analysis(self, fileroot): @@ -309,12 +456,15 @@ def load_analysis(self, fileroot): def setThresholdSummary(self): if self.observations is not None: self.analysissummary.config(text='{} bat calls detected ({} above threshold)'.format(len(self.observations), - len([x for x in self.observations if x['score']>=self.thresholdslider.get()]))) + len([x for x in self.observations if x['score']>=self.thresholdslider.get()]))) + self.clear_analysis() + self.plot_analysis() else: if hasattr(self, 'wavfilename'): self.analysissummary.config(text='No analysis available for {}'.format(self.wavfilename)) else: self.analysissummary.config(text='No soundfile loaded') + def plot_analysis(self): ''' Plot the observed calls as boxes for those that are above the threshold @@ -324,6 +474,57 @@ def plot_analysis(self): None. ''' + + # work through all calls to determine if they are plottable. + # range of data is in self.limits + # area plottable is in self.imbounds (x0,y0,x1,y1) + # virtual plot is in self.fullrange + if not hasattr(self, 'observations'): + return + #example to show boundaries + px = int(self.imbounds[0])+5 #harddoded the offset for the image sizeas it is 10px smaller than the canvas. need to get the padding + py = int(self.imbounds[1])+5 + pw = int(self.imbounds[2]-self.imbounds[0]) + ph = int(self.imbounds[3]-self.imbounds[1]) + print(px,py,pw,ph) + minx = self.limits[0] + maxx = self.limits[1] + print('drawing analysis') + #self.sview.create_rectangle(px,py, px+100, py+100, outline="#ff0" ) + #self.sview.pack() + fw = int(self.fullrange[2] -self.fullrange[0]) + fx = int(self.fullrange[0]) + foffset = int(self.imbounds[0] - self.fullrange[0]) + scale = fw/pw + + pb = py+ph -10 + + #for any point x coordinate is foffset + scale*(x-minx)/(maxx-minx) + for o in self.observations: + col = '#f00' + if o['score'] px and xm Date: Sun, 14 Jun 2020 15:41:21 +0100 Subject: [PATCH 8/8] set colours and plot labels. --- bat_view/batviewer.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/bat_view/batviewer.py b/bat_view/batviewer.py index 0b068f7..28e7107 100644 --- a/bat_view/batviewer.py +++ b/bat_view/batviewer.py @@ -20,6 +20,7 @@ def __init__(self, master=None): self.observations=None self.createWidgets() self.redrawing=0 + self.colormap =plt.cm.seismic def createWidgets(self): menu = tk.Menu(self.master) @@ -42,7 +43,7 @@ def createWidgets(self): #self.loadButton.grid(column=1,row=0) #self.menubar.grid() self.audioframe=tk.Frame(self,bg='#fff') - self.audioframe.pack(fill="x", expand=True, side='top') + self.audioframe.pack(fill="x", side='top') # self.audioframe.grid(sticky=tk.W+tk.E) self.audiofiles_lb = tk.Listbox(self.audioframe) self.audiofiles_lb.grid(row = 0, column=1, rowspan=4, sticky=tk.N+tk.S) @@ -85,6 +86,17 @@ def createWidgets(self): self.freqzoom_sld.grid(row=2,column =4, sticky=tk.E+tk.W) self.imagedraw_btn = tk.Button(self.analysisblock, text="Update Plot", command=self.replot_image) self.imagedraw_btn.grid(column=5, row=1, rowspan=2,sticky=tk.E+tk.W+tk.N+tk.S) + self.colormap_lbl = tk.Label(self.analysisblock, text="Spectrum colours") + self.colormap_lbl.grid(columnspan=2, column=6, row=1) + self.colormapchooser = tk.Listbox(self.analysisblock, height=1) + for m in dir(plt.cm): + if type(getattr(plt.cm,m)).__name__.endswith('Colormap'): + self.colormapchooser.insert(tk.END, m) + self.colormap_sb = tk.Scrollbar(self.analysisblock ) + self.colormapchooser.config(yscrollcommand = self.colormap_sb.set) + self.colormap_sb.config(command=self.colormapchooser.yview) + self.colormapchooser.grid(row=2,column=6) + self.colormap_sb.grid(row=2,column=7) self.thresholdslider = tk.Scale(self.analysisblock,from_=0.0, to=1.0, resolution=0.01, orient=tk.HORIZONTAL, command=self.setthreshold) self.thresholdslider.set(0.8) @@ -94,13 +106,15 @@ def createWidgets(self): self.imageframe=tk.Frame(self,bg='#fff') # self.imageframe.grid(sticky=tk.N+tk.S+tk.W+tk.E) - self.imageframe.pack(fill="both", expand=True, weight=2) + self.imageframe.pack(fill="both", expand=True) self.imageframe.bind('', self.resize_image) self.sview=tk.Canvas(self.imageframe, bg='#f00', height=400, width=800) # self.sview.grid(sticky=tk.N+tk.S+tk.W+tk.E)# self.sview.pack(fill="both", expand=True) def replot_image(self): + cm=self.colormapchooser.get(self.colormapchooser.curselection()) + self.colormap = getattr(plt.cm, cm) self.resize_image(1) def settimestart (self, value): @@ -147,7 +161,21 @@ def settimezoom (self, value): if self.plotlimits[0] != self.timestart_sld.get(): self.timestart_sld.set(self.plotlimits[0]) #self.resize_image(1) - + def setcolormap(self, value): + ''' + Set the colormap for the spectrogram + + Parameters + ---------- + value : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + self.colormap = getattr(plt.cm, value) def setfreqstart (self, value): ''' Sets the start of the frequency window. @@ -393,12 +421,14 @@ def draw_spectrogram(self): txt=self.sview.create_text(dims[0]/2, dims[1]/2, text='Generating Spectrogram. Please wait ... ') self.sview.pack() - p,freqs,bins,im = ax.specgram(self.audio, NFFT=1024, Fs=self.audioparams.framerate, noverlap=512) + p,freqs,bins,im = ax.specgram(self.audio, NFFT=1024, Fs=self.audioparams.framerate, cmap=self.colormap,noverlap=512) print('foobar') self.imbounds=im.get_window_extent().extents ax.set_xlim(self.plotlimits[0],self.plotlimits[1]) ax.set_ylim(self.plotlimits[2],self.plotlimits[3]) + ax.set_ylabel('Frequency (Hz)') + ax.set_xlabel('Time (seconds)') self.fullrange=im.get_window_extent().extents buf = io.BytesIO() print('foobar2')