/*
* Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner,
* Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain,
* Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter,
* Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann,
* Samuel Zweifel
*
* This file is part of Jukefox.
*
* Jukefox is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or any later version. Jukefox is
* distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Jukefox. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.ethz.dcg.jukefox.data.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import ch.ethz.dcg.jukefox.commons.Constants;
import ch.ethz.dcg.jukefox.commons.DataUnavailableException;
import ch.ethz.dcg.jukefox.commons.utils.Log;
import ch.ethz.dcg.jukefox.commons.utils.MathUtils;
import ch.ethz.dcg.jukefox.commons.utils.Pair;
import ch.ethz.dcg.jukefox.commons.utils.RandomProvider;
import ch.ethz.dcg.jukefox.commons.utils.kdtree.AdvancedKdTree;
import ch.ethz.dcg.jukefox.commons.utils.kdtree.KdTreePoint;
import ch.ethz.dcg.jukefox.model.collection.CompleteTag;
import ch.ethz.dcg.jukefox.model.collection.DateTag;
import ch.ethz.dcg.jukefox.model.collection.SongCoords;
import edu.wlu.cs.levy.CG.KDTree;
import edu.wlu.cs.levy.CG.KeySizeException;
public class PreloadedData {
private final static String TAG = PreloadedData.class.getSimpleName();
private Random random;
private AdvancedKdTree<Integer> kdTree;
private ArrayList<Integer> idsOfSongsWithoutCoords;
private ArrayList<Integer> idsOfSongsWithCoords;
private HashMap<Integer, float[]> pcaSongCoords;
private HashMap<Integer, CompleteTag> tags;
private ArrayList<DateTag> sortedDateTags;
public PreloadedData() {
this.random = RandomProvider.getRandom();
}
public int getIdOfRandomSongWithCoords() throws DataUnavailableException {
if (getNumberOfSongsWithCoords() == 0) {
throw new DataUnavailableException("no ids of songs with coordinates available");
}
return getRandomId(idsOfSongsWithCoords);
}
public int getRandomSongId() throws DataUnavailableException {
if (getNumberOfSongs() == 0) {
// If the id-arrays are null (which should never be the case), a
// null pointer exception is thrown. If the arrays are just empty, a
// data unavailable exception is thrown.
throw new DataUnavailableException("no song ids available");
}
int numWithCoords = getNumberOfSongsWithCoords();
int numWithoutCoords = getNumberOfSongsWithoutCoords();
float pWithCoords = (float) numWithCoords / (numWithCoords + numWithoutCoords);
if (random.nextFloat() < pWithCoords) {
return getRandomId(idsOfSongsWithCoords);
}
return getRandomId(idsOfSongsWithoutCoords);
}
/**
* Returns the number of songs in the collection
*
* @return
*/
public int getNumberOfSongs() {
return getNumberOfSongsWithCoords() + getNumberOfSongsWithoutCoords();
}
public void setSongCoords(List<SongCoords> songCoords) {
kdTree = new AdvancedKdTree<Integer>(Constants.DIM);
// first recompute all data structures
idsOfSongsWithCoords = new ArrayList<Integer>(songCoords.size());
for (SongCoords sc : songCoords) {
idsOfSongsWithCoords.add(sc.getId());
try {
kdTree.insert(sc.getCoords(), sc.getId());
} catch (KeySizeException e) {
Log.w(TAG, e);
}
}
}
// public void setSongCoords(AdvancedKdTree<Integer> songKdTree, int[] ids)
// {
// this.kdTree = songKdTree;
// this.ids = ids;
// }
public HashMap<Integer, float[]> getPcaCoords() {
return pcaSongCoords;
}
public void setPcaCoords(HashMap<Integer, float[]> pcaCoords) {
this.pcaSongCoords = pcaCoords;
}
public Collection<CompleteTag> getTags() {
return tags.values();
}
public void setTags(Collection<CompleteTag> tags) {
HashMap<Integer, CompleteTag> tempTags = new HashMap<Integer, CompleteTag>(tags.size());
for (CompleteTag tag : tags) {
tempTags.put(tag.getId(), tag);
// Log.d(TAG, "Put tag with id " + tag.getId());
}
setTags(tempTags);
Log.v(TAG, "set tags in list");
}
public void setTags(HashMap<Integer, CompleteTag> tags) {
this.tags = tags;
Log.v(TAG, "set tags in hashmap");
calculateSortedDateTags();
Log.v(TAG, "sorted date tags calculated.");
}
private void calculateSortedDateTags() {
sortedDateTags = new ArrayList<DateTag>();
for (CompleteTag t : tags.values()) {
DateTag dateTag = DateTag.getDateTag(t);
if (dateTag == null) {
continue;
}
sortedDateTags.add(dateTag);
}
Collections.sort(sortedDateTags, new Comparator<DateTag>() {
@Override
public int compare(DateTag t1, DateTag t2) {
if (t1.getTime() < t2.getTime()) {
return -1;
}
if (t1.getTime() > t2.getTime()) {
return 1;
}
return 0;
}
});
}
public Vector<KdTreePoint<Integer>> getSongsCloseToPosition(float[] position, int number) throws KeySizeException {
return kdTree.getNearestPoints(position, number);
}
/**
* @see KDTree#nearestEuclidean(float[], float)
* @return The song nodes
*/
public Vector<KdTreePoint<Integer>> getSongsAroundPositionEuclidian(float[] position, float distance)
throws KeySizeException {
return kdTree.nearestEuclidean(position, distance);
}
/**
* @see KDTree#nearestHamming(float[], float)
* @return The song nodes
*/
public Vector<KdTreePoint<Integer>> getSongsAroundPositionHamming(float[] position, float distance)
throws KeySizeException {
return kdTree.nearestHamming(position, distance);
}
public CompleteTag getTagById(int id) {
return tags.get(id);
}
public ArrayList<Integer> getAllSongIds() {
ArrayList<Integer> ids = new ArrayList<Integer>(getNumberOfSongs());
ids.addAll(idsOfSongsWithCoords);
ids.addAll(idsOfSongsWithoutCoords);
return ids;
}
/**
* Returns the number of songs for which the collection model has music similarity coordinates
*
* @return
* @throws DataUnavailableException
* if preloaded data is not yet loaded
*/
public int getNumberOfSongsWithCoords() {
return idsOfSongsWithCoords.size();
}
/**
* Returns the number of songs for which the collection model has no music similarity coordinates
*
* @return
*/
public int getNumberOfSongsWithoutCoords() {
return idsOfSongsWithoutCoords.size();
}
private int getRandomId(ArrayList<Integer> ids) {
int idx = random.nextInt(ids.size());
return ids.get(idx);
}
public void setSongCoords(AdvancedKdTree<Integer> songKdTree, ArrayList<Integer> idsWithCoords,
ArrayList<Integer> idsWithoutCoords) {
Log.v(TAG, "set song coords: " + idsWithCoords.size() + ", " + idsWithoutCoords.size());
kdTree = songKdTree;
idsOfSongsWithCoords = idsWithCoords;
idsOfSongsWithoutCoords = idsWithoutCoords;
}
public ArrayList<Integer> getIdsOfSongsWithCoords() {
return idsOfSongsWithCoords;
}
public void setIdsOfSongsWithoutCoords(ArrayList<Integer> idsOfSongsWithoutCoords) {
this.idsOfSongsWithoutCoords = idsOfSongsWithoutCoords;
}
public List<Integer> getIdsOfRandomSongsWithCoords(int numberOfSongs) {
numberOfSongs = Math.min(numberOfSongs, idsOfSongsWithCoords.size());
Log.v(TAG, "number of random songs with coordinates: " + idsOfSongsWithCoords.size());
ArrayList<Integer> ids = new ArrayList<Integer>(numberOfSongs);
ArrayList<Integer> positions = MathUtils.getRandomNumbers(idsOfSongsWithCoords.size(), numberOfSongs, random);
for (Integer pos : positions) {
ids.add(idsOfSongsWithCoords.get(pos));
}
return ids;
}
public ArrayList<DateTag> getSortedDateTags() {
return sortedDateTags;
}
public List<Pair<CompleteTag, Float>> getTagsForCoordinate(float[] coords) {
List<Pair<CompleteTag, Float>> albumTags = new ArrayList<Pair<CompleteTag, Float>>();
for (CompleteTag tag : tags.values()) {
Float prob = MathUtils.dotProduct(coords, tag.getPlsaCoords());
Float mean = tag.getMeanPlsaProb();
Float var = tag.getVariancePlsaProb();
albumTags.add(new Pair<CompleteTag, Float>(tag, (prob - mean) / Float.valueOf((float) Math.sqrt(var))));
}
Collections.sort(albumTags, new Comparator<Pair<CompleteTag, Float>>() {
@Override
public int compare(Pair<CompleteTag, Float> object1, Pair<CompleteTag, Float> object2) {
if (object1.second < object2.second) {
return 1;
} else if (object1.second > object2.second) {
return -1;
} else {
return 0;
}
}
});
return albumTags;
}
}