//----------------------------------------------------------------------------//
// //
// S c o r e R e d u c t i o n //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.score;
import omr.score.PartConnection.Candidate;
import omr.score.PartConnection.Result;
import omr.score.entity.Page;
import omr.score.entity.ScorePart;
import omr.score.entity.ScoreSystem;
import omr.score.entity.SystemPart;
import omr.util.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Class {@code ScoreReduction} is the "reduce" part of a MapReduce
* job for a given score, based on the merge of Audiveris Page
* instances.
* <ol>
* <li>Any Map task processes a score page and produces the related
* XML fragment as its output.</li>
* <li>The Reduce task takes all the XML fragments as input and
* consolidates them in a global Score output.</li></ol>
*
* <p>Typical calling of the feature is as follows:
* <code>
* <pre>
* Map<Integer, String> fragments = ...;
* ScoreReduction reduction = new ScoreReduction(fragments);
* String output = reduction.reduce();
* Map<Integer, Status> statuses = reduction.getStatuses();
* </pre>
* </code>
* </p>
*
* <p><b>Features not yet implemented:</b> <ul>
* <li>Connection of slurs between pages</li>
* <li>In part-list, handling of part-group beside score-part</li>
* </ul></p>
*
* @author Hervé Bitteur
*/
public class ScoreReduction
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(ScoreReduction.class);
//~ Instance fields --------------------------------------------------------
/** Related score. */
private final Score score;
/** Pages to process. */
private final SortedMap<Integer, Page> pages = new TreeMap<>();
/** Global connection of parts. */
private PartConnection connection;
//~ Constructors -----------------------------------------------------------
/**
* Creates a new ScoreReduction object.
*
* @param score the score to process
*/
public ScoreReduction (Score score)
{
this.score = score;
for (TreeNode pn : score.getPages()) {
Page page = (Page) pn;
pages.put(page.getIndex(), page);
}
}
//~ Methods ----------------------------------------------------------------
//--------//
// reduce //
//--------//
/**
* Process a score by merging information from the score pages.
*/
public void reduce ()
{
if (score.getPages().isEmpty()) {
return;
}
/* Connect parts across the pages */
connection = PartConnection.connectScorePages(pages);
// Force the ids of all ScorePart's
numberResults();
// Create score part-list and connect to pages and systems parts
addPartList();
// Debug: List all candidates per result
if (logger.isDebugEnabled()) {
dumpResultMapping();
}
}
//-------------//
// addPartList //
//-------------//
/**
* Build the part-list as the sequence of Result/ScorePart
* instances, and map each of them to a Part.
*/
private void addPartList ()
{
// Map (page) ScorePart -> (score) ScorePart data
List<ScorePart> partList = new ArrayList<>();
for (Result result : connection.getResultMap().keySet()) {
ScorePart scorePart = (ScorePart) result.getUnderlyingObject();
partList.add(scorePart);
}
// Need map: pagePart instance -> set of related systemPart instances
// (Since we only have the reverse link)
Map<ScorePart, List<SystemPart>> page2syst = new LinkedHashMap<>();
for (TreeNode pn : score.getPages()) {
Page page = (Page) pn;
for (TreeNode sn : page.getSystems()) {
ScoreSystem system = (ScoreSystem) sn;
for (TreeNode n : system.getParts()) {
SystemPart systPart = (SystemPart) n;
ScorePart pagePart = systPart.getScorePart();
List<SystemPart> cousins = page2syst.get(pagePart);
if (cousins == null) {
cousins = new ArrayList<>();
page2syst.put(pagePart, cousins);
}
cousins.add(systPart);
}
}
}
// Align each candidate to its related result (System -> Page -> Score)
for (Result result : connection.getResultMap().keySet()) {
ScorePart scorePart = (ScorePart) result.getUnderlyingObject();
int newId = scorePart.getId();
for (Candidate candidate : connection.getResultMap().get(result)) {
ScorePart pagePart = (ScorePart) candidate.getUnderlyingObject();
// Update (page) part id
pagePart.setId(newId);
// Update all related (system) part id
for (SystemPart systPart : page2syst.get(pagePart)) {
systPart.setId(newId);
}
}
}
score.setPartList(partList);
}
//-------------------//
// dumpResultMapping //
//-------------------//
/**
* Debug: List details of all candidates per result.
*/
private void dumpResultMapping ()
{
for (Entry<Result, Set<Candidate>> entry : connection.getResultMap().
entrySet()) {
logger.debug("Result: {}", entry.getKey());
for (Candidate candidate : entry.getValue()) {
logger.debug("* candidate: {}", candidate);
}
}
}
//---------------//
// numberResults //
//---------------//
/**
* Force the id of each result (score-part) as 1, 2, ...
*/
private void numberResults ()
{
int partIndex = 0;
for (Result result : connection.getResultMap().keySet()) {
ScorePart scorePart = (ScorePart) result.getUnderlyingObject();
scorePart.setId(++partIndex);
}
}
}