comparison jbrowse2.py @ 23:39b717d934a8 draft

planemo upload for repository https://github.com/usegalaxy-eu/temporary-tools/tree/master/jbrowse2 commit be2268f4c11d54bdd44789dd88dd9017cad27887-dirty
author fubar
date Sat, 03 Feb 2024 10:17:27 +0000
parents bde6b1d09f7d
children fb6cc7bc24df
comparison
equal deleted inserted replaced
22:2ddd41a0c2d5 23:39b717d934a8
229 bc_pivot = float(bc_pivot) 229 bc_pivot = float(bc_pivot)
230 trackConfig["bicolor_pivot"] = bc_pivot 230 trackConfig["bicolor_pivot"] = bc_pivot
231 elif "scaling" in track: 231 elif "scaling" in track:
232 if track["scaling"]["method"] == "ignore": 232 if track["scaling"]["method"] == "ignore":
233 if track["scaling"]["scheme"]["color"] != "__auto__": 233 if track["scaling"]["scheme"]["color"] != "__auto__":
234 trackConfig["style"]["color"] = track["scaling"]["scheme"][ 234 trackConfig["style"]["color"] = track["scaling"]["scheme"]["color"]
235 "color"
236 ]
237 else: 235 else:
238 trackConfig["style"]["color"] = self.hex_from_rgb( 236 trackConfig["style"]["color"] = self.hex_from_rgb(
239 *self._get_colours() 237 *self._get_colours()
240 ) 238 )
241 else: 239 else:
258 "red": red, 256 "red": red,
259 "green": green, 257 "green": green,
260 "blue": blue, 258 "blue": blue,
261 } 259 }
262 ) 260 )
263 trackConfig["style"]["color"] = color_function.replace( 261 trackConfig["style"]["color"] = color_function.replace("\n", "")
264 "\n", ""
265 )
266 elif trackFormat == "gene_calls": 262 elif trackFormat == "gene_calls":
267 # Default values, based on GFF3 spec 263 # Default values, based on GFF3 spec
268 min_val = 0 264 min_val = 0
269 max_val = 1000 265 max_val = 1000
270 # Get min/max and build a scoring function since JBrowse doesn't 266 # Get min/max and build a scoring function since JBrowse doesn't
271 if ( 267 if scales["type"] == "automatic" or scales["type"] == "__auto__":
272 scales["type"] == "automatic"
273 or scales["type"] == "__auto__"
274 ):
275 min_val, max_val = self.min_max_gff(gff3) 268 min_val, max_val = self.min_max_gff(gff3)
276 else: 269 else:
277 min_val = scales.get("min", 0) 270 min_val = scales.get("min", 0)
278 max_val = scales.get("max", 1000) 271 max_val = scales.get("max", 1000)
279 272
280 if scheme["color"] == "__auto__": 273 if scheme["color"] == "__auto__":
281 user_color = "undefined" 274 user_color = "undefined"
282 auto_color = "'%s'" % self.hex_from_rgb( 275 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours())
283 *self._get_colours()
284 )
285 elif scheme["color"].startswith("#"): 276 elif scheme["color"].startswith("#"):
286 user_color = "'%s'" % self.hex_from_rgb( 277 user_color = "'%s'" % self.hex_from_rgb(
287 *self.rgb_from_hex(scheme["color"][1:]) 278 *self.rgb_from_hex(scheme["color"][1:])
288 ) 279 )
289 auto_color = "undefined" 280 auto_color = "undefined"
290 else: 281 else:
291 user_color = "undefined" 282 user_color = "undefined"
292 auto_color = "'%s'" % self.hex_from_rgb( 283 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours())
293 *self._get_colours()
294 )
295 284
296 color_function = self.COLOR_FUNCTION_TEMPLATE_QUAL.format( 285 color_function = self.COLOR_FUNCTION_TEMPLATE_QUAL.format(
297 **{ 286 **{
298 "opacity": self.OPACITY_MATH[algo].format( 287 "opacity": self.OPACITY_MATH[algo].format(
299 **{"max": max_val, "min": min_val} 288 **{"max": max_val, "min": min_val}
301 "user_spec_color": user_color, 290 "user_spec_color": user_color,
302 "auto_gen_color": auto_color, 291 "auto_gen_color": auto_color,
303 } 292 }
304 ) 293 )
305 294
306 trackConfig["style"]["color"] = color_function.replace( 295 trackConfig["style"]["color"] = color_function.replace("\n", "")
307 "\n", ""
308 )
309 return trackConfig 296 return trackConfig
310 297
311 298
312 def etree_to_dict(t): 299 def etree_to_dict(t):
313 if t is None: 300 if t is None:
379 "tool_tool" 366 "tool_tool"
380 ] = '<a target="_blank" href="{galaxy}/datasets/{encoded_id}/show_params">{tool_id}{tool_version}</a>'.format( 367 ] = '<a target="_blank" href="{galaxy}/datasets/{encoded_id}/show_params">{tool_id}{tool_version}</a>'.format(
381 galaxy=GALAXY_INFRASTRUCTURE_URL, 368 galaxy=GALAXY_INFRASTRUCTURE_URL,
382 encoded_id=metadata.get("dataset_id", ""), 369 encoded_id=metadata.get("dataset_id", ""),
383 tool_id=metadata.get("tool_tool_id", ""), 370 tool_id=metadata.get("tool_tool_id", ""),
384 tool_version=metadata.get("tool_tool_version",""), 371 tool_version=metadata.get("tool_tool_version", ""),
385 ) 372 )
386 return metadata 373 return metadata
387 374
388 375
389 class JbrowseConnector(object): 376 class JbrowseConnector(object):
400 self.config_json_file = os.path.join(outdir, "config.json") 387 self.config_json_file = os.path.join(outdir, "config.json")
401 self.clone_jbrowse() 388 self.clone_jbrowse()
402 389
403 def subprocess_check_call(self, command, output=None): 390 def subprocess_check_call(self, command, output=None):
404 if output: 391 if output:
405 log.debug( 392 log.debug("cd %s && %s > %s", self.outdir, " ".join(command), output)
406 "cd %s && %s > %s", self.outdir, " ".join(command), output
407 )
408 subprocess.check_call(command, cwd=self.outdir, stdout=output) 393 subprocess.check_call(command, cwd=self.outdir, stdout=output)
409 else: 394 else:
410 log.debug("cd %s && %s", self.outdir, " ".join(command)) 395 log.debug("cd %s && %s", self.outdir, " ".join(command))
411 subprocess.check_call(command, cwd=self.outdir) 396 subprocess.check_call(command, cwd=self.outdir)
412 397
613 """ 598 """
614 mafPlugin = { 599 mafPlugin = {
615 "plugins": [ 600 "plugins": [
616 { 601 {
617 "name": "MafViewer", 602 "name": "MafViewer",
618 "url": "https://unpkg.com/jbrowse-plugin-mafviewer/dist/jbrowse-plugin-mafviewer.umd.production.min.js" 603 "url": "https://unpkg.com/jbrowse-plugin-mafviewer/dist/jbrowse-plugin-mafviewer.umd.production.min.js",
619 } 604 }
620 ] 605 ]
621 } 606 }
622 tId = trackData["label"] 607 tId = trackData["label"]
623 fname = "%s.bed" % tId 608 fname = "%s.bed" % tId
632 dest, 617 dest,
633 ] 618 ]
634 self.subprocess_check_call(cmd) 619 self.subprocess_check_call(cmd)
635 # Construct samples list 620 # Construct samples list
636 # We could get this from galaxy metadata, not sure how easily. 621 # We could get this from galaxy metadata, not sure how easily.
637 ps = subprocess.Popen( 622 ps = subprocess.Popen(["grep", "^s [^ ]*", "-o", data], stdout=subprocess.PIPE)
638 ["grep", "^s [^ ]*", "-o", data], stdout=subprocess.PIPE
639 )
640 output = subprocess.check_output(("sort", "-u"), stdin=ps.stdout) 623 output = subprocess.check_output(("sort", "-u"), stdin=ps.stdout)
641 ps.wait() 624 ps.wait()
642 outp = output.decode("ascii") 625 outp = output.decode("ascii")
643 soutp = outp.split("\n") 626 soutp = outp.split("\n")
644 samp = [x.split("s ")[1] for x in soutp if x.startswith("s ")] 627 samp = [x.split("s ")[1] for x in soutp if x.startswith("s ")]
794 fname = "%s.bam" % trackData["label"] 777 fname = "%s.bam" % trackData["label"]
795 dest = "%s/%s" % (self.outdir, fname) 778 dest = "%s/%s" % (self.outdir, fname)
796 url = fname 779 url = fname
797 self.subprocess_check_call(["cp", data, dest]) 780 self.subprocess_check_call(["cp", data, dest])
798 bloc = {"uri": url} 781 bloc = {"uri": url}
799 if bam_index is not None and os.path.exists( 782 if bam_index is not None and os.path.exists(os.path.realpath(bam_index)):
800 os.path.realpath(bam_index)
801 ):
802 # bai most probably made by galaxy and stored in galaxy dirs, need to copy it to dest 783 # bai most probably made by galaxy and stored in galaxy dirs, need to copy it to dest
803 self.subprocess_check_call( 784 self.subprocess_check_call(
804 ["cp", os.path.realpath(bam_index), dest + ".bai"] 785 ["cp", os.path.realpath(bam_index), dest + ".bai"]
805 ) 786 )
806 else: 787 else:
807 # Can happen in exotic condition 788 # Can happen in exotic condition
808 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam 789 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam
809 # => no index generated by galaxy, but there might be one next to the symlink target 790 # => no index generated by galaxy, but there might be one next to the symlink target
810 # this trick allows to skip the bam sorting made by galaxy if already done outside 791 # this trick allows to skip the bam sorting made by galaxy if already done outside
811 if os.path.exists(os.path.realpath(data) + ".bai"): 792 if os.path.exists(os.path.realpath(data) + ".bai"):
812 self.symlink_or_copy( 793 self.symlink_or_copy(os.path.realpath(data) + ".bai", dest + ".bai")
813 os.path.realpath(data) + ".bai", dest + ".bai"
814 )
815 else: 794 else:
816 log.warn("Could not find a bam index (.bai file) for %s", data) 795 log.warn("Could not find a bam index (.bai file) for %s", data)
817 trackDict = { 796 trackDict = {
818 "type": "AlignmentsTrack", 797 "type": "AlignmentsTrack",
819 "trackId": tId, 798 "trackId": tId,
845 fname = "%s.cram" % trackData["label"] 824 fname = "%s.cram" % trackData["label"]
846 dest = "%s/%s" % (self.outdir, fname) 825 dest = "%s/%s" % (self.outdir, fname)
847 url = fname 826 url = fname
848 self.subprocess_check_call(["cp", data, dest]) 827 self.subprocess_check_call(["cp", data, dest])
849 bloc = {"uri": url} 828 bloc = {"uri": url}
850 if cram_index is not None and os.path.exists( 829 if cram_index is not None and os.path.exists(os.path.realpath(cram_index)):
851 os.path.realpath(cram_index)
852 ):
853 # most probably made by galaxy and stored in galaxy dirs, need to copy it to dest 830 # most probably made by galaxy and stored in galaxy dirs, need to copy it to dest
854 self.subprocess_check_call( 831 self.subprocess_check_call(
855 ["cp", os.path.realpath(cram_index), dest + ".crai"] 832 ["cp", os.path.realpath(cram_index), dest + ".crai"]
856 ) 833 )
857 else: 834 else:
858 # Can happen in exotic condition 835 # Can happen in exotic condition
859 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam 836 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam
860 # => no index generated by galaxy, but there might be one next to the symlink target 837 # => no index generated by galaxy, but there might be one next to the symlink target
861 # this trick allows to skip the bam sorting made by galaxy if already done outside 838 # this trick allows to skip the bam sorting made by galaxy if already done outside
862 if os.path.exists(os.path.realpath(data) + ".crai"): 839 if os.path.exists(os.path.realpath(data) + ".crai"):
863 self.symlink_or_copy( 840 self.symlink_or_copy(os.path.realpath(data) + ".crai", dest + ".crai")
864 os.path.realpath(data) + ".crai", dest + ".crai"
865 )
866 else: 841 else:
867 log.warn( 842 log.warn("Could not find a cram index (.crai file) for %s", data)
868 "Could not find a cram index (.crai file) for %s", data
869 )
870 trackDict = { 843 trackDict = {
871 "type": "AlignmentsTrack", 844 "type": "AlignmentsTrack",
872 "trackId": tId, 845 "trackId": tId,
873 "name": trackData["name"], 846 "name": trackData["name"],
874 "assemblyNames": [self.genome_name], 847 "assemblyNames": [self.genome_name],
875 "adapter": { 848 "adapter": {
876 "type": "CramAdapter", 849 "type": "CramAdapter",
877 "cramLocation": bloc, 850 "cramLocation": bloc,
878 "craiLocation": {"uri": fname + ".crai",}, 851 "craiLocation": {
852 "uri": fname + ".crai",
853 },
879 "sequenceAdapter": self.genome_sequence_adapter, 854 "sequenceAdapter": self.genome_sequence_adapter,
880 }, 855 },
881 "displays": [ 856 "displays": [
882 { 857 {
883 "type": "LinearAlignmentsDisplay", 858 "type": "LinearAlignmentsDisplay",
884 "displayId": "%s-LinearAlignmentsDisplay" % tId, 859 "displayId": "%s-LinearAlignmentsDisplay" % tId,
885 }, 860 },
939 self.trackIdlist.append(tId) 914 self.trackIdlist.append(tId)
940 915
941 def _sort_gff(self, data, dest): 916 def _sort_gff(self, data, dest):
942 # Only index if not already done 917 # Only index if not already done
943 if not os.path.exists(dest + ".gz"): 918 if not os.path.exists(dest + ".gz"):
944 cmd = "jbrowse sort-gff %s | bgzip -c > %s.gz" % ( 919 cmd = "jbrowse sort-gff '%s' | bgzip -c > '%s.gz'" % (
945 data, 920 data,
946 dest, 921 dest,
947 ) # "gff3sort.pl --precise '%s' | grep -v \"^$\" > '%s'" 922 ) # "gff3sort.pl --precise '%s' | grep -v \"^$\" > '%s'"
948 self.subprocess_popen(cmd) 923 self.subprocess_popen(cmd)
949 self.subprocess_check_call( 924 self.subprocess_check_call(["tabix", "-f", "-p", "gff", dest + ".gz"])
950 ["tabix", "-f", "-p", "gff", dest + ".gz"]
951 )
952 925
953 def _sort_bed(self, data, dest): 926 def _sort_bed(self, data, dest):
954 # Only index if not already done 927 # Only index if not already done
955 if not os.path.exists(dest): 928 if not os.path.exists(dest):
956 cmd = "sort -k1,1 -k2,2n %s | bgzip -c > %s" % (data, dest) 929 cmd = "sort -k1,1 -k2,2n '%s' | bgzip -c > '%s'" % (data, dest)
957 self.subprocess_popen(cmd) 930 self.subprocess_popen(cmd)
958 cmd = ["tabix", "-f", "-p", "bed", dest] 931 cmd = ["tabix", "-f", "-p", "bed", dest]
959 self.subprocess_check_call(cmd) 932 self.subprocess_check_call(cmd)
960 933
961 def add_gff(self, data, ext, trackData): 934 def add_gff(self, data, ext, trackData):
1172 elif dataset_ext in ("hic",): 1145 elif dataset_ext in ("hic",):
1173 self.add_hic( 1146 self.add_hic(
1174 dataset_path, 1147 dataset_path,
1175 outputTrackConfig, 1148 outputTrackConfig,
1176 ) 1149 )
1150 elif dataset_ext in ("cool", "mcool", "scool"):
1151 hictempd = tempfile.mkdtemp()
1152 hic_path = os.path.join(
1153 self.outdir, "%s_%d_%s.hic" % (track_human_label, i, dataset_ext)
1154 )
1155 self.subprocess_check_call(
1156 [
1157 "hictk",
1158 "convert",
1159 "-f",
1160 "--output-fmt",
1161 "hic",
1162 "--tmpdir",
1163 hictempd,
1164 dataset_path,
1165 hic_path,
1166 ]
1167 )
1168 self.add_hic(
1169 hic_path,
1170 outputTrackConfig,
1171 )
1172 shutil.rmtree(hictempd)
1177 elif dataset_ext in ("bed",): 1173 elif dataset_ext in ("bed",):
1178 self.add_bed( 1174 self.add_bed(
1179 dataset_path, 1175 dataset_path,
1180 dataset_ext, 1176 dataset_ext,
1181 outputTrackConfig, 1177 outputTrackConfig,
1189 self.add_bigwig( 1185 self.add_bigwig(
1190 dataset_path, 1186 dataset_path,
1191 outputTrackConfig, 1187 outputTrackConfig,
1192 ) 1188 )
1193 elif dataset_ext == "bam": 1189 elif dataset_ext == "bam":
1194 real_indexes = track["conf"]["options"]["pileup"][ 1190 real_indexes = track["conf"]["options"]["pileup"]["bam_indices"][
1195 "bam_indices" 1191 "bam_index"
1196 ]["bam_index"] 1192 ]
1197 if not isinstance(real_indexes, list): 1193 if not isinstance(real_indexes, list):
1198 real_indexes = [real_indexes] 1194 real_indexes = [real_indexes]
1199 1195
1200 self.add_bam( 1196 self.add_bam(
1201 dataset_path, 1197 dataset_path,
1202 outputTrackConfig, 1198 outputTrackConfig,
1203 track["conf"]["options"]["pileup"], 1199 track["conf"]["options"]["pileup"],
1204 bam_index=real_indexes[i], 1200 bam_index=real_indexes[i],
1205 ) 1201 )
1206 elif dataset_ext == "cram": 1202 elif dataset_ext == "cram":
1207 real_indexes = track["conf"]["options"]["cram"][ 1203 real_indexes = track["conf"]["options"]["cram"]["cram_indices"][
1208 "cram_indices" 1204 "cram_index"
1209 ]["cram_index"] 1205 ]
1210 if not isinstance(real_indexes, list): 1206 if not isinstance(real_indexes, list):
1211 real_indexes = [real_indexes] 1207 real_indexes = [real_indexes]
1212 1208
1213 self.add_cram( 1209 self.add_cram(
1214 dataset_path, 1210 dataset_path,
1280 "end": 100000, 1276 "end": 100000,
1281 } 1277 }
1282 1278
1283 if data.get("defaultLocation", ""): 1279 if data.get("defaultLocation", ""):
1284 ddl = data["defaultLocation"] 1280 ddl = data["defaultLocation"]
1285 loc_match = re.search(r"^([^:]+):(\d*)\.*(\d*)$", ddl) 1281 loc_match = re.search(r"^([^:]+):([\d,]*)\.*([\d,]*)$", ddl)
1286 if loc_match: 1282 if loc_match:
1287 refName = loc_match.group(1) 1283 refName = loc_match.group(1)
1288 drdict["refName"] = refName 1284 drdict["refName"] = refName
1289 if loc_match.group(2) > "": 1285 if loc_match.group(2) > "":
1290 drdict["start"] = int(loc_match.group(2)) 1286 drdict["start"] = int(loc_match.group(2))
1347 config_json = {} 1343 config_json = {}
1348 if self.config_json: 1344 if self.config_json:
1349 config_json.update(self.config_json) 1345 config_json.update(self.config_json)
1350 config_data = {} 1346 config_data = {}
1351 1347
1352 config_data["disableAnalytics"] = ( 1348 config_data["disableAnalytics"] = data.get("analytics", "false") == "true"
1353 data.get("analytics", "false") == "true"
1354 )
1355 1349
1356 config_data["theme"] = { 1350 config_data["theme"] = {
1357 "palette": { 1351 "palette": {
1358 "primary": {"main": data.get("primary_color", "#0D233F")}, 1352 "primary": {"main": data.get("primary_color", "#0D233F")},
1359 "secondary": {"main": data.get("secondary_color", "#721E63")}, 1353 "secondary": {"main": data.get("secondary_color", "#721E63")},
1360 "tertiary": {"main": data.get("tertiary_color", "#135560")}, 1354 "tertiary": {"main": data.get("tertiary_color", "#135560")},
1361 "quaternary": { 1355 "quaternary": {"main": data.get("quaternary_color", "#FFB11D")},
1362 "main": data.get("quaternary_color", "#FFB11D")
1363 },
1364 }, 1356 },
1365 "typography": {"fontSize": int(data.get("font_size", 10))}, 1357 "typography": {"fontSize": int(data.get("font_size", 10))},
1366 } 1358 }
1367 if not config_json.get("configuration", None): 1359 if not config_json.get("configuration", None):
1368 config_json["configuration"] = {} 1360 config_json["configuration"] = {}
1412 1404
1413 if __name__ == "__main__": 1405 if __name__ == "__main__":
1414 parser = argparse.ArgumentParser(description="", epilog="") 1406 parser = argparse.ArgumentParser(description="", epilog="")
1415 parser.add_argument("--xml", help="Track Configuration") 1407 parser.add_argument("--xml", help="Track Configuration")
1416 parser.add_argument("--outdir", help="Output directory", default="out") 1408 parser.add_argument("--outdir", help="Output directory", default="out")
1417 parser.add_argument( 1409 parser.add_argument("--version", "-V", action="version", version="%(prog)s 2.0.1")
1418 "--version", "-V", action="version", version="%(prog)s 2.0.1"
1419 )
1420 args = parser.parse_args() 1410 args = parser.parse_args()
1421 tree = ET.parse(args.xml) 1411 tree = ET.parse(args.xml)
1422 root = tree.getroot() 1412 root = tree.getroot()
1423 1413
1424 # This should be done ASAP 1414 # This should be done ASAP
1510 ) 1500 )
1511 track_conf["category"] = track.attrib["cat"] 1501 track_conf["category"] = track.attrib["cat"]
1512 track_conf["format"] = track.attrib["format"] 1502 track_conf["format"] = track.attrib["format"]
1513 if track.find("options/style"): 1503 if track.find("options/style"):
1514 track_conf["style"] = { 1504 track_conf["style"] = {
1515 item.tag: parse_style_conf(item) 1505 item.tag: parse_style_conf(item) for item in track.find("options/style")
1516 for item in track.find("options/style")
1517 } 1506 }
1518 if track.find("options/style_labels"): 1507 if track.find("options/style_labels"):
1519 track_conf["style_labels"] = { 1508 track_conf["style_labels"] = {
1520 item.tag: parse_style_conf(item) 1509 item.tag: parse_style_conf(item)
1521 for item in track.find("options/style_labels") 1510 for item in track.find("options/style_labels")
1524 track_conf["conf"] = etree_to_dict(track.find("options")) 1513 track_conf["conf"] = etree_to_dict(track.find("options"))
1525 track_conf["category"] = track.attrib["cat"] 1514 track_conf["category"] = track.attrib["cat"]
1526 track_conf["format"] = track.attrib["format"] 1515 track_conf["format"] = track.attrib["format"]
1527 try: 1516 try:
1528 # Only pertains to gff3 + blastxml. TODO? 1517 # Only pertains to gff3 + blastxml. TODO?
1529 track_conf["style"] = { 1518 track_conf["style"] = {t.tag: t.text for t in track.find("options/style")}
1530 t.tag: t.text for t in track.find("options/style")
1531 }
1532 except TypeError: 1519 except TypeError:
1533 track_conf["style"] = {} 1520 track_conf["style"] = {}
1534 pass 1521 pass
1535 track_conf["conf"] = etree_to_dict(track.find("options")) 1522 track_conf["conf"] = etree_to_dict(track.find("options"))
1536 keys = jc.process_annotations(track_conf) 1523 keys = jc.process_annotations(track_conf)
1557 general_data = { 1544 general_data = {
1558 "analytics": root.find("metadata/general/analytics").text, 1545 "analytics": root.find("metadata/general/analytics").text,
1559 "primary_color": root.find("metadata/general/primary_color").text, 1546 "primary_color": root.find("metadata/general/primary_color").text,
1560 "secondary_color": root.find("metadata/general/secondary_color").text, 1547 "secondary_color": root.find("metadata/general/secondary_color").text,
1561 "tertiary_color": root.find("metadata/general/tertiary_color").text, 1548 "tertiary_color": root.find("metadata/general/tertiary_color").text,
1562 "quaternary_color": root.find( 1549 "quaternary_color": root.find("metadata/general/quaternary_color").text,
1563 "metadata/general/quaternary_color"
1564 ).text,
1565 "font_size": root.find("metadata/general/font_size").text, 1550 "font_size": root.find("metadata/general/font_size").text,
1566 } 1551 }
1567 jc.add_general_configuration(general_data) 1552 jc.add_general_configuration(general_data)
1568 trackconf = jc.config_json.get("tracks", None) 1553 trackconf = jc.config_json.get("tracks", None)
1569 if trackconf: 1554 if trackconf: