Exemplo n.º 1
0
    def __init__(self, dataDict=None, hits=None, holds=None):
        """ Initialize a package,

        Can initialize with either overloaded method.

        :param dataDict: The data dictionary, it'll be directly assigned to dataDict. The names must explicitly match
        :param hits: The hits as a OsuHitList
        :param holds: The holds as a OsuHoldList
        """
        if dataDict is not None: self.dataDict = dataDict
        elif hits is not None:   self.dataDict = {'hits': hits, 'holds': holds}
        else: self.dataDict: Dict[str, OsuNoteList] = {'hits': OsuHitList(), 'holds': OsuHoldList()}
Exemplo n.º 2
0
    def convert(sm: SMMapSet) -> List[OsuMap]:
        """ Converts a SMMapset to possibly multiple osu maps

        Note that a mapset contains maps, so a list would be expected.
        SMMap conversion is not possible due to lack of SMMapset Metadata

        :param sm:
        :return:
        """

        # I haven't tested with non 4 keys, so it might explode :(

        osuMapSet: List[OsuMap] = []
        for smMap in sm.maps:
            assert isinstance(smMap, SMMap)

            hits: List[OsuHit] = []
            holds: List[OsuHold] = []

            # Note Conversion
            for hit in smMap.notes.hits():
                hits.append(OsuHit(offset=hit.offset, column=hit.column))
            for hold in smMap.notes.holds():
                holds.append(
                    OsuHold(offset=hold.offset,
                            column=hold.column,
                            _length=hold.length))

            bpms: List[Bpm] = []

            # Timing Point Conversion
            for bpm in smMap.bpms:
                bpms.append(OsuBpm(offset=bpm.offset, bpm=bpm.bpm))

            # Extract Metadata
            osuMap = OsuMap(
                backgroundFileName=sm.background,
                title=sm.title,
                titleUnicode=sm.titleTranslit,
                artist=sm.artist,
                artistUnicode=sm.artistTranslit,
                audioFileName=sm.music,
                creator=sm.credit,
                version=f"{smMap.difficulty} {smMap.difficultyVal}",
                previewTime=int(sm.sampleStart),
                bpms=OsuBpmList(bpms),
                notes=OsuNotePkg(hits=OsuHitList(hits),
                                 holds=OsuHoldList(holds)))
            osuMapSet.append(osuMap)
        return osuMapSet
Exemplo n.º 3
0
    def convert(o2j: O2JMapSet) -> List[OsuMap]:
        """ Converts a Mapset to multiple Osu maps

        Note that a mapset contains maps, so a list would be expected.
        O2JMap conversion is not possible due to lack of O2JMapset Metadata

        :param o2j:
        :return:
        """

        osuMapSet: List[OsuMap] = []
        for o2jMap in o2j.maps:
            assert isinstance(o2jMap, O2JMap)

            hits: List[OsuHit] = []
            holds: List[OsuHold] = []

            # Note Conversion
            for hit in o2jMap.notes.hits():
                hits.append(OsuHit(offset=hit.offset, column=hit.column))
            for hold in o2jMap.notes.holds():
                holds.append(
                    OsuHold(offset=hold.offset,
                            column=hold.column,
                            _length=hold.length))

            bpms: List[Bpm] = []

            # Timing Point Conversion
            for bpm in o2jMap.bpms:
                bpms.append(OsuBpm(offset=bpm.offset, bpm=bpm.bpm))

            # Extract Metadata
            osuMap = OsuMap(
                title=o2j.title,
                artist=o2j.artist,
                creator=o2j.creator,
                version=f"Level {o2j.level[o2j.maps.index(o2jMap)]}",
                bpms=OsuBpmList(bpms),
                circleSize=7,
                notes=OsuNotePkg(hits=OsuHitList(hits),
                                 holds=OsuHoldList(holds)))
            osuMapSet.append(osuMap)
        return osuMapSet
Exemplo n.º 4
0
    def convert(qua: QuaMap) -> OsuMap:
        """ Converts a Quaver map to an osu map

        :param qua:
        :return:
        """

        hits: List[OsuHit] = []
        holds: List[OsuHold] = []

        # Note Conversion
        for hit in qua.notes.hits():
            hits.append(OsuHit(offset=hit.offset, column=hit.column))
        for hold in qua.notes.holds():
            holds.append(OsuHold(offset=hold.offset, column=hold.column, _length=hold.length))

        bpms: List[Bpm] = []
        svs: List[OsuSv] = []
        # Timing Point Conversion
        for bpm in qua.bpms:
            bpms.append(OsuBpm(offset=bpm.offset, bpm=bpm.bpm))

        for sv in qua.svs:
            svs.append(OsuSv(offset=sv.offset, multiplier=sv.multiplier))

        # Extract Metadata
        osuMap = OsuMap(
            backgroundFileName=qua.backgroundFile,
            title=qua.title,
            circleSize=QuaMapMode.getKeys(qua.mode),
            titleUnicode=qua.title,
            artist=qua.artist,
            artistUnicode=qua.artist,
            audioFileName=qua.audioFile,
            creator=qua.creator,
            version=qua.difficultyName,
            previewTime=qua.songPreviewTime,
            bpms=OsuBpmList(bpms),
            svs=OsuSvList(svs),
            notes=OsuNotePkg(hits=OsuHitList(hits),
                             holds=OsuHoldList(holds))
        )

        return osuMap
Exemplo n.º 5
0
def hitSoundCopy(mFrom: OsuMap, mTo: OsuMap, inplace: bool = False) -> OsuMap:
    """ Copies the hitsound from mFrom to mTo
    
    :param inplace: Whether to just modify this instance or return a modified copy
    :param mFrom: The map you want to copy from
    :param mTo: The map you want to copy to, it doesn't mutate this.
    :return: A copy of mTo with the copied hitsounds.
    """
    dfFrom = pd.concat([df for df in mFrom.notes.df().values()], sort=False)
    dfFrom = dfFrom.drop(['column', 'length'], axis='columns', errors='ignore')
    dfFrom = dfFrom[(dfFrom['additionSet'] != 0) | (dfFrom['customSet'] != 0) |
                    (dfFrom['hitsoundSet'] != 0) | (dfFrom['sampleSet'] != 0) |
                    (dfFrom['hitsoundFile'] != "")]
    dfFrom: pd.DataFrame
    dfFrom.sort_values('offset').reset_index(drop=True, inplace=True)

    HITSOUND_CLAP = 2
    HITSOUND_FINISH = 4
    HITSOUND_WHISTLE = 8

    # Before we group, we want to split the hitsoundFile to clap, finish and whistle (2, 4, 8)
    dfFrom['hitsoundClap'] \
        = np.where(dfFrom['hitsoundSet'] & HITSOUND_CLAP == HITSOUND_CLAP, HITSOUND_CLAP, 0)
    dfFrom['hitsoundFinish'] \
        = np.where(dfFrom['hitsoundSet'] & HITSOUND_FINISH == HITSOUND_FINISH, HITSOUND_FINISH, 0)
    dfFrom['hitsoundWhistle'] \
        = np.where(dfFrom['hitsoundSet'] & HITSOUND_WHISTLE == HITSOUND_WHISTLE, HITSOUND_WHISTLE, 0)

    dfFrom.drop('hitsoundSet', inplace=True, axis='columns')
    dfFrom = dfFrom.groupby('offset')

    # We'll just get the mTo data then export it again
    dfToNotes = pd.concat(mTo.notes.df(), sort=False)
    dfToNotes.sort_values('offset').reset_index(drop=True, inplace=True)
    dfToOffsets = dfToNotes['offset']

    # We grab a deepCopy if not inplace
    mToCopy = mTo if inplace else deepcopy(mTo)
    mToCopy.resetAllSamples()

    # The idea is to loop through unique offsets where there's hitsounds/samples
    # For each offset, we group by the volume, because we can slot multiple default samples if we just specify 1 volume

    # e.g. < (C)lap (F)inish (W)histle >
    # C F W  Vol | C F W  Vol
    # 1 0 0  20  | 1 1 1  20
    # 0 1 0  20  | 1 0 0  30
    # 0 0 1  20  | 0 1 1  40
    # 1 0 0  30  | CUSTOM 20
    # 0 1 1  40  |
    # CUSTOM 20  |

    for offset, offsetGroup in dfFrom:
        # You cannot have hitsound Files and the default hitsounds together
        # We find out which indexes match on the df we want to copy to
        slotIndexes = list(
            (dfToOffsets == offset)[dfToOffsets == offset].index)
        slot = 0  # Indicates the slot on "TO" we're looking at right now
        slotMax = len(slotIndexes)  # The maximum slots

        offsetGroup: pd.DataFrame
        vGroups = offsetGroup.groupby('volume', as_index=False)\
                             .agg({'hitsoundFile': ';'.join,
                                   'hitsoundClap': 'sum',
                                   'hitsoundFinish': 'sum',
                                   'hitsoundWhistle': 'sum'})
        vGroups: pd.DataFrame

        for k, vGroup in vGroups.iterrows():  # vGroup -> Volume Group
            volume = vGroup['volume']
            claps = int(vGroup['hitsoundClap'] / HITSOUND_CLAP)
            finishes = int(vGroup['hitsoundFinish'] / HITSOUND_FINISH)
            whistles = int(vGroup['hitsoundWhistle'] / HITSOUND_WHISTLE)
            hitsoundFiles = [
                file for file in vGroup['hitsoundFile'].split(';')
                if len(file) > 0
            ]

            samples = max(claps, finishes, whistles)
            for i in range(0, samples):
                # We loop through the default C F W samples here
                if slot == slotMax:
                    log.debug(
                        f"No slot to place hitsound {slot} > {slotMax}, dropping hitsound at {offset}"
                    )
                    break

                val = 0
                if claps:
                    claps -= 1
                    val += HITSOUND_CLAP
                if finishes:
                    finishes -= 1
                    val += HITSOUND_FINISH
                if whistles:
                    whistles -= 1
                    val += HITSOUND_WHISTLE
                log.debug(f"Slotted Hitsound {val} at {offset} vol {volume}")
                dfToNotes.at[slotIndexes[slot], 'hitsoundSet'] = val
                dfToNotes.at[slotIndexes[slot],
                             'volume'] = volume if volume > 0 else 0
                slot += 1

            for file in hitsoundFiles:
                # We loop through the custom sample here
                if slot == slotMax:
                    log.debug(
                        f"No slot to place hitsound {slot} > {slotMax}, sampling {file} at {offset}"
                    )
                    mToCopy.samples.append(
                        OsuSample(offset=offset,
                                  sampleFile=file,
                                  volume=volume))
                    break
                log.debug(f"Slotted Hitsound {file} at {offset} vol {volume}")
                dfToNotes.at[slotIndexes[slot], 'hitsoundFile'] = file
                dfToNotes.at[slotIndexes[slot],
                             'volume'] = volume if volume > 0 else 0
                slot += 1

    newDf = dfToNotes.to_dict('records')
    newDfHit = [deepcopy(n) for n in newDf if not isinstance(n['_tail'], dict)]
    newDfHold = [deepcopy(n) for n in newDf if isinstance(n['_tail'], dict)]
    for n in newDfHit:
        del n['_tail']

    mToCopy.notes = OsuNotePkg(
        hits=OsuHitList([OsuHit(**hit) for hit in newDfHit]),
        holds=OsuHoldList([OsuHold.fromDict(hold) for hold in newDfHold]))

    return None if inplace else mToCopy
Exemplo n.º 6
0
from math import pi

import numpy as np

from aleph.consts import *
from reamber.algorithms.generate.sv.generators.svOsuMeasureLineMD import svOsuMeasureLineMD, SvOsuMeasureLineEvent
from reamber.osu.OsuBpm import OsuBpm
from reamber.osu.OsuMap import OsuMap
from reamber.osu.lists.notes.OsuHitList import OsuHitList

# 03:22:782 () -

offsets0 = OsuHitList.readEditorString("04:00:222 (240222|2,240650|2,241079|2,241507|2,241936|2,242364|2,242793|2,"
                                       "243222|2,243650|2,244079|2,244507|2,244936|2,245364|2,245793|2,246222|2,"
                                       "246650|2,247079|2) - ").offsets()
offsets1 = OsuHitList.readEditorString("04:07:079 (247079|2,247507|2,247936|2,248364|2,248793|2,249222|2,249650|2,"
                                       "250079|2,250507|2,250936|2,251364|2,251793|2,252222|2,252650|2,253079|2,"
                                       "253507|2,253936|2) - ").offsets()

offsets2 = OsuHitList.readEditorString("04:13:936 (253936|2,254364|2,254793|2,255222|2,255650|2,256079|2,256507|2,"
                                       "256936|2,257364|2,257793|2,258222|2,258650|2,259079|2,259507|2,259936|2,"
                                       "260364|2,260793|2) - ").offsets()
offsets3 = OsuHitList.readEditorString("04:21:222 (260793|2,261222|2,261650|2,262079|2,262507|2,262936|2,263364|2,"
                                       "263793|2,264222|2,264650|2,265079|2,265507|2,265936|2,266364|2,266793|2,"
                                       "267222|2,267650|2) - ").offsets()

offsets4 = OsuHitList.readEditorString("04:27:650 (267650|1,268079|1,268507|1,268936|1,269364|1,269793|1,270222|1,"
                                       "270650|1,271079|1,271507|1,271936|1,272364|1,272793|1,273222|1,273650|1,"
                                       "274079|1,274507|1) - ").offsets()
offsets5 = OsuHitList.readEditorString("04:34:507 (274507|3,274936|3,275364|3,275793|3,276222|3,276650|3,277079|3,"
                                       "277507|3,277936|3,278364|3,278793|3,279222|3,279650|3,280079|3,280507|3,"
Exemplo n.º 7
0
from reamber.osu.OsuMap import OsuMap
from reamber.osu.lists.notes.OsuHitList import OsuHitList

offsets = OsuHitList.readEditorString(
    "05:29:364 ("
    "329364|3,329484|3,329604|3,329724|3,329844|3,329964|3,330084|3,330204|3,"
    "330324|3,330444|3,330564|3,330684|3,330804|3,330924|3,331044|3,331164|3,"
    "332244|0,332364|0,332484|0,332604|0,332724|0,332844|0,332964|0,333084|0,"
    "333204|0,333324|0,333444|0,333564|0,333684|0,333804|0,333924|0,334044|0,"
    "334164|0,334284|0,334404|0,334524|0,334644|0,334764|0,334884|0,335004|0,"
    "335124|0,335244|0,335364|0,335484|0,335604|0,335724|0,335844|0,335964|0,"
    "337044|0,337164|0,337284|0,337404|0,337524|0,337644|0,337764|0,337884|0,"
    "338004|0,338124|0,338244|0,338364|0,338484|0,338604|0,338724|0,338844|0,"
    "339924|0,340044|0,340164|0,340284|0,340404|0,340524|0,340644|0,340764|0,"
    "340884|0,341004|0,341124|0,341244|0,341364|0,341484|0,341604|0,341724|0,"
    "341844|0,341964|0,342084|0,342204|0,342324|0,342444|0,342564|0,342684|0,"
    "342804|0,342924|0,343044|0,343164|0,343284|0,343404|0,343524|0,343644|0,"
    "345684|0,345804|0,345924|0,346044|0,346164|0,346284|0,346404|0,346524|0,"
    "346644|0,346764|0,346884|0,347004|0,347124|0,347244|0,347364|0,347484|0,"
    "348564|0,348684|0,348804|0,348924|0,349044|0,349164|0,349284|0,349404|0,"
    "349524|0,349644|0,349764|0,349884|0,350004|0,350124|0,350244|0,350364|0,"
    "350484|0,350604|0,350724|0,350844|0,350964|0,351084|0,351204|0,351324|0,"
    "351444|0,351564|0,351684|0,351804|0,351924|0,352044|0,352164|0,352284|0,"
    "353364|0,353484|0,353604|0,353724|0,353844|0,353964|0,354084|0,354204|0,"
    "354324|0,354444|0,354564|0,354684|0,354804|0,354924|0,355044|0,355164|0,"
    "356244|0,356364|0,356484|0,356604|0,356724|0,356844|0,356964|0,357084|0,"
    "357204|0,357324|0,357444|0,357564|0,357684|0,357804|0,357924|0,358044|0,"
    "358164|0,358284|0,358404|0,358524|0,358644|0,358764|0,358884|0,359004|0"
    ") - ").offsets()

offsetsStart = OsuHitList.readEditorString(
    "05:29:364 (329364|2,332244|2,337044|2,339924|2,345684|2,348564|2,"
Exemplo n.º 8
0
from math import pi

import numpy as np

from aleph.consts import *
from reamber.algorithms.generate.sv.generators.svOsuMeasureLineMD import svOsuMeasureLineMD, SvOsuMeasureLineEvent
from reamber.osu.OsuMap import OsuMap
from reamber.osu.lists.notes.OsuHitList import OsuHitList

# 03:22:782 () -

offsets0 = OsuHitList.readEditorString(
    "03:56:382 (236382|2,236622|2,236862|2,237102|2,237342|2,237582|2,237822|2,"
    "238062|2,238302|2) - ").offsets()
offsets1 = OsuHitList.readEditorString(
    "03:58:302 (238302|2,238542|2,238782|2,239022|2,239262|2,239502|2,239742|2,"
    "239982|2,240222|2) - ").offsets()

rands = -np.random.rand(len(offsets0) - 1)

COS_CURVE = 0.15


def f598(m: OsuMap):
    events = [
        *[
            SvOsuMeasureLineEvent(
                firstOffset=o0,
                lastOffset=o1,
                startX=-1,
                endX=1,
Exemplo n.º 9
0
import numpy as np

from aleph.consts import *
from reamber.algorithms.generate.sv.generators.svOsuMeasureLineMD import svOsuMeasureLineMD, SvOsuMeasureLineEvent
from reamber.osu.OsuBpm import OsuBpm, MAX_BPM
from reamber.osu.OsuMap import OsuMap
from reamber.osu.lists.notes.OsuHitList import OsuHitList

offsets0 = OsuHitList.readEditorString(
    "06:00:084 (360084|3,361044|3,361524|3,362004|3) - ").offsets()
offsets1 = OsuHitList.readEditorString(
    "06:02:964 (362964|3,363444|3,363924|3,364884|3,365364|3,365844|3,366324|3,"
    "366804|3) - ").offsets()
offsets2 = OsuHitList.readEditorString(
    "06:07:764 (367764|3,368724|3,369204|3,369684|2) - ").offsets()
offsets3 = OsuHitList.readEditorString(
    "06:10:644 (370644|3,371604|3,373524|3,374484|3) - ").offsets()

DELAY = 120

np.random.seed(0)


def EVENT(funcs, startX, endX, startY, endY, offsetsFrom, offsetsTo):
    events = [(SvOsuMeasureLineEvent(firstOffset=r0,
                                     lastOffset=r1,
                                     startX=startX,
                                     endX=endX,
                                     startY=startY,
                                     endY=endY,
                                     funcs=funcs),
Exemplo n.º 10
0
def f083(m: OsuMap):
    notes = [h for h in m.notes.hits() if 33000 < h.offset < 55200]
    notes2 = [h for h in m.notes.hits() if 55900 < h.offset < 63900]
    sweeps = OsuHitList.readEditorString(
        "00:36:457 (36457|3,40297|3,44137|3,47977|3,51817|3,59497|3)").offsets(
        )

    events = [
        SvOsuMeasureLineEvent(firstOffset=32857,
                              lastOffset=55177,
                              startX=0,
                              endX=1,
                              startY=0,
                              endY=1,
                              funcs=[
                                  lambda x: 0.25,
                                  lambda x: 0.50,
                                  lambda x: 0.75,
                                  lambda x: 1,
                              ]),
        *[
            SvOsuMeasureLineEvent(
                firstOffset=n.offset - 500,
                lastOffset=n.offset,
                startX=1,
                endX=0,
                startY=0,
                endY=4,
                funcs=[
                    lambda x, n=n, t=t: x - t + n.column
                    for t in np.linspace(0, 0.05, NOTE_THICKNESS)
                ]) for n in notes
        ],
        *[
            SvOsuMeasureLineEvent(
                firstOffset=55177,
                lastOffset=55777,
                startX=0,
                endX=1,
                startY=0,
                endY=1,
                funcs=[
                    lambda x, i=i: 0 + x + np.random.rand() % 0.2,
                    lambda x, i=i: 0.25 + x + np.random.rand() % 0.2,
                    lambda x, i=i: 0.50 + x + np.random.rand() % 0.2,
                    lambda x, i=i: 0.75 + x + np.random.rand() % 0.2,
                    lambda x, i=i: 0 - x + np.random.rand() % 0.2,
                    lambda x, i=i: 0.25 - x + np.random.rand() % 0.2,
                    lambda x, i=i: 0.50 - x + np.random.rand() % 0.2,
                    lambda x, i=i: 0.75 - x + np.random.rand() % 0.2,
                ]) for i in range(0, RAND_SIZE // 3)
        ],
        SvOsuMeasureLineEvent(firstOffset=55777,
                              lastOffset=63817,
                              startX=0,
                              endX=8,
                              startY=0,
                              endY=1,
                              funcs=[
                                  lambda x: 0.25,
                                  lambda x: 0.50,
                                  lambda x: 0.75,
                                  lambda x: 1,
                              ]),
        *[
            SvOsuMeasureLineEvent(
                firstOffset=n.offset - 500,
                lastOffset=n.offset,
                startX=0,
                endX=1,
                startY=0,
                endY=4,
                funcs=[
                    lambda x, n=n, t=t: x - t + n.column
                    for t in np.linspace(0, 0.05, NOTE_THICKNESS)
                ]) for n in notes2
        ],
        # These are the sweeps
        *[
            SvOsuMeasureLineEvent(
                firstOffset=s - 240,
                lastOffset=s + 240,
                startX=-1,
                endX=1,
                startY=0,
                endY=1,
                funcs=[
                    lambda x, t=t:
                    (1 / np.random.rand()**2 * np.abs(np.sin((x - t) * pi)) +
                     (x - t)**2) % 1
                    for t in np.linspace(0, 0.25, RAND_SIZE // 2)
                ]) for s in sweeps
        ]
    ]
    svs, bpms = svOsuMeasureLineMD(events,
                                   scalingFactor=SCALE,
                                   firstOffset=32857,
                                   lastOffset=63817,
                                   paddingSize=PADDING,
                                   endBpm=250,
                                   metronome=999)

    m.svs.extend(svs)
    m.bpms.extend(bpms)
Exemplo n.º 11
0
from reamber.osu.lists.notes.OsuHitList import OsuHitList

# 03:22:782 () -

offsets = OsuHitList.readEditorString("03:41:022 (221022|0,221022|3,221022|2,221142|1,221262|1,221382|0,221382|2,"
                                      "221382|3,221502|1,221622|2,221742|2,221742|3,221742|0,221742|1,222702|1,"
                                      "222702|0,222702|3,222702|2,222942|0,222942|3,222942|2,223062|2,223062|3,"
                                      "223062|0,223182|1,223302|0,223302|2,223302|3,223422|1,223542|2,223662|2,"
                                      "223662|3,223662|0,223662|1,224142|2,224142|0,224142|3,224142|1,224262|1,"
                                      "224382|2,224502|0,224502|1,224502|3,224622|2,224742|1,224862|2,224862|0,"
                                      "224862|3,224982|1,225102|1,225222|2,225222|3,225222|0,225342|1,225462|2,"
                                      "225582|2,225582|3,225582|0,225582|1,226542|1,226542|0,226542|3,226542|2,"
                                      "226782|0,226782|3,226782|2,226902|2,226902|3,226902|0,227022|1,227142|0,"
                                      "227142|2,227142|3,227262|1,227382|2,227502|2,227502|3,227502|1,227502|0,"
                                      "227982|2,227982|0,227982|3,227982|1,228102|1,228222|2,228342|0,228342|1,"
                                      "228342|3,228462|2,228582|3,228702|1,228702|0,228702|2,228822|3,228942|3,"
                                      "229062|2,229062|1,229062|0,229182|3,229302|0,229422|0,229422|1,229422|2,"
                                      "229422|3,230382|3,230382|2,230382|1,230382|0,230622|1,230622|2,230622|0,"
                                      "230742|0,230742|2,230742|1,230862|3,230982|2,230982|1,230982|0,231102|3,"
                                      "231222|0,231342|1,231342|0,231342|2,231342|3,231822|3,231822|1,231822|2,"
                                      "231822|0,231942|1,232062|2,232182|3,232182|1,232182|0,232302|2,232422|3,"
                                      "232542|2,232542|0,232542|1,232662|3,232782|3,232902|0,232902|1,232902|2,"
                                      "233022|3,233142|0,233262|2,233262|3,233262|0,233262|1,234222|1,234222|0,"
                                      "234222|3,234222|2,234462|0,234462|2,234462|1,234582|1,234582|2,234582|0,"
                                      "234702|3,234822|0,234822|1,234822|2,234942|3,235062|0,235182|2,235182|3,"
                                      "235182|1,235182|0,235662|2,235662|0,235662|3,235662|1,235782|1,235902|2,"
                                      "236022|0,236022|1,236022|3,236142|2,236262|3,236382|2,236382|1,236382|0) - ")\
    .offsets()

offsets = np.unique(offsets)