Implementing chunked matching for scaleability

This commit is contained in:
2016-03-23 16:04:40 +00:00
parent 6573c8a12f
commit 4d2d4788f7
3 changed files with 113 additions and 15 deletions
+5 -4
View File
@@ -675,7 +675,7 @@ class AudioFile(object):
self.__enter__()
self.pysndfile_object.seek(seek)
def generate_grain_times(self, grain_length, overlap):
def generate_grain_times(self, grain_length, overlap, save_times=False):
"""
Generates an array of start and finish pairs based on overlapping
frames at the grain length specified.
@@ -696,8 +696,9 @@ class AudioFile(object):
times = np.hstack((times, times)).astype(np.dtype('float64'))
times *= hop_size
times[:, 1] += grain_length
# Save grain times as a member variable for later refference.
self.times = times
if save_times:
# Save grain times as a member variable for later refference.
self.times = times
return times
def __getitem__(self, key):
@@ -708,7 +709,7 @@ class AudioFile(object):
raise IndexError("AudioFile object grain times must be generated "
"before grains can be accesed by index. Try running "
"AnalysedAudioFile.generate_grain_times(grain_size, "
"overlap)")
"overlap, save_times=True)")
grain_times = self.times[key].copy()
grain_times *= (self.samplerate / 1000)
return self.read_grain(start_index=grain_times[0], grain_size=grain_times[1]-grain_times[0])
+28 -3
View File
@@ -76,6 +76,21 @@ def parse_arguments():
'and match data will be stored in the /data directory.'
)
parser.add_argument(
'--src-db',
help="Specifies the directory to create the source database and store analyses "
"in. If not specified then the source directory will be used directly.",
type=str
)
parser.add_argument(
'--tar_db',
help="Specifies the directory to create the target database and store analyses "
"in. If not specified then the target directory will be used directly.",
type=str,
default=''
)
analyses = [
"rms",
"zerox",
@@ -178,7 +193,6 @@ def parse_arguments():
args.verbose -= 1
args.verbose = levels[args.verbose]
pdb.set_trace()
return args
@@ -186,6 +200,15 @@ def main():
# Process commandline arguments
args = parse_arguments()
src_audio_dir = None
if args.src_db != '':
src_audio_dir = args.src_db
tar_audio_dir = None
if args.tar_db != '':
tar_audio_dir = args.tar_db
logger = loggerops.create_logger(
logger_streamlevel=args.verbose,
log_filename=modpath,
@@ -196,7 +219,8 @@ def main():
source_db = AudioDatabase(
args.source,
analysis_list=args.analyse,
config=config
config=config,
db_dir=src_audio_dir
)
source_db.load_database(reanalyse=config.analysis["reanalyse"])
@@ -204,7 +228,8 @@ def main():
target_db = AudioDatabase(
args.target,
analysis_list=args.analyse,
config=config
config=config,
db_dir=tar_audio_dir
)
target_db.load_database(reanalyse=config.analysis["reanalyse"])
+80 -8
View File
@@ -376,11 +376,22 @@ class Matcher:
for tind, target_entry in enumerate(self.target_db.analysed_audio):
# Create an array of grain times for target sample
target_times = target_entry.generate_grain_times(grain_size, overlap)
target_times = target_entry.generate_grain_times(grain_size, overlap, save_times=True)
# Stores an accumulated distance between source and target grains,
# added to by each analysis.
distance_accum = np.zeros((target_times.shape[0], source_sample_indexes[-1][-1]))
# Allocate memory for storing accumulated distances between
# source and target grains
pdb.set_trace()
x_size = target_times.shape[0]
y_size = int(source_sample_indexes[-1][-1])
chunk_size = 8192
self.output_db.data.create_dataset("data_distance", (x_size, y_size), dtype=np.float, chunks=True)
self.output_db.data.create_dataset("distance_accum", (x_size, y_size), dtype=np.float, chunks=True, fillvalue=0)
for analysis in self.matcher_analyses:
self.logger.info("Current analysis: {0}".format(analysis))
analysis_formatting = self.analysis_dict[analysis]
@@ -391,10 +402,8 @@ class Matcher:
# Get data for all target grains for each analysis
target_data, s = target_entry.analysis_data_grains(target_times, analysis, format=analysis_formatting)
# Allocate memory for storing accumulated distances between
# source and target grains
self.data_distance = np.zeros((target_data.shape[0], source_sample_indexes[-1][-1]))
data_max = 0.
for sind, source_entry in enumerate(self.source_db.analysed_audio):
# Get the start and end array indexes allocated for the
@@ -402,22 +411,85 @@ class Matcher:
start_index, end_index = source_sample_indexes[sind]
# Create an array of grain times for source sample
source_times = source_entry.generate_grain_times(grain_size, overlap)
source_times = source_entry.generate_grain_times(grain_size, overlap, save_times=True)
self.logger.info("Matching \"{0}\" for: {1} to {2}".format(analysis, source_entry.name, target_entry.name))
# Get data for all source grains for each analysis
source_data, s = source_entry.analysis_data_grains(source_times, analysis, format=analysis_formatting)
source_entry.close()
# Calculate the euclidean distance between the source and
# source values of each grain and add to array
a = self.distance_calc(target_data, source_data)
self.data_distance[:, start_index:end_index] = a
self.output_db.data["data_distance"][:, int(start_index):int(end_index)] = a
self.output_db.data.flush()
a_max = np.max(a)
if a_max > data_max:
data_max = a_max
# Normalize and weight the distances. A higher weighting gives
# an analysis presedence over others.
self.data_distance *= (1/self.data_distance.max()) * weightings[analysis]
distance_accum += self.data_distance
i = 0
membuff = np.zeros((chunk_size, chunk_size))
membuff2 = np.zeros((chunk_size, chunk_size))
while i < x_size:
j = chunk_size
if i+j > x_size:
j = x_size - i
k = 0
while k < y_size:
l = chunk_size
if k+l > y_size:
l = y_size - k
print(i)
print(k)
self.output_db.data["data_distance"].read_direct(membuff, np.s_[i:i+j, k:k+l], np.s_[0:j, 0:l])
self.output_db.data["distance_accum"].read_direct(membuff2, np.s_[i:i+j, k:k+l], np.s_[0:j, 0:l])
weighted_mem = membuff[0:j, 0:l] * (1/data_max) * weightings[analysis]
self.output_db.data["data_distance"][i:i+j, k:k+l] = weighted_mem
self.output_db.data["distance_accum"][i:i+j, k:k+l] = membuff2[0:j, 0:l] + weighted_mem
k += chunk_size
i += chunk_size
self.logger.info("Calculating the closest {0} overall matches...". format(self.match_quantity))
i = 0
# Allocate memory for storing chunks.
membuff = np.zeros((chunk_size, chunk_size))
# Allocate memory for storing the best matches for each target
# grain
match_indexes = np.empty((x_size, self.match_quantity)).fill(None)
# Allocate memory for storing the match distance of these grains.
match_vals = np.empty((x_size, self.match_quantity)).fill(np.inf)
while i < x_size:
j = chunk_size
if i+j > x_size:
j = x_size - i
k = 0
while k < y_size:
l = chunk_size
if k+l > y_size:
l = y_size - k
print(i)
print(k)
# Read the current chunk to memory
self.output_db.data["distance_accum"].read_direct(membuff, np.s_[i:i+j, k:k+l], np.s_[0:j, 0:l])
# Find all matches from the current chunk that are closer
# than the worst match from previous chunks.
closer_matches = membuff < match_vals.max(axis=1)
chunk_match_inds = np.argsort(membuff, axis=1)
# Sort indexes so that best matches are at the start of the array.
match_indexes = distance_accum.argsort(axis=1)[:, :self.match_quantity]