From 455ab7559bf5c20a7043ccb9ccee15443c349944 Mon Sep 17 00:00:00 2001 From: tpylak <tpylak> Date: Wed, 4 Aug 2010 11:04:54 +0000 Subject: [PATCH] SE-287 dynamix: read mapping of positions to wells from the additional file SVN: 17336 --- .../etl/dynamix/HCSImageFileExtractor.java | 58 +- .../etl/dynamix/WellLocationMappingUtils.java | 151 +++ .../dynamix/WellLocationMappingUtilsTest.java | 62 + .../cisd/openbis/dss/etl/dynamix/pos2loc.tsv | 1153 +++++++++++++++++ 4 files changed, 1409 insertions(+), 15 deletions(-) create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtils.java create mode 100644 screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtilsTest.java create mode 100644 screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/pos2loc.tsv diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java index 88320361ef9..a3538845d5b 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java @@ -19,7 +19,9 @@ package ch.systemsx.cisd.openbis.dss.etl.dynamix; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; @@ -30,7 +32,9 @@ import ch.systemsx.cisd.bds.hcs.Location; import ch.systemsx.cisd.openbis.dss.etl.AbstractHCSImageFileExtractor; import ch.systemsx.cisd.openbis.dss.etl.AcquiredPlateImage; import ch.systemsx.cisd.openbis.dss.etl.HCSImageFileExtractionResult.Channel; +import ch.systemsx.cisd.openbis.dss.etl.dynamix.WellLocationMappingUtils.DynamixWellPosition; import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation; /** * Image extractor for DynamiX project - work in progress. @@ -39,12 +43,17 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier; */ public class HCSImageFileExtractor extends AbstractHCSImageFileExtractor { + private static final String POSITION_MAPPING_FILE_NAME = "pos2loc.tsv"; + private final List<String> channelNames; + private final Map<File/* mapping file */, Map<DynamixWellPosition, WellLocation>> wellLocationMapCache; + public HCSImageFileExtractor(final Properties properties) { super(properties); this.channelNames = extractChannelNames(properties); + this.wellLocationMapCache = new HashMap<File, Map<DynamixWellPosition, WellLocation>>(); } @Override @@ -74,23 +83,14 @@ public class HCSImageFileExtractor extends AbstractHCSImageFileExtractor @Override /* - * Note: the right mapping for DynamiX project should be found, this one is just to fit all - * images on the 24x48 plate. Odd columns contain right position, even contain left position. - * @param plateLocation - format left_pos100 + * @param plateLocation - format row_column */ protected final Location tryGetPlateLocation(final String plateLocation) { final String[] tokens = StringUtils.split(plateLocation, "_"); - boolean isLeft = (tokens[0].equalsIgnoreCase("left")); - Integer pos = Integer.parseInt(tokens[1].substring(3)); - assert pos > 0 && pos <= 576 : "wrong position: " + pos; - - int sideShift = isLeft ? 1 : 0; - int singleSidedMaxColumn = 24; - int row = ((pos - 1) / singleSidedMaxColumn); - int col = ((pos - 1) % singleSidedMaxColumn) * 2 + sideShift; - - return new Location(col + 1, row + 1); + Integer row = new Integer(tokens[0]); + Integer column = new Integer(tokens[1]); + return Location.tryCreateLocationFromRowAndColumn(row, column); } @Override @@ -114,10 +114,12 @@ public class HCSImageFileExtractor extends AbstractHCSImageFileExtractor } return null; } + WellLocation wellLocation = getWellLocation(imageFile, tokens); + // "left_dia_pos100_t20100227_152439.tif" ImageFileInfo info = new ImageFileInfo(); - // left_pos100 - info.setPlateLocationToken(tokens[0] + "_" + tokens[2]); + // row_column - will be parsed later. It's unnecessary and should be refactored. + info.setPlateLocationToken(wellLocation.getRow() + "_" + wellLocation.getColumn()); info.setWellLocationToken(null); info.setChannelToken(tokens[1]); @@ -126,4 +128,30 @@ public class HCSImageFileExtractor extends AbstractHCSImageFileExtractor info.setTimepointToken("" + Arrays.asList(images).indexOf(imageFile)); return info; } + + private WellLocation getWellLocation(File imageFile, final String[] tokens) + { + Map<DynamixWellPosition, WellLocation> map = getWellLocationMapping(imageFile); + DynamixWellPosition wellPos = + WellLocationMappingUtils.parseWellPosition(tokens[0], tokens[2]); + return map.get(wellPos); + } + + private Map<DynamixWellPosition, WellLocation> getWellLocationMapping(File imageFile) + { + File mappingFile = getMappingFile(imageFile); + Map<DynamixWellPosition, WellLocation> map = wellLocationMapCache.get(mappingFile); + if (map == null) + { + map = WellLocationMappingUtils.parseWellLocationMap(mappingFile); + wellLocationMapCache.put(mappingFile, map); + } + return map; + } + + private static File getMappingFile(File imageFile) + { + File mappingDir = imageFile.getParentFile().getParentFile().getParentFile(); + return new File(mappingDir, POSITION_MAPPING_FILE_NAME); + } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtils.java new file mode 100644 index 00000000000..68d8100e1a4 --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.dss.etl.dynamix; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ch.systemsx.cisd.common.annotation.BeanProperty; +import ch.systemsx.cisd.common.parser.TabFileLoader; +import ch.systemsx.cisd.common.utilities.AbstractHashable; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation; + +/** + * Utility methods to map DynamiX specific position to well location. + * + * @author Tomasz Pylak + */ +class WellLocationMappingUtils +{ + public static Map<DynamixWellPosition, WellLocation> parseWellLocationMap(File mappingFile) + { + final TabFileLoader<MappingEntry> parser = + new TabFileLoader<MappingEntry>(MappingEntry.class); + List<MappingEntry> mappingEntries = parser.load(mappingFile); + return createMapping(mappingEntries); + } + + public static DynamixWellPosition parseWellPosition(String sideToken, String posToken) + { + String posNumber = posToken.substring("pos".length()); + return new DynamixWellPosition(new Integer(posNumber), isRight(sideToken)); + } + + private static Map<DynamixWellPosition, WellLocation> createMapping( + List<MappingEntry> mappingEntries) + { + Map<DynamixWellPosition, WellLocation> mapping = + new HashMap<DynamixWellPosition, WellLocation>(); + for (MappingEntry entry : mappingEntries) + { + DynamixWellPosition wellPos = parseWellPosition(entry.getSide(), entry.getPosition()); + + int row = new Integer(entry.getRow()); + int col = new Integer(entry.getColumn()); + WellLocation wellLoc = new WellLocation(row, col); + + mapping.put(wellPos, wellLoc); + } + return mapping; + } + + private static boolean isRight(String sideToken) + { + return sideToken.equalsIgnoreCase("Right"); + } + + public static class MappingEntry extends AbstractHashable + { + private String position; + + private String side; + + private String row; + + private String column; + + public String getPosition() + { + return position; + } + + @BeanProperty(label = "position") + public void setPosition(String position) + { + this.position = position; + } + + public String getSide() + { + return side; + } + + @BeanProperty(label = "Side") + public void setSide(String side) + { + this.side = side; + } + + public String getRow() + { + return row; + } + + @BeanProperty(label = "row") + public void setRow(String row) + { + this.row = row; + } + + public String getColumn() + { + return column; + } + + @BeanProperty(label = "column") + public void setColumn(String column) + { + this.column = column; + } + } + + static class DynamixWellPosition extends AbstractHashable + { + private final int position; + + private final boolean isRight; + + public DynamixWellPosition(int position, boolean isRight) + { + this.position = position; + this.isRight = isRight; + } + + public int getPosition() + { + return position; + } + + public boolean isRight() + { + return isRight; + } + } + +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtilsTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtilsTest.java new file mode 100644 index 00000000000..e861742a58b --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/WellLocationMappingUtilsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.dss.etl.dynamix; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.openbis.dss.etl.dynamix.WellLocationMappingUtils.DynamixWellPosition; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation; + +/** + * Tests of {@link WellLocationMappingUtils} + * + * @author Tomasz Pylak + */ +public class WellLocationMappingUtilsTest extends AssertJUnit +{ + @Test + public void testMapping() throws IOException + { + Map<DynamixWellPosition, WellLocation> map = + WellLocationMappingUtils.parseWellLocationMap(new File( + "sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/pos2loc.tsv")); + DynamixWellPosition pos = new DynamixWellPosition(100, true); + + WellLocation wellLocation = map.get(pos); + assertEquals(wellLocation.getRow(), 5); + assertEquals(wellLocation.getColumn(), 17); + } + + @Test + public void testPositionParsing() throws IOException + { + DynamixWellPosition expectedPos = new DynamixWellPosition(100, true); + DynamixWellPosition pos = WellLocationMappingUtils.parseWellPosition("Right", "100"); + assertEquals(expectedPos, pos); + + expectedPos = new DynamixWellPosition(5, false); + pos = WellLocationMappingUtils.parseWellPosition("Left", "5"); + assertEquals(expectedPos, pos); + + } + +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/pos2loc.tsv b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/pos2loc.tsv new file mode 100644 index 00000000000..b95b0be8f3a --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/pos2loc.tsv @@ -0,0 +1,1153 @@ +position side row column +1 Right 1 3 +1 Left 1 4 +2 Right 1 5 +2 Left 1 6 +3 Right 1 7 +3 Left 1 8 +4 Right 1 9 +4 Left 1 10 +5 Right 1 11 +5 Left 1 12 +6 Right 1 13 +6 Left 1 14 +7 Right 1 15 +7 Left 1 16 +8 Right 1 17 +8 Left 1 18 +9 Right 1 19 +9 Left 1 20 +10 Right 1 21 +10 Left 1 22 +11 Right 1 23 +11 Left 1 24 +12 Right 1 25 +12 Left 1 26 +13 Right 1 27 +13 Left 1 28 +14 Right 1 29 +14 Left 1 30 +15 Right 1 31 +15 Left 1 32 +16 Right 1 33 +16 Left 1 34 +17 Right 1 35 +17 Left 1 36 +18 Right 1 37 +18 Left 1 38 +19 Right 1 39 +19 Left 1 40 +20 Right 1 41 +20 Left 1 42 +21 Right 1 43 +21 Left 1 44 +22 Right 1 45 +22 Left 1 46 +23 Right 1 47 +23 Left 1 48 +24 Right 2 47 +24 Left 2 48 +25 Right 2 45 +25 Left 2 46 +26 Right 2 43 +26 Left 2 44 +27 Right 2 41 +27 Left 2 42 +28 Right 2 39 +28 Left 2 40 +29 Right 2 37 +29 Left 2 38 +30 Right 2 35 +30 Left 2 36 +31 Right 2 33 +31 Left 2 34 +32 Right 2 31 +32 Left 2 32 +33 Right 2 29 +33 Left 2 30 +34 Right 2 27 +34 Left 2 28 +35 Right 2 25 +35 Left 2 26 +36 Right 2 23 +36 Left 2 24 +37 Right 2 21 +37 Left 2 22 +38 Right 2 19 +38 Left 2 20 +39 Right 2 17 +39 Left 2 18 +40 Right 2 15 +40 Left 2 16 +41 Right 2 13 +41 Left 2 14 +42 Right 2 11 +42 Left 2 12 +43 Right 2 9 +43 Left 2 10 +44 Right 2 7 +44 Left 2 8 +45 Right 2 5 +45 Left 2 6 +46 Right 2 3 +46 Left 2 4 +47 Right 3 3 +47 Left 3 4 +48 Right 3 5 +48 Left 3 6 +49 Right 3 7 +49 Left 3 8 +50 Right 3 9 +50 Left 3 10 +51 Right 3 11 +51 Left 3 12 +52 Right 3 13 +52 Left 3 14 +53 Right 3 15 +53 Left 3 16 +54 Right 3 17 +54 Left 3 18 +55 Right 3 19 +55 Left 3 20 +56 Right 3 21 +56 Left 3 22 +57 Right 3 23 +57 Left 3 24 +58 Right 3 25 +58 Left 3 26 +59 Right 3 27 +59 Left 3 28 +60 Right 3 29 +60 Left 3 30 +61 Right 3 31 +61 Left 3 32 +62 Right 3 33 +62 Left 3 34 +63 Right 3 35 +63 Left 3 36 +64 Right 3 37 +64 Left 3 38 +65 Right 3 39 +65 Left 3 40 +66 Right 3 41 +66 Left 3 42 +67 Right 3 43 +67 Left 3 44 +68 Right 3 45 +68 Left 3 46 +69 Right 3 47 +69 Left 3 48 +70 Right 4 47 +70 Left 4 48 +71 Right 4 45 +71 Left 4 46 +72 Right 4 43 +72 Left 4 44 +73 Right 4 41 +73 Left 4 42 +74 Right 4 39 +74 Left 4 40 +75 Right 4 37 +75 Left 4 38 +76 Right 4 35 +76 Left 4 36 +77 Right 4 33 +77 Left 4 34 +78 Right 4 31 +78 Left 4 32 +79 Right 4 29 +79 Left 4 30 +80 Right 4 27 +80 Left 4 28 +81 Right 4 25 +81 Left 4 26 +82 Right 4 23 +82 Left 4 24 +83 Right 4 21 +83 Left 4 22 +84 Right 4 19 +84 Left 4 20 +85 Right 4 17 +85 Left 4 18 +86 Right 4 15 +86 Left 4 16 +87 Right 4 13 +87 Left 4 14 +88 Right 4 11 +88 Left 4 12 +89 Right 4 9 +89 Left 4 10 +90 Right 4 7 +90 Left 4 8 +91 Right 4 5 +91 Left 4 6 +92 Right 4 3 +92 Left 4 4 +93 Right 5 3 +93 Left 5 4 +94 Right 5 5 +94 Left 5 6 +95 Right 5 7 +95 Left 5 8 +96 Right 5 9 +96 Left 5 10 +97 Right 5 11 +97 Left 5 12 +98 Right 5 13 +98 Left 5 14 +99 Right 5 15 +99 Left 5 16 +100 Right 5 17 +100 Left 5 18 +101 Right 5 19 +101 Left 5 20 +102 Right 5 21 +102 Left 5 22 +103 Right 5 23 +103 Left 5 24 +104 Right 5 25 +104 Left 5 26 +105 Right 5 27 +105 Left 5 28 +106 Right 5 29 +106 Left 5 30 +107 Right 5 31 +107 Left 5 32 +108 Right 5 33 +108 Left 5 34 +109 Right 5 35 +109 Left 5 36 +110 Right 5 37 +110 Left 5 38 +111 Right 5 39 +111 Left 5 40 +112 Right 5 41 +112 Left 5 42 +113 Right 5 43 +113 Left 5 44 +114 Right 5 45 +114 Left 5 46 +115 Right 5 47 +115 Left 5 48 +116 Right 6 47 +116 Left 6 48 +117 Right 6 45 +117 Left 6 46 +118 Right 6 43 +118 Left 6 44 +119 Right 6 41 +119 Left 6 42 +120 Right 6 39 +120 Left 6 40 +121 Right 6 37 +121 Left 6 38 +122 Right 6 35 +122 Left 6 36 +123 Right 6 33 +123 Left 6 34 +124 Right 6 31 +124 Left 6 32 +125 Right 6 29 +125 Left 6 30 +126 Right 6 27 +126 Left 6 28 +127 Right 6 25 +127 Left 6 26 +128 Right 6 23 +128 Left 6 24 +129 Right 6 21 +129 Left 6 22 +130 Right 6 19 +130 Left 6 20 +131 Right 6 17 +131 Left 6 18 +132 Right 6 15 +132 Left 6 16 +133 Right 6 13 +133 Left 6 14 +134 Right 6 11 +134 Left 6 12 +135 Right 6 9 +135 Left 6 10 +136 Right 6 7 +136 Left 6 8 +137 Right 6 5 +137 Left 6 6 +138 Right 6 3 +138 Left 6 4 +139 Right 7 3 +139 Left 7 4 +140 Right 7 5 +140 Left 7 6 +141 Right 7 7 +141 Left 7 8 +142 Right 7 9 +142 Left 7 10 +143 Right 7 11 +143 Left 7 12 +144 Right 7 13 +144 Left 7 14 +145 Right 7 15 +145 Left 7 16 +146 Right 7 17 +146 Left 7 18 +147 Right 7 19 +147 Left 7 20 +148 Right 7 21 +148 Left 7 22 +149 Right 7 23 +149 Left 7 24 +150 Right 7 25 +150 Left 7 26 +151 Right 7 27 +151 Left 7 28 +152 Right 7 29 +152 Left 7 30 +153 Right 7 31 +153 Left 7 32 +154 Right 7 33 +154 Left 7 34 +155 Right 7 35 +155 Left 7 36 +156 Right 7 37 +156 Left 7 38 +157 Right 7 39 +157 Left 7 40 +158 Right 7 41 +158 Left 7 42 +159 Right 7 43 +159 Left 7 44 +160 Right 7 45 +160 Left 7 46 +161 Right 7 47 +161 Left 7 48 +162 Right 8 47 +162 Left 8 48 +163 Right 8 45 +163 Left 8 46 +164 Right 8 43 +164 Left 8 44 +165 Right 8 41 +165 Left 8 42 +166 Right 8 39 +166 Left 8 40 +167 Right 8 37 +167 Left 8 38 +168 Right 8 35 +168 Left 8 36 +169 Right 8 33 +169 Left 8 34 +170 Right 8 31 +170 Left 8 32 +171 Right 8 29 +171 Left 8 30 +172 Right 8 27 +172 Left 8 28 +173 Right 8 25 +173 Left 8 26 +174 Right 8 23 +174 Left 8 24 +175 Right 8 21 +175 Left 8 22 +176 Right 8 19 +176 Left 8 20 +177 Right 8 17 +177 Left 8 18 +178 Right 8 15 +178 Left 8 16 +179 Right 8 13 +179 Left 8 14 +180 Right 8 11 +180 Left 8 12 +181 Right 8 9 +181 Left 8 10 +182 Right 8 7 +182 Left 8 8 +183 Right 8 5 +183 Left 8 6 +184 Right 8 3 +184 Left 8 4 +185 Right 9 3 +185 Left 9 4 +186 Right 9 5 +186 Left 9 6 +187 Right 9 7 +187 Left 9 8 +188 Right 9 9 +188 Left 9 10 +189 Right 9 11 +189 Left 9 12 +190 Right 9 13 +190 Left 9 14 +191 Right 9 15 +191 Left 9 16 +192 Right 9 17 +192 Left 9 18 +193 Right 9 19 +193 Left 9 20 +194 Right 9 21 +194 Left 9 22 +195 Right 9 23 +195 Left 9 24 +196 Right 9 25 +196 Left 9 26 +197 Right 9 27 +197 Left 9 28 +198 Right 9 29 +198 Left 9 30 +199 Right 9 31 +199 Left 9 32 +200 Right 9 33 +200 Left 9 34 +201 Right 9 35 +201 Left 9 36 +202 Right 9 37 +202 Left 9 38 +203 Right 9 39 +203 Left 9 40 +204 Right 9 41 +204 Left 9 42 +205 Right 9 43 +205 Left 9 44 +206 Right 9 45 +206 Left 9 46 +207 Right 9 47 +207 Left 9 48 +208 Right 10 47 +208 Left 10 48 +209 Right 10 45 +209 Left 10 46 +210 Right 10 43 +210 Left 10 44 +211 Right 10 41 +211 Left 10 42 +212 Right 10 39 +212 Left 10 40 +213 Right 10 37 +213 Left 10 38 +214 Right 10 35 +214 Left 10 36 +215 Right 10 33 +215 Left 10 34 +216 Right 10 31 +216 Left 10 32 +217 Right 10 29 +217 Left 10 30 +218 Right 10 27 +218 Left 10 28 +219 Right 10 25 +219 Left 10 26 +220 Right 10 23 +220 Left 10 24 +221 Right 10 21 +221 Left 10 22 +222 Right 10 19 +222 Left 10 20 +223 Right 10 17 +223 Left 10 18 +224 Right 10 15 +224 Left 10 16 +225 Right 10 13 +225 Left 10 14 +226 Right 10 11 +226 Left 10 12 +227 Right 10 9 +227 Left 10 10 +228 Right 10 7 +228 Left 10 8 +229 Right 10 5 +229 Left 10 6 +230 Right 10 3 +230 Left 10 4 +231 Right 11 3 +231 Left 11 4 +232 Right 11 5 +232 Left 11 6 +233 Right 11 7 +233 Left 11 8 +234 Right 11 9 +234 Left 11 10 +235 Right 11 11 +235 Left 11 12 +236 Right 11 13 +236 Left 11 14 +237 Right 11 15 +237 Left 11 16 +238 Right 11 17 +238 Left 11 18 +239 Right 11 19 +239 Left 11 20 +240 Right 11 21 +240 Left 11 22 +241 Right 11 23 +241 Left 11 24 +242 Right 11 25 +242 Left 11 26 +243 Right 11 27 +243 Left 11 28 +244 Right 11 29 +244 Left 11 30 +245 Right 11 31 +245 Left 11 32 +246 Right 11 33 +246 Left 11 34 +247 Right 11 35 +247 Left 11 36 +248 Right 11 37 +248 Left 11 38 +249 Right 11 39 +249 Left 11 40 +250 Right 11 41 +250 Left 11 42 +251 Right 11 43 +251 Left 11 44 +252 Right 11 45 +252 Left 11 46 +253 Right 11 47 +253 Left 11 48 +254 Right 12 47 +254 Left 12 48 +255 Right 12 45 +255 Left 12 46 +256 Right 12 43 +256 Left 12 44 +257 Right 12 41 +257 Left 12 42 +258 Right 12 39 +258 Left 12 40 +259 Right 12 37 +259 Left 12 38 +260 Right 12 35 +260 Left 12 36 +261 Right 12 33 +261 Left 12 34 +262 Right 12 31 +262 Left 12 32 +263 Right 12 29 +263 Left 12 30 +264 Right 12 27 +264 Left 12 28 +265 Right 12 25 +265 Left 12 26 +266 Right 12 23 +266 Left 12 24 +267 Right 12 21 +267 Left 12 22 +268 Right 12 19 +268 Left 12 20 +269 Right 12 17 +269 Left 12 18 +270 Right 12 15 +270 Left 12 16 +271 Right 12 13 +271 Left 12 14 +272 Right 12 11 +272 Left 12 12 +273 Right 12 9 +273 Left 12 10 +274 Right 12 7 +274 Left 12 8 +275 Right 12 5 +275 Left 12 6 +276 Right 12 3 +276 Left 12 4 +277 Right 13 3 +277 Left 13 4 +278 Right 13 5 +278 Left 13 6 +279 Right 13 7 +279 Left 13 8 +280 Right 13 9 +280 Left 13 10 +281 Right 13 11 +281 Left 13 12 +282 Right 13 13 +282 Left 13 14 +283 Right 13 15 +283 Left 13 16 +284 Right 13 17 +284 Left 13 18 +285 Right 13 19 +285 Left 13 20 +286 Right 13 21 +286 Left 13 22 +287 Right 13 23 +287 Left 13 24 +288 Right 13 25 +288 Left 13 26 +289 Right 13 27 +289 Left 13 28 +290 Right 13 29 +290 Left 13 30 +291 Right 13 31 +291 Left 13 32 +292 Right 13 33 +292 Left 13 34 +293 Right 13 35 +293 Left 13 36 +294 Right 13 37 +294 Left 13 38 +295 Right 13 39 +295 Left 13 40 +296 Right 13 41 +296 Left 13 42 +297 Right 13 43 +297 Left 13 44 +298 Right 13 45 +298 Left 13 46 +299 Right 13 47 +299 Left 13 48 +300 Right 14 47 +300 Left 14 48 +301 Right 14 45 +301 Left 14 46 +302 Right 14 43 +302 Left 14 44 +303 Right 14 41 +303 Left 14 42 +304 Right 14 39 +304 Left 14 40 +305 Right 14 37 +305 Left 14 38 +306 Right 14 35 +306 Left 14 36 +307 Right 14 33 +307 Left 14 34 +308 Right 14 31 +308 Left 14 32 +309 Right 14 29 +309 Left 14 30 +310 Right 14 27 +310 Left 14 28 +311 Right 14 25 +311 Left 14 26 +312 Right 14 23 +312 Left 14 24 +313 Right 14 21 +313 Left 14 22 +314 Right 14 19 +314 Left 14 20 +315 Right 14 17 +315 Left 14 18 +316 Right 14 15 +316 Left 14 16 +317 Right 14 13 +317 Left 14 14 +318 Right 14 11 +318 Left 14 12 +319 Right 14 9 +319 Left 14 10 +320 Right 14 7 +320 Left 14 8 +321 Right 14 5 +321 Left 14 6 +322 Right 14 3 +322 Left 14 4 +323 Right 15 3 +323 Left 15 4 +324 Right 15 5 +324 Left 15 6 +325 Right 15 7 +325 Left 15 8 +326 Right 15 9 +326 Left 15 10 +327 Right 15 11 +327 Left 15 12 +328 Right 15 13 +328 Left 15 14 +329 Right 15 15 +329 Left 15 16 +330 Right 15 17 +330 Left 15 18 +331 Right 15 19 +331 Left 15 20 +332 Right 15 21 +332 Left 15 22 +333 Right 15 23 +333 Left 15 24 +334 Right 15 25 +334 Left 15 26 +335 Right 15 27 +335 Left 15 28 +336 Right 15 29 +336 Left 15 30 +337 Right 15 31 +337 Left 15 32 +338 Right 15 33 +338 Left 15 34 +339 Right 15 35 +339 Left 15 36 +340 Right 15 37 +340 Left 15 38 +341 Right 15 39 +341 Left 15 40 +342 Right 15 41 +342 Left 15 42 +343 Right 15 43 +343 Left 15 44 +344 Right 15 45 +344 Left 15 46 +345 Right 15 47 +345 Left 15 48 +346 Right 16 47 +346 Left 16 48 +347 Right 16 45 +347 Left 16 46 +348 Right 16 43 +348 Left 16 44 +349 Right 16 41 +349 Left 16 42 +350 Right 16 39 +350 Left 16 40 +351 Right 16 37 +351 Left 16 38 +352 Right 16 35 +352 Left 16 36 +353 Right 16 33 +353 Left 16 34 +354 Right 16 31 +354 Left 16 32 +355 Right 16 29 +355 Left 16 30 +356 Right 16 27 +356 Left 16 28 +357 Right 16 25 +357 Left 16 26 +358 Right 16 23 +358 Left 16 24 +359 Right 16 21 +359 Left 16 22 +360 Right 16 19 +360 Left 16 20 +361 Right 16 17 +361 Left 16 18 +362 Right 16 15 +362 Left 16 16 +363 Right 16 13 +363 Left 16 14 +364 Right 16 11 +364 Left 16 12 +365 Right 16 9 +365 Left 16 10 +366 Right 16 7 +366 Left 16 8 +367 Right 16 5 +367 Left 16 6 +368 Right 16 3 +368 Left 16 4 +369 Right 17 3 +369 Left 17 4 +370 Right 17 5 +370 Left 17 6 +371 Right 17 7 +371 Left 17 8 +372 Right 17 9 +372 Left 17 10 +373 Right 17 11 +373 Left 17 12 +374 Right 17 13 +374 Left 17 14 +375 Right 17 15 +375 Left 17 16 +376 Right 17 17 +376 Left 17 18 +377 Right 17 19 +377 Left 17 20 +378 Right 17 21 +378 Left 17 22 +379 Right 17 23 +379 Left 17 24 +380 Right 17 25 +380 Left 17 26 +381 Right 17 27 +381 Left 17 28 +382 Right 17 29 +382 Left 17 30 +383 Right 17 31 +383 Left 17 32 +384 Right 17 33 +384 Left 17 34 +385 Right 17 35 +385 Left 17 36 +386 Right 17 37 +386 Left 17 38 +387 Right 17 39 +387 Left 17 40 +388 Right 17 41 +388 Left 17 42 +389 Right 17 43 +389 Left 17 44 +390 Right 17 45 +390 Left 17 46 +391 Right 17 47 +391 Left 17 48 +392 Right 18 47 +392 Left 18 48 +393 Right 18 45 +393 Left 18 46 +394 Right 18 43 +394 Left 18 44 +395 Right 18 41 +395 Left 18 42 +396 Right 18 39 +396 Left 18 40 +397 Right 18 37 +397 Left 18 38 +398 Right 18 35 +398 Left 18 36 +399 Right 18 33 +399 Left 18 34 +400 Right 18 31 +400 Left 18 32 +401 Right 18 29 +401 Left 18 30 +402 Right 18 27 +402 Left 18 28 +403 Right 18 25 +403 Left 18 26 +404 Right 18 23 +404 Left 18 24 +405 Right 18 21 +405 Left 18 22 +406 Right 18 19 +406 Left 18 20 +407 Right 18 17 +407 Left 18 18 +408 Right 18 15 +408 Left 18 16 +409 Right 18 13 +409 Left 18 14 +410 Right 18 11 +410 Left 18 12 +411 Right 18 9 +411 Left 18 10 +412 Right 18 7 +412 Left 18 8 +413 Right 18 5 +413 Left 18 6 +414 Right 18 3 +414 Left 18 4 +415 Right 19 3 +415 Left 19 4 +416 Right 19 5 +416 Left 19 6 +417 Right 19 7 +417 Left 19 8 +418 Right 19 9 +418 Left 19 10 +419 Right 19 11 +419 Left 19 12 +420 Right 19 13 +420 Left 19 14 +421 Right 19 15 +421 Left 19 16 +422 Right 19 17 +422 Left 19 18 +423 Right 19 19 +423 Left 19 20 +424 Right 19 21 +424 Left 19 22 +425 Right 19 23 +425 Left 19 24 +426 Right 19 25 +426 Left 19 26 +427 Right 19 27 +427 Left 19 28 +428 Right 19 29 +428 Left 19 30 +429 Right 19 31 +429 Left 19 32 +430 Right 19 33 +430 Left 19 34 +431 Right 19 35 +431 Left 19 36 +432 Right 19 37 +432 Left 19 38 +433 Right 19 39 +433 Left 19 40 +434 Right 19 41 +434 Left 19 42 +435 Right 19 43 +435 Left 19 44 +436 Right 19 45 +436 Left 19 46 +437 Right 19 47 +437 Left 19 48 +438 Right 20 47 +438 Left 20 48 +439 Right 20 45 +439 Left 20 46 +440 Right 20 43 +440 Left 20 44 +441 Right 20 41 +441 Left 20 42 +442 Right 20 39 +442 Left 20 40 +443 Right 20 37 +443 Left 20 38 +444 Right 20 35 +444 Left 20 36 +445 Right 20 33 +445 Left 20 34 +446 Right 20 31 +446 Left 20 32 +447 Right 20 29 +447 Left 20 30 +448 Right 20 27 +448 Left 20 28 +449 Right 20 25 +449 Left 20 26 +450 Right 20 23 +450 Left 20 24 +451 Right 20 21 +451 Left 20 22 +452 Right 20 19 +452 Left 20 20 +453 Right 20 17 +453 Left 20 18 +454 Right 20 15 +454 Left 20 16 +455 Right 20 13 +455 Left 20 14 +456 Right 20 11 +456 Left 20 12 +457 Right 20 9 +457 Left 20 10 +458 Right 20 7 +458 Left 20 8 +459 Right 20 5 +459 Left 20 6 +460 Right 20 3 +460 Left 20 4 +461 Right 21 3 +461 Left 21 4 +462 Right 21 5 +462 Left 21 6 +463 Right 21 7 +463 Left 21 8 +464 Right 21 9 +464 Left 21 10 +465 Right 21 11 +465 Left 21 12 +466 Right 21 13 +466 Left 21 14 +467 Right 21 15 +467 Left 21 16 +468 Right 21 17 +468 Left 21 18 +469 Right 21 19 +469 Left 21 20 +470 Right 21 21 +470 Left 21 22 +471 Right 21 23 +471 Left 21 24 +472 Right 21 25 +472 Left 21 26 +473 Right 21 27 +473 Left 21 28 +474 Right 21 29 +474 Left 21 30 +475 Right 21 31 +475 Left 21 32 +476 Right 21 33 +476 Left 21 34 +477 Right 21 35 +477 Left 21 36 +478 Right 21 37 +478 Left 21 38 +479 Right 21 39 +479 Left 21 40 +480 Right 21 41 +480 Left 21 42 +481 Right 21 43 +481 Left 21 44 +482 Right 21 45 +482 Left 21 46 +483 Right 21 47 +483 Left 21 48 +484 Right 22 47 +484 Left 22 48 +485 Right 22 45 +485 Left 22 46 +486 Right 22 43 +486 Left 22 44 +487 Right 22 41 +487 Left 22 42 +488 Right 22 39 +488 Left 22 40 +489 Right 22 37 +489 Left 22 38 +490 Right 22 35 +490 Left 22 36 +491 Right 22 33 +491 Left 22 34 +492 Right 22 31 +492 Left 22 32 +493 Right 22 29 +493 Left 22 30 +494 Right 22 27 +494 Left 22 28 +495 Right 22 25 +495 Left 22 26 +496 Right 22 23 +496 Left 22 24 +497 Right 22 21 +497 Left 22 22 +498 Right 22 19 +498 Left 22 20 +499 Right 22 17 +499 Left 22 18 +500 Right 22 15 +500 Left 22 16 +501 Right 22 13 +501 Left 22 14 +502 Right 22 11 +502 Left 22 12 +503 Right 22 9 +503 Left 22 10 +504 Right 22 7 +504 Left 22 8 +505 Right 22 5 +505 Left 22 6 +506 Right 22 3 +506 Left 22 4 +507 Right 23 3 +507 Left 23 4 +508 Right 23 5 +508 Left 23 6 +509 Right 23 7 +509 Left 23 8 +510 Right 23 9 +510 Left 23 10 +511 Right 23 11 +511 Left 23 12 +512 Right 23 13 +512 Left 23 14 +513 Right 23 15 +513 Left 23 16 +514 Right 23 17 +514 Left 23 18 +515 Right 23 19 +515 Left 23 20 +516 Right 23 21 +516 Left 23 22 +517 Right 23 23 +517 Left 23 24 +518 Right 23 25 +518 Left 23 26 +519 Right 23 27 +519 Left 23 28 +520 Right 23 29 +520 Left 23 30 +521 Right 23 31 +521 Left 23 32 +522 Right 23 33 +522 Left 23 34 +523 Right 23 35 +523 Left 23 36 +524 Right 23 37 +524 Left 23 38 +525 Right 23 39 +525 Left 23 40 +526 Right 23 41 +526 Left 23 42 +527 Right 23 43 +527 Left 23 44 +528 Right 23 45 +528 Left 23 46 +529 Right 23 47 +529 Left 23 48 +530 Right 24 47 +530 Left 24 48 +531 Right 24 45 +531 Left 24 46 +532 Right 24 43 +532 Left 24 44 +533 Right 24 41 +533 Left 24 42 +534 Right 24 39 +534 Left 24 40 +535 Right 24 37 +535 Left 24 38 +536 Right 24 35 +536 Left 24 36 +537 Right 24 33 +537 Left 24 34 +538 Right 24 31 +538 Left 24 32 +539 Right 24 29 +539 Left 24 30 +540 Right 24 27 +540 Left 24 28 +541 Right 24 25 +541 Left 24 26 +542 Right 24 23 +542 Left 24 24 +543 Right 24 21 +543 Left 24 22 +544 Right 24 19 +544 Left 24 20 +545 Right 24 17 +545 Left 24 18 +546 Right 24 15 +546 Left 24 16 +547 Right 24 13 +547 Left 24 14 +548 Right 24 11 +548 Left 24 12 +549 Right 24 9 +549 Left 24 10 +550 Right 24 7 +550 Left 24 8 +551 Right 24 5 +551 Left 24 6 +552 Right 24 3 +552 Left 24 4 +553 Right 24 1 +553 Left 24 2 +554 Right 23 1 +554 Left 23 2 +555 Right 22 1 +555 Left 22 2 +556 Right 21 1 +556 Left 21 2 +557 Right 20 1 +557 Left 20 2 +558 Right 19 1 +558 Left 19 2 +559 Right 18 1 +559 Left 18 2 +560 Right 17 1 +560 Left 17 2 +561 Right 16 1 +561 Left 16 2 +562 Right 15 1 +562 Left 15 2 +563 Right 14 1 +563 Left 14 2 +564 Right 13 1 +564 Left 13 2 +565 Right 12 1 +565 Left 12 2 +566 Right 11 1 +566 Left 11 2 +567 Right 10 1 +567 Left 10 2 +568 Right 9 1 +568 Left 9 2 +569 Right 8 1 +569 Left 8 2 +570 Right 7 1 +570 Left 7 2 +571 Right 6 1 +571 Left 6 2 +572 Right 5 1 +572 Left 5 2 +573 Right 4 1 +573 Left 4 2 +574 Right 3 1 +574 Left 3 2 +575 Right 2 1 +575 Left 2 2 +576 Right 1 1 +576 Left 1 2 \ No newline at end of file -- GitLab