
    3j                       d dl mZ d dlZd dlZd dlZd dlZd dlZd dlmZ d dlm	Z	 ej                  j                  e      Zeej                  z   dz   Zeej                  z   dz   Zeej                  z   dz   Zeej                  z   dz   Zd	Z ej(                  d
g d      Z ej(                  dg d      Z G d d      Z G d d      Z G d d      Z G d d      Zedk(  rd dlZ ej8                          yy)    )annotationsN)note)streamzk525OMRMvt1.xmlzk525GTMvt1.xmlzk525OMRshort.xmlzk525GTshort.xmlFMeasureRelationship)flaggedMeasurePartflaggedMeasureIndexcorrectMeasurePartcorrectMeasureIndexcorrectionProbabilityPriorsIntegrationScore)total
horizontalverticalignoredc                  l    e Zd ZdZddZd Zd Zd Zd Zd Z	d	 Z
d
 Zd Zd Zd Zd Zd Zd Zd Zy)ScoreCorrectorzO
    takes in a music21.stream.Score object and runs OMR correction on it.
    Nc                    || _         g | _        g | _        d | _        t	        t        |j                              D ],  }| j                  j                  | j                  |             . y N)	scoresinglePartsmeasureSlicesdistributionArrayrangelenpartsappendgetSinglePart)selfr   ps      C/DATA/.local/lib/python3.12/site-packages/music21/omr/correctors.py__init__zScoreCorrector.__init__/   sX    
!%s5;;'(A##D$6$6q$9: )    c                "    | j                         S )zO
        Run all known models for OMR correction on
        this score
        )runPriorModelr   s    r    runzScoreCorrector.run8   s    
 !!##r"   c                ~    | j                         }| j                         }| j                  ||       | j                  S )z}
        run the horizontal and vertical correction models
        on the score.  Returns the new self.score object.
        )runHorizontalCorrectionModelrunVerticalCorrectionModelgenerateCorrectedScorer   )r   !correctingArrayHorizontalAllPartscorrectingArrayVerticalAllPartss      r    r$   zScoreCorrector.runPriorModel?   sA    
 -1,M,M,O)*.*I*I*K'##$E$C	Ezzr"   c                b    g }| j                   D ]  }|j                  |j                          |S )a,  
        Returns an array of arrays, each of which is the hashed notes for a part

        >>> p1 = stream.Part()
        >>> p1.insert(0, meter.TimeSignature('4/4'))
        >>> p1.append(note.Note('C', type = 'half'))
        >>> p1.append(note.Rest(type='half'))
        >>> p1.append(note.Note('C', type = 'half'))
        >>> p1.append(note.Rest(type='half'))
        >>> p1.makeMeasures(inPlace=True)
        >>> p2 = stream.Part()
        >>> p2.insert(0, meter.TimeSignature('4/4'))
        >>> p2.repeatAppend(note.Note('C', type='quarter'), 8)
        >>> p2.makeMeasures(inPlace=True)
        >>> s = stream.Score()
        >>> s.insert(0, p1)
        >>> s.insert(0, p2)
        >>> ss = omr.correctors.ScoreCorrector(s)
        >>> ss.getAllHashes()
        [['Z[', 'Z['], ['PPPP', 'PPPP']]
        )r   r   hashedNotes)r   allPartsHashesr   s      r    getAllHasheszScoreCorrector.getAllHashesJ   s1    , !!A!!!--0 "r"   c                H    t        | j                  j                  |   |      S )zT
        returns a NEW SinglePart object for part number pn from the score

        )
SinglePartr   r   )r   pns     r    r   zScoreCorrector.getSingleParte   s      $****2.33r"   c                n    g }| j                   D ]#  }|j                         }|j                  |       % |S )z
        runs for sp in self.singleParts:
            sp.runHorizontalCorrectionModel()

        returns correctingArrayAllParts
        )r   r(   r   )r   correctingArrayAllPartsspcorrectingArrayOneParts       r    r(   z+ScoreCorrector.runHorizontalCorrectionModelm   s@     #%""B%'%D%D%F"#**+AB # '&r"   c           	        	 | j                   |   }|dk(  rt        d| d      	 |S # t        $ r t        | |      }|t        | j                         k\  rB| j                   j	                  d t        t        | j                         |dz         D               || j                   |<   | j                         }||_        Y |S w xY w)aV  
        Given an index, i, returns a MeasureSlice object at that index

        >>> omrPath = omr.correctors.K525omrShortPath
        >>> omrScore = converter.parse(omrPath)
        >>> ssOMR = omr.correctors.ScoreCorrector(omrScore)
        >>> ssOMR.getMeasureSlice(4)
        <music21.omr.correctors.MeasureSlice object at 0x...>
        r   zMeasure slice z out of rangec              3      K   | ]  }d   yw)r   N ).0_s     r    	<genexpr>z1ScoreCorrector.getMeasureSlice.<locals>.<genexpr>   s     )[5Z!5Zs      )r   
IndexErrorMeasureSlicer   extendr   verticalProbabilityDistallProbabilities)r   imsvpds       r    getMeasureSlicezScoreCorrector.getMeasureSlicez   s    
	&##A&BQw >!M!BCC  	  	&dA&BC**++"")))[U3t?Q?Q;RTUXYTY5Z)[[$&Dq!..0C"%B		&s   #( BC Cc                    g }t        t        | j                              D ],  }| j                  |   j                  }|j	                  |       . |S )aM  
        Returns an array of the incorrect measure indices arrays for each part.
        This is used in the MeasureSlice object to make sure we're not comparing a flagged
        measure to other flagged measures in its slice

        >>> omrPath = omr.correctors.K525omrShortPath
        >>> omrScore = converter.parse(omrPath)
        >>> ssOMR = omr.correctors.ScoreCorrector(omrScore)
        >>> ssOMR
        <music21.omr.correctors.ScoreCorrector object at 0x...>
        >>> ssOMR.getAllIncorrectMeasures()
        [[1, 3, 9, 10, 12, 17, 20], [2, 12, 14, 17], [1, 9], []]
        )r   r   r   incorrectMeasuresr   )r   allPartsIncorrectMeasuresr   ims       r    getAllIncorrectMeasuresz&ScoreCorrector.getAllIncorrectMeasures   sP     %'!s4++,-A!!!$66B%,,R0 . )(r"   c                    | j                   | j                   S g }t        | j                        }t        |      D ]"  }|j	                  | j                  |             $ || _         |S )z
        Uses a score and returns an array of probabilities.
        For n in the array, n is the probability that the nth part

        )r   r   r   r   r   ,getVerticalProbabilityDistributionSinglePart)r   r   numberOfPartsrD   s       r    rB   z&ScoreCorrector.verticalProbabilityDist   si     !!-)))D,,-}%A$$T%V%VWX%YZ &!2  r"   c                6   |}t        | j                        }dg|z  }t        | j                  |   j                        }t        |      D ]4  }| j	                  ||      }t        |      D ]  }||xx   ||   z  cc<    6 |D 	cg c]  }	|	|z  	 }
}	|
S c c}	w )a  
        Returns the Vertical Probability Distribution (PrP) for a single part.

        Get the Priors for the Violin II part (first 20 measures only)

        >>> omrPath = omr.correctors.K525omrShortPath
        >>> omrScore = converter.parse(omrPath)
        >>> ssOMR = omr.correctors.ScoreCorrector(omrScore)
        >>> allDists = ssOMR.getVerticalProbabilityDistributionSinglePart(1)
        >>> [f'{p:0.3f}' for p in allDists]
        ['0.571', '1.000', '0.667', '0.714']
        r   )r   r   r.   r   9getVerticalProbabilityDistributionSinglePartSingleMeasure)r   r3   rD   rO   partDistArraylengthOfScorekmeasureDistArraypartCounterxnormalizedPartDistArrays              r    rN   z;ScoreCorrector.getVerticalProbabilityDistributionSinglePart   s     D,,-m+D,,Q/;;<}%A#]]^_abc$]3k*.>{.KK*  4 & ?L"Lm1}#4m"L&& #Ms   Bc                   |}|}t        | j                        }t        | j                  |   j                  |         }dg|z  }|j	                  | j                  |   j
                  |          t        |      D ]H  }||k(  rd||<   |j                  | j                  |   j
                  |         }	|	dk(  rd||<   Dd||<   J |S )N              ?)r   r   MeasureHashmeasureStreamsetSequenceMatcherr.   r   getMeasureDifference)
r   r3   measureIndexrD   rT   rO   mhrU   partNummeasureDifferences
             r    rQ   zHScoreCorrector.getVerticalProbabilityDistributionSinglePartSingleMeasure   s    D,,-))!,::1=>5=0
d..q1==a@A]+G!|,/ ) %'$;$;$$W-99!<%! %+03$W-03$W- ,  r"   c                J    | j                  |      }|j                  |      }|S )z
        Returns an array of the minimum distance measure indices
        given a measure (with index i) within a part pn to compare to
        )rG   runSliceSearch)r   rD   r3   rE   correctingMeasures        r    runVerticalSearchz ScoreCorrector.runVerticalSearch   s+    
 !!!$--b1  r"   c                `   | j                   |   j                  |   }| j                   |   j                  |   }|j                  t        j                        D cg c]  }|j
                   }}|j                  D ]  }	|j                  |	        d}
|D ]  }	t        j                  |	      }	 t        |t        j                        r@||
   }|j                  |j
                  _        |j                  |j
                  _        |
dz  }
|j                  |        yc c}w # t        $ r Y $w xY w)a  
        Takes a destination measure, deletes its contents, and replaces them
        with the contents of a source measure but retains as many pitches as possible

        The destination measure would normally be in the set F of flagged measures
        (having an incorrect number of beats)
        while the source measure is in the set C of correcting measures.

        >>> s = corpus.parse('bwv66.6').measures(1, 2)
        >>> s.show('text')
        {0.0} <music21.stream.Part Soprano>
            ...
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note A>
                {1.0} <music21.note.Note B>
                {2.0} <music21.note.Note C#>
                {3.0} <music21.note.Note E>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note C#>
                {1.0} <music21.note.Note B>
                {2.0} <music21.note.Note A>
                {3.0} <music21.note.Note C#>
        {0.0} <music21.stream.Part Alto>
             ...
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note F#>
                {1.0} <music21.note.Note E>
                {2.0} <music21.note.Note E>
                {3.0} <music21.note.Note E>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note E>
                {0.5} <music21.note.Note A>
                {1.0} <music21.note.Note G#>
                {2.0} <music21.note.Note E>
                {3.0} <music21.note.Note G#>
        ...

        Replace part 1, measure 2 (index 1) with part 0, measure 1 (index 0) while retaining
        as many pitches as possible. The eighth-notes will become quarters:

        >>> scOMR = omr.correctors.ScoreCorrector(s)
        >>> scOMR.substituteOneMeasureContentsForAnother(0, 0, 1, 1)
        >>> s2 = scOMR.score
        >>> s2.show('text')
        {0.0} <music21.stream.Part Soprano>
            ...
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note A>
                {1.0} <music21.note.Note B>
                {2.0} <music21.note.Note C#>
                {3.0} <music21.note.Note E>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note C#>
                {1.0} <music21.note.Note B>
                {2.0} <music21.note.Note A>
                {3.0} <music21.note.Note C#>
        {0.0} <music21.stream.Part Alto>
             ...
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note F#>
                {1.0} <music21.note.Note E>
                {2.0} <music21.note.Note E>
                {3.0} <music21.note.Note E>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note E>
                {1.0} <music21.note.Note A>
                {2.0} <music21.note.Note G#>
                {3.0} <music21.note.Note E>
        ...
        r   r>   N)r   r]   getElementsByClassr   Notepitchelementsremovecopydeepcopy
isinstanceoctavenamer?   r   )r   sourceHorizontalIndexsourceVerticalIndexdestinationHorizontalIndexdestinationVerticalIndexincorrectMeasurecorrectMeasurenoldNotePitchesel
pitchIndexnewEloldPitchs                r    &substituteOneMeasureContentsForAnotherz5ScoreCorrector.substituteOneMeasureContentsForAnother   s   ` 56DDE_` 	 ))*=>LLMbc+;+N+Ntyy+YZ+Ya!''+YZ"++B##B' , 
 BMM"%EeTYY/-j9H)1EKK&'/}}EKK$!OJ ##E* ! [  s   D.AD!!	D-,D-c                J   | j                         }g }t        t        | j                              D ]o  }g }| j                  |   j                  }t        t        |            D ]*  }||   }| j                  ||      }|j                  |       , |j                  |       q |S )z
        Runs a basic vertical correction model on a ScoreCorrector object.
        That is, for each flagged measure, this method replaces the rhythm in that flagged measure
        with the rhythm of a measure with the least difference.
        )rB   r   r   r   rI   rg   r   )	r   unused_allProbabilitiescorrectingMeasuresAllPartsr   correctingMeasuresOnePartrK   rD   incorrectMeasureIndexrf   s	            r    r)   z)ScoreCorrector.runVerticalCorrectionModelO  s     #'">">"@%'"s4++,-A(*%!!!$66B3r7^(*1%$($:$:;PRS$T!)001BC $ '--.GH . *)r"   c           	        d}d}d}d}t        | j                        }t        |      D ]G  }t        t        ||               D ]  }	t        t        ||               D ]  }
||   |	   }||   |
   }|j                  |j                  k7  r-|j                  |j                  k7  rG|j                  }|j                  }|dz  }|j
                  |j
                  kD  r2|dz  }|j                  }|j                  }| j                  ||||       |dz  }|j                  }|j                  }| j                  ||||         | j                  |   j                         | j                  |   _
        J t        ||||      S )a  
        Given two correcting arrays (one from the horizontal model and one from
        the vertical model),
        which offer source measures for each flagged measure in each part,
        this method compares the probabilities of proposed
        source measures for each flagged measure,
        and replaces the flagged measures contents with the more probable source measure
        using substituteOneMeasureContentsForAnother.
        It then rehashes the score so that a new difference comparison can be run.

        Returns a collections.namedtuple of the total number of flagged measures, the total number
        corrected by the horizontal (Prior based on Distance) and the
        vertical (Prior based on Parts)
        methods.
        r   r>   )r   r   r   r   r   r   r
   r	   r   "getSequenceHashesFromMeasureStreamr.   r   )r   horizontalArrayverticalArraytotalFlaggedtotalHorizontaltotalVerticaltotalIgnorednumPartsr   hvhorizontalTupleverticalTupleru   rv   rs   rt   s                    r    r*   z%ScoreCorrector.generateCorrectedScorea  s     t''(xA3q123s=#345A&5a&8&;O$1!$4Q$7M&99]=]=]] &::m>_>__ 1@1T1T./>/Q/Q, A%L '<<}?b?bb'1,0?0S0S-.=.P.P+CC13F68PR &*0=0Q0Q-.;.N.N+CC13F68PR; 6 4F   #FFH Q+G !J &lO]T`aar"   r   )__name__
__module____qualname____doc__r!   r&   r$   r0   r   r(   rG   rL   rB   rN   rQ   rg   r   r)   r*   r:   r"   r    r   r   +   sU    ;$	64'.)*!'0 *!b+H*$;br"   r   c                  B    e Zd Zd
dZd ZddZd ZddZd Zd Z	d	 Z
y)r2   Nc                    || _         || _        d | _        d | _        d | _        |B| j                         | _        | j                         | _        | j                  d      | _
        y d | _        d | _        d | _
        y )NT)runFast)	scorePart
partNumber
indexArrayprobabilityDistributionrf   getMeasuresr]   r   r.   getIncorrectMeasureIndicesrI   )r   partr3   s      r    r!   zSinglePart.__init__  s}    '+$!%!%!1!1!3D#FFHD%)%D%DT%D%RD"!%D#D%)D"r"   c                v    | j                   j                  t        j                        | _        | j                  S r   )r   ri   r   Measurer]   r%   s    r    r   zSinglePart.getMeasures  s)    !^^>>v~~N!!!r"   c                   ddl m} g | _        |du rM	 | j                  d   }|j                  xs |j                  |j                        }|#|j                  d      }n|j                  d      }t        t        | j                              D ]  }|du r8| j                  |   }|j                  xs |j                  |j                        }|j                  j                  }| j                  |   j                  j                  |k(  r|| j                  j                  |        | j                  S # t        $ r |j                  d      }Y w xY w)a  
        Returns an array of all the measures that OMR software would flag - that is,
        measures that do
        not have the correct number of beats given the current time signature

        if runFast is True (by default), assumes that the initial TimeSignature
        is the TimeSignature for the entire piece.

        >>> p = stream.Part()
        >>> ts = meter.TimeSignature('6/8')
        >>> m1 = stream.Measure()
        >>> m1.number = 1
        >>> m1.append(ts)
        >>> m1.append(note.Note('C4', quarterLength = 3.0))
        >>> p.append(m1)
        >>> m2 = stream.Measure()
        >>> m2.number = 2
        >>> m2.append(note.Note('C4', quarterLength = 1.5))
        >>> p.append(m2)

        >>> sp = omr.correctors.SinglePart(p, pn = 0)
        >>> sp.getIncorrectMeasureIndices()
        [1]

        >>> p[1]
        <music21.stream.Measure 2 offset=3.0>
        >>> p[1].insert(0, meter.TimeSignature('3/8'))
        >>> sp.getIncorrectMeasureIndices(runFast=False)
        []

        r   )meterTz4/4F)music21r   rI   r]   timeSignaturegetContextByClassTimeSignaturer?   r   r   barDurationquarterLengthdurationr   )r   r   r   mtsrD   tsOmrs          r    r   z%SinglePart.getIncorrectMeasureIndices  s7   B 	"!#d?0&&q)__P(;(;E<O<O(P z((/$$U+Bs4--./A%&&q)__P(;(;E<O<O(PNN00E!!!$--;;uD&&--a0 0 %%%%  0((/0s   8D3 3EEc                    g }| j                   j                  t        j                        }t	        t        |            D ]1  }t        ||         }|j                         }|j                  |       3 |S )z`
        takes in a measure stream of a part
        returns an array of hashed strings
        )	r]   ri   r   r   r   r   r\   getHashStringr   )r   measureStreamNotesmeasureStreamMeasuresrD   ra   myHashedNotess         r    r   z-SinglePart.getSequenceHashesFromMeasureStream  sn    
   $ 2 2 E Efnn Us012A2156B,,.M%%m4 3
 "!r"   c                   |du r| j                   | j                   S t        | j                        dz  }dg|z  }dg|z  }t        t        | j                              D ]  }t	        | j
                  |         }|j                  | j                  |          g }t        t        | j                              D ]  }t        | j                        ||z
  z
  }	||z
   ||	<   ||k(  r*|j                  d       t        | j                        ||	<   V|j                  | j                  |         }
|
dk(  r|j                  d       ||	xx   dz  cc<   |j                  d       ||	xx   dz  cc<     |j                  d       |D cg c]  }|t        | j                        z   }}|j                  d       || _         || _
        | j                   S c c}w )a  
        Uses (takes?) an array of hashed measures and returns an array of probabilities.
        For n in the array, n is the probability that the measure (n-(length of score)) away
        from a flagged measure will offer a rhythmic solution.

        These are the probabilities that, within a part, a measure offers a solution, given its
        distance from a flagged measure.
        F   r   d   r[   rZ   )r   r   r.   r   r\   r]   r^   r   r_   popr   )r   
regeneratesizeOfArrayallDistArrayr   rD   ra   	distArrayrT   
arrayIndexrc   rW   normalizedDistArrays                r    horizontalProbabilityDistz$SinglePart.horizontalProbabilityDist   s    4#?#?#K///$**+a/s[(S;&
s4++,-AT//23B!!$"2"21"56I3t//01 !1!12a!e<
+,q5
:&6$$S) 0343C3C/DL,(*(?(?@P@PQR@S(T%(C/!((-$Z0C70!((-$Z0C70 2	 .* 	qBNO,Qq3t'7'7#88,O"':$$+++	 Ps   	Gc                   | j                         }| j                  }||   }| j                  |   }t        | j                  |         }|j                  |       g }t        t        | j                              D ]^  }||v r|j                  d       |j                  | j                  |         }	| j                  ||      }
|	|
z  }|j                  |       ` t        |      }g }t        |      D ]  \  }}||k(  s|j                  |        t        | j                  || j                  |d   |      | _        | j                  S )z
        Returns an array of the indices of the minimum distance measures
        given a measure (with index i) to compare to.

        rZ   r   )r   rI   r.   r\   r]   r^   r   r   r   getProbabilityBasedOnChangesgetProbabilityDistributionmax	enumerater   r   rf   )r   rD   unused_probabilityDistributionrI   r   hashedNotesIra   probabilityArrayrT   priorBasedOnChangesProbabilitypriorBasedOnDistanceProbabilitypriorBasedOnChangesAndDistancemaximumProbabilitymaximumProbabilityMeasures
lineNumberr   s                   r    runHorizontalSearchzSinglePart.runHorizontalSearch*  sj    *.)G)G)I& 22 1! 4''(=>++,ABC
l+s4++,-A%% '', 241P1P$$Q'2).262Q2Q)3+/ 3Q4S3T. ''(FG . !!12 &("&'78MJ&&*11*= 9 "5T__5J59__5OPQ5R5G	"I %%%r"   c                    g }t        t        | j                              D ]$  }| j                  |      }|j	                  |       & |S )z
        Runs a basic horizontal correction model on a score.
        That is, for each flagged measure, this method replaces the rhythm in that flagged measure
        with the rhythm of a measure with the least difference.
        )r   r   rI   r   r   )r   correctingArrayrD   rf   s       r    r(   z'SinglePart.runHorizontalCorrectionModelU  sK     s41123A $ 8 8 ;""#45 4 r"   c                d    | j                   }||z
  t        | j                        z   dz
  }||   }|S )Nr>   )r   r   r.   )r   sourceIndexdestinationIndexr   indexdistanceProbabilitys         r    r   z%SinglePart.getProbabilityDistributionb  s?    "&">">//3t7G7G3HH1L5e<""r"   )NN)F)r   r   r   r!   r   r   r   r   r   r(   r   r:   r"   r    r2   r2     s-    *"
:&z"(,T)&V#r"   r2   c                  "    e Zd ZdZd Zd Zd Zy)r@   z4
    represents a single measure from all parts
    c                @   g | _         || _        || _        g | _        d | _        d | _        t        t        | j                  j                              D ]I  }| j                  j                  |   }|j                         }| j                   j                  ||          K y r   )arrayOfMeasureObjectsr   r   sliceMeasureHashObjectsrC   rf   r   r   r   r   r   )r   r   rD   r   r   measuress         r    r!   zMeasureSlice.__init__n  s    %'"

')$ $!%DJJ$:$: ;<J::))*5D'')H&&--hqk: =r"   c                    t        t        | j                              D ]5  }t        | j                  |         }| j                  j                  |       7 | j                  S )a  
        >>> omrPath = omr.correctors.K525omrShortPath
        >>> omrScore = converter.parse(omrPath)
        >>> ssOMR = omr.correctors.ScoreCorrector(omrScore)
        >>> ssOMR
        <music21.omr.correctors.ScoreCorrector object at 0x...>
        >>> measureSlice = ssOMR.getMeasureSlice(2)
        >>> measureSlice
        <music21.omr.correctors.MeasureSlice object at 0x...>
        )r   r   r   r\   r   r   )r   rD   ra   s      r    getSliceHasheszMeasureSlice.getSliceHashes|  sU     s45567AT77:;B((//3 8 +++r"   c                   g }| j                         }| j                  j                         }||   }|j                          t	        t        | j                              D ]  }||k(  r|j                  d       | j                  ||   v r|j                  d       =||   j                         }|j                  |      }| j                  }	|	|   |   }
||
z  }|j                  |        t        |      }g }t        |      D ]  \  }}||k(  s|j                  |        t        || j                  |d   | j                  |      | _        | j                  S )a  
        Takes in an incorrectPartIndex and returns an array
        of the measure indices within the slice that have the
        maximum probability to correct a given flagged measures.

        Returns a namedtuple (MeasureRelationship)

        >>> omrPath = omr.correctors.K525omrShortPath
        >>> omrScore = converter.parse(omrPath)
        >>> ssOMR = omr.correctors.ScoreCorrector(omrScore)
        >>> measureSlice = ssOMR.getMeasureSlice(2)
        >>> measureSlice
        <music21.omr.correctors.MeasureSlice object at 0x...>
        >>> measureSlice.runSliceSearch(1)
        MeasureRelationship(flaggedMeasurePart=1, flaggedMeasureIndex=2,
            correctMeasurePart=3, correctMeasureIndex=2, correctionProbability=0.0054...)

        >>> measureSlice = ssOMR.getMeasureSlice(3)
        >>> measureSlice.runSliceSearch(0)
        MeasureRelationship(flaggedMeasurePart=0,
            flaggedMeasureIndex=3, correctMeasurePart=1, correctMeasureIndex=3,
            correctionProbability=2.41...e-14)
        rZ   r   )r   r   rL   r^   r   r   r   r   r   r   r   rC   r   r   r   rf   )r   incorrectPartIndexr   sliceHashesallIncorrectMeasuresra   rT   
hashStringr   ap'priorBasedOnVerticalDistanceProbabilityr   r   r   r   r   s                   r    re   zMeasureSlice.runSliceSearch  sn   0 ))+#zzAAC+,
s45567A&& '',3A66 '', )^99;
131P1PQ[1\.**:<=O:PQR:S72P4[3\. ''(FG 8" !!12%'"&'78MJ&&*11*= 9 "55G59ZZ5OPQ5R59ZZ5G	"I %%%r"   N)r   r   r   r   r!   r   re   r:   r"   r    r@   r@   i  s    ;,$9&r"   r@   c                  x    e Zd ZdZddZd Zd Zd Zd Zd Z	dd	Z
d
 ZddZd ZddZd Zd Zd Zd Zd Zy)r\   zl
    Able to do a number of matching, substitution and hashing operations on
    a given measure object
    Nc                h    || _         d | _        d | _        | j                   | j                          y y r   )measureObjectr   sequenceMatcherr   )r   r   s     r    r!   zMeasureHash.__init__  s6    *#)  *r"   c                   d}| j                   y| j                   }|j                  du r|j                  }n|j                         }|j                  }|D ]  }|j                  j
                  dk(  r|| j                  |      z  }1|j                  r|| j                  |      z  }R|j                  r|| j                  |      z  }s|j                  s|| j                  |      z  } || _        |S )aV  
        takes a stream and returns a hashed string for searching on
        and stores it in self.hashString

        If a measure object has multiple voices, use the first  voice.

        >>> m = stream.Measure()
        >>> m.append(note.Note('C', quarterLength=1.5))
        >>> m.append(note.Note('C', quarterLength=0.5))
        >>> m.append(note.Rest(quarterLength=1.5))
        >>> m.append(note.Note('B', quarterLength=0.5))

        >>> hasher = omr.correctors.MeasureHash(m)
        >>> hasher.getHashString()
        'VFUF'
        >>> hasher.hashString == 'VFUF'
        True
         TrZ   )r   isFlatnotesAndRestschordifyr   r   	hashGraceisNotehashNoteisResthashRestisChordr   )r   r   mo	subStreamry   s        r    r   zMeasureHash.getHashString  s    ( 
%99!!BI((B Azz''3.dnnQ//
dmmA..
dmmA..
dmmA..
  %r"   c                    | j                  |j                  j                        }d}|dz  dk(  r|dkD  rt        |      }|S |dz  dk(  r|dkD  rt        |dz         }|S |dk  rt        d      }|S t	        d      )a  
        Encodes a note

        >>> hasher = omr.correctors.MeasureHash()

        >>> n = note.Note('C')
        >>> n.duration.type = 'quarter'
        >>> hasher.hashNote(n)
        'P'
        >>> n2 = note.Note('C')
        >>> n2.duration.type = 'half'
        >>> hasher.hashNote(n2)
        'Z'
        >>> n3 = note.Note('C', quarterLength=1.5)
        >>> hasher.hashNote(n3)
        'V'
        r   r   r   r>   zInvalid Byte Encoding)hashQuarterLengthr   r   chr
ValueError)r   ry   duration1to127byteEncodings       r    r   zMeasureHash.hashNote  s    ( //

0H0HIA"~'9~.L  a1$!);~12L  aq6L  455r"   c                >    | j                  d      }t        |      }|S )z@
        Gives a Grace Note a duration of a 128th note

        g      ?)r   r   )r   ry   graceNoteDurationr   s       r    r   zMeasureHash.hashGrace$  s&    
 !228<,-r"   c                    | j                  |j                  j                        }d}|dz  dk(  r|dkD  rt        |dz         }|S |dz  dk(  r|dkD  rt        |      }|S |dk  rt        d      }|S )z
        Encodes a rest

        >>> r = note.Rest(1.0)
        >>> hasher = omr.correctors.MeasureHash()
        >>> hasher.hashRest(r)
        'Q'
        r   r   r   r>   )r   r   r   r   )r   rr   r   s       r    r   zMeasureHash.hashRest-  s     //

0H0HIA"~'9~12L
 	 a1$!);~.L  aq6Lr"   c                    d}|r:t        t        j                  |dz        dz        }t        t	        |d      d      }|S )z
        Turns a QuarterLength duration into an integer from 1 to 127

        >>> hasher = omr.correctors.MeasureHash()
        >>> hasher.hashQuarterLength(1.0)
        80

        >>> hasher.hashQuarterLength(2.0)
        90
        r>      
      )intmathlog2r   min)r   qlr   s      r    r   zMeasureHash.hashQuarterLengthB  sA      28!4r!9:N ^S!91=Nr"   c                    |0| j                   | j                         }|| _         n| j                   }t        j                  d |d      | _        y )Nr   )r   r   difflibSequenceMatcherr   )r   hashess     r    r^   zMeasureHash.setSequenceMatcherS  sF    >&++-"(&66tVRHr"   c                    | j                   j                  |       | j                   j                         }|dk(  rd}d|z
  S )a  
        Returns the difference ratio between two measures
        b is the "correct" measure that we want to replace the flagged measure with

        Takes a hashString

        >>> m = stream.Measure()
        >>> m.append(note.Note('C', quarterLength=1.5))
        >>> m.append(note.Note('C', quarterLength=0.5))
        >>> m.append(note.Rest(quarterLength=1.5))
        >>> m.append(note.Note('B', quarterLength=0.5))

        >>> hasher = omr.correctors.MeasureHash(m)
        >>> hasher.setSequenceMatcher()
        >>> hasher.getMeasureDifference('VGUF')
        0.25

        >>> m = stream.Measure()
        >>> m.append(note.Note('C', quarterLength=1.5))
        >>> m.append(note.Note('C', quarterLength=0.5))
        >>> m.append(note.Rest(quarterLength=1.5))
        >>> m.append(note.Note('B', quarterLength=0.5))

        >>> hasher = omr.correctors.MeasureHash(m)
        >>> hasher.setSequenceMatcher()
        >>> hasher.getMeasureDifference('VFUF')
        1.0

        r[   rZ   r>   )r   set_seq2ratio)r   r   myRatios      r    r_   z MeasureHash.getMeasureDifference]  sB    > 	%%j1&&,,.c>G7{r"   c                    | j                   | j                          || j                   j                  |       | j                   j                         S )a  
        Gets the opcodes from a simple sequenceMatcher for the current measureHash

        Example of Violin II vs. Viola and Cello in K525 I, m. 17

        >>> vlnII = converter.parse('tinynotation: 4/4 e4 e8. e8 c4 c8 c8').flatten().notes.stream()
        >>> viola = converter.parse('tinynotation: 4/4 c4 c8  c8 A4 A8 A8').flatten().notes.stream()
        >>> cello = converter.parse('tinynotation: 4/4 C4 C4     D4 D4   ').flatten().notes.stream()
        >>> vlnII_MH = omr.correctors.MeasureHash(vlnII)
        >>> viola_MH = omr.correctors.MeasureHash(viola)
        >>> cello_MH = omr.correctors.MeasureHash(cello)
        >>> vlnII_MH.getOpCodes(viola_MH.hashString)
        [('equal', 0, 1, 0, 1), ('replace', 1, 2, 1, 2), ('equal', 2, 6, 2, 6)]
        >>> vlnII_MH.getOpCodes(cello_MH.hashString)
        [('equal', 0, 1, 0, 1), ('delete', 1, 3, 1, 1),
         ('equal', 3, 4, 1, 2), ('replace', 4, 6, 2, 4)]
        )r   r^   r  get_opcodes)r   	otherHashs     r    
getOpCodeszMeasureHash.getOpCodes  sK    $ '##%   )))4##//11r"   c                    | j                  |      }d}|D ]0  }| j                  ||      }|j                  |      dk(  r|},||z  }2 |S )a	  
        Takes a hash string and gets the probability based on changes.

        >>> otherHash = 'e'
        >>> hashString = 'GFPGF'
        >>> mh = omr.correctors.MeasureHash()
        >>> mh.hashString = hashString
        >>> mh.getProbabilityBasedOnChanges(otherHash)
        2.9472832125e-14

        Example of Violin II vs. Viola and Cello in K525 I, m. 17

        >>> vlnII = converter.parse('tinynotation: 4/4 e4 e8. e8 c4 c8 c8').flatten().notes.stream()
        >>> viola = converter.parse('tinynotation: 4/4 c4 c8  c8 A4 A8 A8').flatten().notes.stream()
        >>> cello = converter.parse('tinynotation: 4/4 C4 C4     D4 D4   ').flatten().notes.stream()
        >>> vlnII_MH = omr.correctors.MeasureHash(vlnII)
        >>> viola_MH = omr.correctors.MeasureHash(viola)
        >>> cello_MH = omr.correctors.MeasureHash(cello)
        >>> vlnII_MH.getProbabilityBasedOnChanges(viola_MH.hashString)
        0.0076295...
        >>> vlnII_MH.getProbabilityBasedOnChanges(cello_MH.hashString)
        4.077...e-09
        rZ   r   )r  !differenceProbabilityForOneOpCoder   )r   r  opcodesallProbabilityopcodeoneProbabilitys         r    r   z(MeasureHash.getProbabilityBasedOnChanges  s[    4 //),F!CCFIVN}}V$)!/.0  r"   c                   || j                   }|t        d      |d   }|dk(  r|d   |d   z
  }| j                         |z  S |dk(  r(||d   |d    }||d   |d    }| j                  ||      S |d	k(  r|d   |d   z
  }| j	                         |z  S |d
k(  r|d   |d   z
  }	| j                         |	z  S t        d      )aL  
        Given an opCodeTuple and a source, differenceProbabilityForOneOpCode
        returns the difference probability for one type of op-code
        (replace, insert, delete, or equal).
        Here, the destination is in the set F of flagged measures and the
        source is in the set C of correcting measures.
        Source and destination are both hashStrings

        >>> source = 'PFPFFF'
        >>> destination = 'PFPFGF'
        >>> ops = ('equal', 0, 4, 0, 4)
        >>> mh = omr.correctors.MeasureHash()
        >>> mh.differenceProbabilityForOneOpCode(ops, source, destination)
        0.8762013031640626

        Omission

        >>> ops2 = ('insert', 4, 4, 4, 5)
        >>> mh2 = omr.correctors.MeasureHash()
        >>> mh2.differenceProbabilityForOneOpCode(ops2, source, destination)
        0.009

        >>> ops3 = ('replace', 2, 4, 2, 4)
        >>> mh3 = omr.correctors.MeasureHash()
        >>> mh3.differenceProbabilityForOneOpCode(ops3, 'PPPPP', 'PPVZP')
        0.0001485

        Five deletes in a row:

        >>> ops4 = ('delete', 0, 5, 0, 0)
        >>> mh3 = omr.correctors.MeasureHash()
        >>> mh3.differenceProbabilityForOneOpCode(ops4, 'e', 'GFPGF')
        1.024e-12

        Example of Violin II vs. Viola in K525 I, m. 17

        >>> vlnII = converter.parse('tinynotation: 4/4 e4 e8. e8 c4 c8 c8').flatten().notes.stream()
        >>> viola = converter.parse('tinynotation: 4/4 c4 c8  c8 A4 A8 A8').flatten().notes.stream()
        >>> vlnIIMH = omr.correctors.MeasureHash(vlnII)
        >>> violaMH = omr.correctors.MeasureHash(viola)
        >>> vlnIIMH.hashString
        'PLFPFF'
        >>> violaMH.hashString
        'PFFPFF'
        >>> opCodes = vlnIIMH.getOpCodes(violaMH.hashString)
        >>> for oc in opCodes:
        ...     prob = vlnIIMH.differenceProbabilityForOneOpCode(
        ...         oc,
        ...         violaMH.hashString,
        ...     )
        ...     print(f'{oc!r:>30} : {prob:.3f}')
                 ('equal', 0, 1, 0, 1) : 0.968
               ('replace', 1, 2, 1, 2) : 0.009
                 ('equal', 2, 6, 2, 6) : 0.876
        z HashString has not yet been set!r   equal      replacer>   r   insertdeletezIncorrect opCodeType value.)r   r   getProbabilityOnEqualitygetProbabilityOnSubstitutegetProbabilityOnOmissiongetProbabilityOnAddition)
r   opCodeTuplesourcedestination
opCodeTypelengthOfEqualSectionsourceSnippetdestinationSnippetnumberOfOmissionsnumberOfAdditionss
             r    r  z-MeasureHash.differenceProbabilityForOneOpCode  s   r //K" !CDD ^
 #.q>KN#B 1138LLL9$";q>+a.AM!,[^KN!K22=BTUU8# +AQ ?0026GGG8# +AQ ?0026GGG:;;r"   c                     y)z
        Parts or the whole of a string were equal.

        >>> omr.correctors.MeasureHash().getProbabilityOnEquality()
        0.9675
        g(\?r:   r%   s    r    r  z$MeasureHash.getProbabilityOnEquality  s     r"   c                     y)a[  
        In order for the source to be correct,
        the destination omitted a symbol.
        Associated with type 'delete' and in the case of replacement of
        a dotted version of a note with an undotted version (or double dot with dotted, etc.)

        >>> omr.correctors.MeasureHash().getProbabilityOnOmission()
        0.009
        g;On?r:   r%   s    r    r!  z$MeasureHash.getProbabilityOnOmission  s     r"   c                     y)z
        In order for the source to be correct,
        the destination added a symbol
        Associated with type 'insert'

        >>> omr.correctors.MeasureHash().getProbabilityOnAddition()
        0.004
        gMbp?r:   r%   s    r    r"  z$MeasureHash.getProbabilityOnAddition$  s     r"   c                >   t        |      }t        |      }||kD  r!||z
  }| j                         |z  }|dd|z   }n(||k  r!||z
  }| j                         |z  }|dd|z   }nd}t        t        |            D ]!  }||   }	||   }
|| j	                  |	|
      z  }# |S )aR  
        Source and destination are measureHash strings
        Source is in set C of correcting measures.
        Destination is in set F of flagged measures.

        (Rossant & Bloch)

        * value change: 50.77% of all errors (inverse: 0.0197)
        * confusions: 9.23% of all errors (inverse: 0.108)
            Note: these get the most probability, because they are the rarest
        * omission: 27.69% of all errors (inverse: 0.0361)
        * addition: 12.31% of all errors (inverse: 0.08125)

        >>> mh = omr.correctors.MeasureHash()

        Replacement of eighth note (F) for quarter note (P) = shift of one value:

        >>> mh.getProbabilityOnSubstitute('F', 'P')
        0.0165

        Replacement of eighth note (F) for eighth rest (G) = shift of one type:

        >>> mh.getProbabilityOnSubstitute('F', 'G')
        0.003

        Omission of any symbol, less common so costs more
        The proposed correction assumes that the incorrect measure omitted a symbol

        >>> mh.getProbabilityOnSubstitute('', 'P')
        0.009

        Addition of any symbol, less common so costs more
        The proposed correction assumes that the incorrect measure added a symbol

        >>> mh.getProbabilityOnSubstitute('P', '')
        0.004

        Combination of value shift and an addition:

        >>> mh.getProbabilityOnSubstitute('F', 'PP')
        0.0001485

        Take minimum length. Compare index to index. Any additional letters
        in the flagged measure get graded as additions. Any additional letters
        in the comparison measure get graded as omissions.

        r   r[   )r   r"  r!  r   getProbabilityFromOneCharSub)r   r$  r%  lsldr+  baseProbabilityr*  rD   
sourceChardestChars              r    r   z&MeasureHash.getProbabilityOnSubstitute/  s    ` [7 "R";;=ARROAb#445F"W "R";;=ARRO%a->(>?K!Os6{#AJ"1~Ht@@XVVO $
 r"   c                2   t        |      t        |      z
  }t        j                  |      }|dk(  ry|dz  dk(  r
|dz  }d|z  S |dk(  r| j                         S |dk(  r| j	                         S |dz  d	k7  ry
| j	                         | j                         z  S )a  
        Source and destination are strings of one character

        >>> mh = omr.correctors.MeasureHash()

        Eighth note to eighth rest:

        >>> mh.getProbabilityFromOneCharSub('F', 'G')
        0.003

        Eighth note to quarter note:

        >>> mh.getProbabilityFromOneCharSub('F', 'P')
        0.0165

        Eighth note to half note:

        >>> mh.getProbabilityFromOneCharSub('F', 'Z')
        0.0002722...

        Quarter note to dotted quarter note:

        >>> mh.getProbabilityFromOneCharSub('P', 'V')
        0.009

        Dotted quarter note to quarter note:

        >>> mh.getProbabilityFromOneCharSub('V', 'P')
        0.004

        >>> mh.getProbabilityFromOneCharSub('A', 'Y')
        3.6e-05
        rZ   r[   r   g      $@gL7A`?g      @g      r   r   g~jth?)ordr  fabsr"  r!  )r   r$  r%  charDiffabsCharDiffnumberOfShiftss         r    r1  z(MeasureHash.getProbabilityFromOneCharSubr  s    D v;[!11ii)s?2$(4/N^++_002200221_!
 002T5R5R5TTTr"   r   )r   r   r   r   r!   r   r   r   r   r   r^   r_   r  r   r  r  r!  r"  r   r1  r:   r"   r    r\   r\     sa    
!)V D*"I#J22#JM<^
	AF6Ur"   r\   __main__)
__future__r   rn   collectionsr  r  osr   r   r   pathdirname__file__pathNamesepK525omrFilePathK525groundTruthFilePathK525omrShortPathK525groundTruthShortPathdebug
namedtupler   r   r   r2   r@   r\   r   mainTestr:   r"   r    <module>rM     s   #     	  77??8$RVV#&77"RVV+.>> bff$'99 #bff,/@@ ,k,,  0//2 qb qbhG# G#T^& ^&B^U ^UB zG r"   