diff --git a/docroot/resources/images/color-tables/PUNCH.png b/docroot/resources/images/color-tables/PUNCH.png new file mode 100644 index 000000000..8b103e7a9 Binary files /dev/null and b/docroot/resources/images/color-tables/PUNCH.png differ diff --git a/docroot/schema/image_layer.schema.json b/docroot/schema/image_layer.schema.json index 1a349620d..ec78ea3f5 100644 --- a/docroot/schema/image_layer.schema.json +++ b/docroot/schema/image_layer.schema.json @@ -38,7 +38,7 @@ "nickname": { "type": "string", "enum": [ - "GOES-R SUVI 94","GOES-R SUVI 131","GOES-R SUVI 171","GOES-R SUVI 195","GOES-R SUVI 284","GOES-R SUVI 304","EIT 171","EIT 195","EIT 284","EIT 304","LASCO C2","LASCO C3","MDI Mag","MDI Int","AIA 94","AIA 131","AIA 171","AIA 193","AIA 211","AIA 304","AIA 335","AIA 1600","AIA 1700","AIA 4500","HMI Int","HMI Mag","EUVI-A 171","EUVI-A 195","EUVI-A 284","EUVI-A 304","EUVI-B 171","EUVI-B 195","EUVI-B 284","EUVI-B 304","COR1-A","COR2-A","COR1-B","COR2-B","SWAP 174","SXT AlMgMn","SXT thin-Al","SXT white-light","XRT Al_med/Al_mesh","XRT Al_med/Al_thick","XRT Al_med/Be_thick","XRT Al_med/Gband","XRT Al_med/Open","XRT Al_med/Ti_poly","XRT Al_poly/Al_mesh","XRT Al_poly/Al_thick","XRT Al_poly/Be_thick","XRT Al_poly/Gband","XRT Al_poly/Open","XRT Al_poly/Ti_poly","XRT Be_med/Al_mesh","XRT Be_med/Al_thick","XRT Be_med/Be_thick","XRT Be_med/Gband","XRT Be_med/Open","XRT Be_med/Ti_poly","XRT Be_thin/Al_mesh","XRT Be_thin/Al_thick","XRT Be_thin/Be_thick","XRT Be_thin/Gband","XRT Be_thin/Open","XRT Be_thin/Ti_poly","XRT C_poly/Al_mesh","XRT C_poly/Al_thick","XRT C_poly/Be_thick","XRT C_poly/Gband","XRT C_poly/Open","XRT C_poly/Ti_poly","XRT Mispositioned/Mispositioned","XRT Open/Al_mesh","XRT Open/Al_thick","XRT Open/Be_thick","XRT Open/Gband","XRT Open/Open","XRT Open/Ti_poly","TRACE 171","TRACE 195","TRACE 284","TRACE 1216","TRACE 1550","TRACE 1600","TRACE 1700","TRACE white-light","COSMO KCor","EUI FSI 174","EUI FSI 304","EUI HRI 174","EUI HRI 1216","IRIS SJI 1330","IRIS SJI 2796","IRIS SJI 1400","IRIS SJI 1600","IRIS SJI 2832","IRIS SJI 5000","GONG H-alpha","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","PUNCH","GOES CCOR-1","SWFO-L1 CCOR-2","SUVI 94","SUVI 131","SUVI 171","SUVI 195","SUVI 284","SUVI 304","XRT Any/Any","XRT Any/Al_mesh","XRT Any/Al_thick","XRT Any/Be_thick","XRT Any/Gband","XRT Any/Open","XRT Any/Ti_poly","XRT Al_med/Any","XRT Al_poly/Any","XRT Be_med/Any","XRT Be_thin/Any","XRT C_poly/Any","XRT Open/Any" + "GOES-R SUVI 94","GOES-R SUVI 131","GOES-R SUVI 171","GOES-R SUVI 195","GOES-R SUVI 284","GOES-R SUVI 304","EIT 171","EIT 195","EIT 284","EIT 304","LASCO C2","LASCO C3","MDI Mag","MDI Int","AIA 94","AIA 131","AIA 171","AIA 193","AIA 211","AIA 304","AIA 335","AIA 1600","AIA 1700","AIA 4500","HMI Int","HMI Mag","EUVI-A 171","EUVI-A 195","EUVI-A 284","EUVI-A 304","EUVI-B 171","EUVI-B 195","EUVI-B 284","EUVI-B 304","COR1-A","COR2-A","COR1-B","COR2-B","SWAP 174","SXT AlMgMn","SXT thin-Al","SXT white-light","XRT Al_med/Al_mesh","XRT Al_med/Al_thick","XRT Al_med/Be_thick","XRT Al_med/Gband","XRT Al_med/Open","XRT Al_med/Ti_poly","XRT Al_poly/Al_mesh","XRT Al_poly/Al_thick","XRT Al_poly/Be_thick","XRT Al_poly/Gband","XRT Al_poly/Open","XRT Al_poly/Ti_poly","XRT Be_med/Al_mesh","XRT Be_med/Al_thick","XRT Be_med/Be_thick","XRT Be_med/Gband","XRT Be_med/Open","XRT Be_med/Ti_poly","XRT Be_thin/Al_mesh","XRT Be_thin/Al_thick","XRT Be_thin/Be_thick","XRT Be_thin/Gband","XRT Be_thin/Open","XRT Be_thin/Ti_poly","XRT C_poly/Al_mesh","XRT C_poly/Al_thick","XRT C_poly/Be_thick","XRT C_poly/Gband","XRT C_poly/Open","XRT C_poly/Ti_poly","XRT Mispositioned/Mispositioned","XRT Open/Al_mesh","XRT Open/Al_thick","XRT Open/Be_thick","XRT Open/Gband","XRT Open/Open","XRT Open/Ti_poly","TRACE 171","TRACE 195","TRACE 284","TRACE 1216","TRACE 1550","TRACE 1600","TRACE 1700","TRACE white-light","COSMO KCor","EUI FSI 174","EUI FSI 304","EUI HRI 174","EUI HRI 1216","IRIS SJI 1330","IRIS SJI 2796","IRIS SJI 1400","IRIS SJI 1600","IRIS SJI 2832","IRIS SJI 5000","GONG H-alpha","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","RHESSI","PUNCH","GOES","SWFO-L1","PUNCH","GOES-R SUVI 94","GOES-R SUVI 131","GOES-R SUVI 171","GOES-R SUVI 195","GOES-R SUVI 284","GOES-R SUVI 304","XRT Any/Any","XRT Any/Al_mesh","XRT Any/Al_thick","XRT Any/Be_thick","XRT Any/Gband","XRT Any/Open","XRT Any/Ti_poly","XRT Al_med/Any","XRT Al_poly/Any","XRT Be_med/Any","XRT Be_thin/Any","XRT C_poly/Any","XRT Open/Any" ] }, "sourceId": { "type": "integer" }, @@ -52,14 +52,14 @@ "name": { "type": "string", "enum": [ - "", "GOES-R", "100-300keV","12-25keV","1216","131","1330","1400","1550","1600","1700","171","174","193","195","211","25-50keV","2796","2832","284","3-6keV","304","335","4500","50-100keV","5000","530","6-12keV","6562","735","94","AIA","AlMgMn","Al_med","Al_mesh","Al_poly","Al_thick","Any","Back Projection","Be_med","Be_thick","Be_thin","C2","C3","CCOR-1","CCOR-2","Clean","Clean 59","continuum","COR1","COR2","COSMO","C_poly","EIT","EUI","EUVI","FSI","Gband","GOES","GONG","H-alpha","Hinode","HMI","HRI","IRIS","KCor","LASCO","magnetogram","MDI","MEM GE","Mispositioned","MLSO","Open","PROBA2","PUNCH","RHESSI","SDO","SECCHI","SJI","SOHO","SOLO","STEREO_A","STEREO_B","SUVI","SWAP","SWFO-L1","SXT","thin-Al","Ti_poly","TRACE","VIS CS","VIS FWDFIT","WFI+NFI","white-light","XRT","Yohkoh" + "", "GOES-R", "100-300keV","12-25keV","1216","131","1330","1400","1550","1600","1700","171","174","193","195","211","25-50keV","2796","2832","284","3-6keV","304","335","4500","50-100keV","5000","6-12keV","6562","735","94","AIA","Al_med","Al_mesh","Al_poly","Al_thick","AlMgMn","Any","Back Projection","Be_med","Be_thick","Be_thin","C_poly","C2","C3","CCOR-1","CCOR-2","Clean","Clean 59","continuum","COR1","COR2","COSMO","EIT","EUI","EUVI","FSI","Gband","GOES","GONG","H-alpha","Hinode","HMI","HRI","IRIS","KCor","LASCO","magnetogram","MDI","MEM GE","Mispositioned","MLSO","Open","Polarized Brightness","PROBA2","PUNCH","RHESSI","SDO","SECCHI","SJI","SOHO","SOLO","STEREO_A","STEREO_B","SUVI","SWAP","SWFO-L1","SXT","thin-Al","Ti_poly","Total Brightness","TRACE","VIS CS","VIS FWDFIT","WFI+NFI","white-light","XRT","Yohkoh" ], "$comment": "Autogenerated from available sources" }, "label": { "type": "string", "enum": [ - "", "Detector","Energy Band","Filter","Filter Wheel 1","Filter Wheel 2","Instrument","Measurement","Observatory","Reconstruction" + "","Detector","Energy Band","Filter","Filter Wheel 1","Filter Wheel 2","Instrument","Measurement","Observatory","Reconstruction" ], "$comment": "Autogenerated from available sources" } @@ -81,7 +81,7 @@ "^(Detector|Energy Band|Filter|Filter Wheel 1|Filter Wheel 2|Instrument|Measurement|Observatory|Reconstruction)$": { "type": "string", "enum": [ - "GOES-R", "100-300keV","12-25keV","1216","131","1330","1400","1550","1600","1700","171","174","193","195","211","25-50keV","2796","2832","284","3-6keV","304","335","4500","50-100keV","5000","530","6-12keV","6562","735","94","AIA","AlMgMn","Al_med","Al_mesh","Al_poly","Al_thick","Any","Back Projection","Be_med","Be_thick","Be_thin","C2","C3","CCOR-1","CCOR-2","Clean","Clean 59","continuum","COR1","COR2","COSMO","C_poly","EIT","EUI","EUVI","FSI","Gband","GOES","GONG","H-alpha","Hinode","HMI","HRI","IRIS","KCor","LASCO","magnetogram","MDI","MEM GE","Mispositioned","MLSO","Open","PROBA2","PUNCH","RHESSI","SDO","SECCHI","SJI","SOHO","SOLO","STEREO_A","STEREO_B","SUVI","SWAP","SWFO-L1","SXT","thin-Al","Ti_poly","TRACE","VIS CS","VIS FWDFIT","WFI+NFI","white-light","XRT","Yohkoh" + "GOES-R", "100-300keV","12-25keV","1216","131","1330","1400","1550","1600","1700","171","174","193","195","211","25-50keV","2796","2832","284","3-6keV","304","335","4500","50-100keV","5000","6-12keV","6562","735","94","AIA","Al_med","Al_mesh","Al_poly","Al_thick","AlMgMn","Any","Back Projection","Be_med","Be_thick","Be_thin","C_poly","C2","C3","CCOR-1","CCOR-2","Clean","Clean 59","continuum","COR1","COR2","COSMO","EIT","EUI","EUVI","FSI","Gband","GOES","GONG","H-alpha","Hinode","HMI","HRI","IRIS","KCor","LASCO","magnetogram","MDI","MEM GE","Mispositioned","MLSO","Open","Polarized Brightness","PROBA2","PUNCH","RHESSI","SDO","SECCHI","SJI","SOHO","SOLO","STEREO_A","STEREO_B","SUVI","SWAP","SWFO-L1","SXT","thin-Al","Ti_poly","Total Brightness","TRACE","VIS CS","VIS FWDFIT","WFI+NFI","white-light","XRT","Yohkoh" ] } } diff --git a/docroot/schema/post_movie.schema.json b/docroot/schema/post_movie.schema.json index 844c336ba..4d6768892 100644 --- a/docroot/schema/post_movie.schema.json +++ b/docroot/schema/post_movie.schema.json @@ -17,7 +17,7 @@ "layers": { "type": "string", "maxLength": 1000, - "pattern": "^[\\_\\w\\]\\[,\\-:\\.]+$" + "pattern": "^[\\_\\w\\]\\[,\\-:\\.\\+ ]+$" }, "eventsState": { "$ref": "https://api.helioviewer.org/schema/event_layers.schema.json" diff --git a/docroot/schema/post_screenshot.schema.json b/docroot/schema/post_screenshot.schema.json index 502c7b8ef..6092c2fa9 100644 --- a/docroot/schema/post_screenshot.schema.json +++ b/docroot/schema/post_screenshot.schema.json @@ -15,7 +15,7 @@ "layers": { "type": "string", "maxLength": 1000, - "pattern": "^[\\_\\w\\]\\[,\\-:\\.]+$" + "pattern": "^[\\_\\w\\]\\[,\\-:\\.\\+ ]+$" }, "eventsState": { "$ref": "https://api.helioviewer.org/schema/event_layers.schema.json" diff --git a/install/__test__/test_punch.py b/install/__test__/test_punch.py new file mode 100644 index 000000000..dbf2dd51a --- /dev/null +++ b/install/__test__/test_punch.py @@ -0,0 +1,25 @@ +import unittest +import datetime +from helioviewer.hvpull.servers.punch import PUNCHDataServer + +class TestPunchDataServer(unittest.TestCase): + def test_compute_directories(self): + punch = PUNCHDataServer() + dirs = punch.compute_directories(datetime.datetime(2026, 1, 1), datetime.datetime(2026, 1, 5)) + assert dirs == [ + 'https://umbra.nascom.nasa.gov/punch/L/3/CAM/2026/01/05', + 'https://umbra.nascom.nasa.gov/punch/L/3/PAM/2026/01/05', + 'https://umbra.nascom.nasa.gov/punch/L/3/CAM/2026/01/04', + 'https://umbra.nascom.nasa.gov/punch/L/3/PAM/2026/01/04', + 'https://umbra.nascom.nasa.gov/punch/L/3/CAM/2026/01/03', + 'https://umbra.nascom.nasa.gov/punch/L/3/PAM/2026/01/03', + 'https://umbra.nascom.nasa.gov/punch/L/3/CAM/2026/01/02', + 'https://umbra.nascom.nasa.gov/punch/L/3/PAM/2026/01/02', + 'https://umbra.nascom.nasa.gov/punch/L/3/CAM/2026/01/01', + 'https://umbra.nascom.nasa.gov/punch/L/3/PAM/2026/01/01', + ] + + def test_get_datetime_from_file(self): + punch = PUNCHDataServer() + assert punch.get_datetime_from_file("PUNCH_L3_PAM_20251102001600_v0j.fits") == datetime.datetime(2025, 11, 2, 0, 16, 0) + assert punch.get_datetime_from_file("PUNCH_L3_CAM_20260221001600_v0j.fits") == datetime.datetime(2026, 2, 21, 0, 16, 0) diff --git a/install/database/2026_04_07_punch_rename.sql b/install/database/2026_04_07_punch_rename.sql new file mode 100644 index 000000000..1f273713e --- /dev/null +++ b/install/database/2026_04_07_punch_rename.sql @@ -0,0 +1,14 @@ +UPDATE datasources SET description = 'PUNCH WFI+NFI CAM Mosaic' WHERE id = 131; + +UPDATE datasource_property SET name = 'WFI+NFI', fitsName = 'WFI+NFI', description = 'Clear low-noise science mosaic, bkg-sub & resolved into B & uncertainty layer' WHERE sourceId = 131 AND label = 'Instrument'; + +UPDATE datasource_property SET name = 'Total Brightness', fitsName = 'PUNCH Level-3 Intermediate F-corona Subtracted Unpolarized Mosaic' WHERE sourceId = 131 AND label = 'Measurement'; + +INSERT INTO datasources (id, name, description, units, layeringOrder, enabled, sourceIdGroup, displayOrder) +VALUES (134, 'PUNCH', 'PUNCH WFI+NFI PAM Mosaic', NULL, 1, 0, '', 0); + +INSERT INTO datasource_property (sourceId, label, name, fitsName, description, uiOrder) +VALUES +(134, 'Observatory', 'PUNCH', 'PUNCH', 'PUNCH', 1), +(134, 'Instrument', 'WFI+NFI', 'WFI+NFI', 'Polarized low-noise science mosaic, bkg-sub & resolved into B, pB, & uncertainty layer', 2), +(134, 'Measurement', 'Polarized Brightness', 'PUNCH Level-3 Intermediate F-corona Subtracted Polarized Mosaic', '', 3); diff --git a/install/helioviewer/db.py b/install/helioviewer/db.py index e300ec76a..7fffe6410 100644 --- a/install/helioviewer/db.py +++ b/install/helioviewer/db.py @@ -361,7 +361,8 @@ def create_datasource_table(cursor): (128, 'RHESSI', 'RHESSI 25-50keV VIS FWDFIT', NULL, 1, 0, '', 0, 0, 0, 0), (129, 'RHESSI', 'RHESSI 50-100keV VIS FWDFIT', NULL, 1, 0, '', 0, 0, 0, 0), (130, 'RHESSI', 'RHESSI 100-300keV VIS FWDFIT', NULL, 1, 0, '', 0, 0, 0, 0), -(131, 'PUNCH', 'PUNCH WFI+NFI', NULL, 1, 0, '', 0, 0, 0, 0), +(131, 'PUNCH', 'PUNCH WFI+NFI CAM Mosaic', NULL, 1, 0, '', 0, 0, 0, 0), +(134, 'PUNCH', 'PUNCH WFI+NFI PAM Mosaic', NULL, 1, 0, '', 0, 0, 0, 0), (132, 'GOES', 'CCOR-1', NULL, 1, 0, '', 0, 0, 0, 0), (133, 'SWFO-L1', 'CCOR-2', NULL, 1, 0, '', 0, 0, 0, 0), (2000, 'GOES-R SUVI 94', 'GOES-R SUVI 94', NULL, 1, 0, '', 0, 0, 0, 0), @@ -495,6 +496,7 @@ def create_datasource_property_table(cursor): (129, 'Observatory', 'RHESSI', 'RHESSI', 'RHESSI', 1), (130, 'Observatory', 'RHESSI', 'RHESSI', 'RHESSI', 1), (131, 'Observatory', 'PUNCH', 'PUNCH', 'PUNCH', 1), +(134, 'Observatory', 'PUNCH', 'PUNCH', 'PUNCH', 1), (2000, 'Observatory', 'GOES', 'GOES-R', 'GOES-R', 1), (2001, 'Observatory', 'GOES', 'GOES-R', 'GOES-R', 1), (2002, 'Observatory', 'GOES', 'GOES-R', 'GOES-R', 1), @@ -548,7 +550,8 @@ def create_datasource_property_table(cursor): (92, 'Instrument', 'SJI', 'SJI', 'Slit Jaw Imager', 2), (93, 'Instrument', 'SJI', 'SJI', 'Slit Jaw Imager', 2), (94, 'Instrument', 'GONG', 'GONG', 'GONG', 2), -(131, 'Instrument', 'WFI+NFI', 'WFI+NFI', 'Wide and Near Field Imagers', 2), +(131, 'Instrument', 'WFI+NFI', 'WFI+NFI', 'Clear low-noise science mosaic, bkg-sub & resolved into B & uncertainty layer', 2), +(134, 'Instrument', 'WFI+NFI', 'WFI+NFI', 'Polarized low-noise science mosaic, bkg-sub & resolved into B, pB, & uncertainty layer', 2), (2000, 'Instrument', 'SUVI', 'SUVI', 'Solar UltraViolet Imager', 2), (2001, 'Instrument', 'SUVI', 'SUVI', 'Solar UltraViolet Imager', 2), (2002, 'Instrument', 'SUVI', 'SUVI', 'Solar UltraViolet Imager', 2), @@ -574,6 +577,8 @@ def create_datasource_property_table(cursor): (86, 'Detector', 'HRI', 'HRI_EUV', 'High Resolution Imager Extreme Ultraviolet', 3), (87, 'Detector', 'HRI', 'HRI_LYA', 'High Resolution Imager Lyman-a', 3), (94, 'Detector', 'H-alpha', 'H-alpha', 'H-alpha', 3), +(131, 'Measurement', 'Total Brightness', 'PUNCH Level-3 Intermediate F-corona Subtracted Unpolarized Mosaic', '', 3), +(134, 'Measurement', 'Polarized Brightness', 'PUNCH Level-3 Intermediate F-corona Subtracted Polarized Mosaic', '', 3), (0, 'Measurement', '171', '171', '171 Ångström extreme ultraviolet', 3), (1, 'Measurement', '195', '195', '195 Ångström extreme ultraviolet', 3), (2, 'Measurement', '284', '284', '284 Ångström extreme ultraviolet', 3), @@ -618,7 +623,6 @@ def create_datasource_property_table(cursor): (92, 'Measurement', '2832', '2832', '2832 Ångström', 3), (93, 'Measurement', '5000', '5000', '5000 Ångström', 3), (94, 'Measurement', '6562', '6562', 'H-alpha 6562 angstrom', 4), -(131, 'Measurement', '530', '530', '', 3), (2000, 'Measurement', '94', '94', '94 Ångström', 3), (2001, 'Measurement', '131', '131', '131 Ångström', 3), (2002, 'Measurement', '171', '171', '171 Ångström', 3), diff --git a/install/helioviewer/hvpull/net/daemon.py b/install/helioviewer/hvpull/net/daemon.py index a827d6671..f7504bd73 100644 --- a/install/helioviewer/hvpull/net/daemon.py +++ b/install/helioviewer/hvpull/net/daemon.py @@ -539,8 +539,6 @@ def ingest(self, urls): # If everything looks good, move to archive and add to database # print image_params['date'] - date_str = image_params['date'].strftime('%Y/%m/%d') - # The files must be transcoded in order to work with JHelioviewer. # Therefore, any problem with the transcoding process must raise # an error. @@ -566,13 +564,7 @@ def ingest(self, urls): sys.exit(1) # Move to archive - if image_params['observatory'] == "Hinode": - directory = os.path.join(self.image_archive, image_params['nickname'], date_str, str(image_params['filter1']), str(image_params['filter2'])) - elif image_params['observatory'] == "RHESSI": - directory = os.path.join(self.image_archive, image_params['nickname'], date_str, str(image_params['reconstruction_method'])) - else: - directory = os.path.join(self.image_archive, image_params['nickname'], date_str, str(image_params['measurement'])) - + directory = os.path.join(self.image_archive, image_params['storage_path']) dest = os.path.join(directory, filename) image_params['filepath'] = dest diff --git a/install/helioviewer/hvpull/servers/punch.py b/install/helioviewer/hvpull/servers/punch.py index fddc7b808..7be5e473e 100644 --- a/install/helioviewer/hvpull/servers/punch.py +++ b/install/helioviewer/hvpull/servers/punch.py @@ -1,18 +1,24 @@ """PUNCH DataServer""" from helioviewer.hvpull.servers import DataServer +from itertools import product,starmap import datetime import os class PUNCHDataServer(DataServer): def __init__(self): # TODO: Need to update to actual punch source when it's available. - DataServer.__init__(self, "/tmp/incoming/", "PUNCH") + DataServer.__init__(self, "https://umbra.nascom.nasa.gov/punch/L/3/", "PUNCH") self.pause = datetime.timedelta(minutes=30) def compute_directories(self, start_date, end_date): """Computes a list of remote directories expected to contain files""" - dirs = [os.path.join(self.uri)] - return dirs + top_level_folders = ["CAM","PAM"] + + dirs = starmap( + lambda date, folder: os.path.join(self.uri, folder, date), + product(self.get_dates(start_date, end_date), top_level_folders) + ) + return list(dirs) def get_datetime_from_file(self, filename): fname = os.path.basename(filename) diff --git a/install/helioviewer/jp2.py b/install/helioviewer/jp2.py index 6524b650a..7cf1e8a44 100644 --- a/install/helioviewer/jp2.py +++ b/install/helioviewer/jp2.py @@ -99,17 +99,8 @@ def insert_images(images, sources, rootdir, db, cursor, mysql, step_function=Non prev = "" source = sources - if img['observatory'] == "Hinode": - leafs = ["observatory", "instrument", "detector", "filter1", "filter2"] - elif img["observatory"] == "RHESSI": - leafs = ["observatory", "energy_band", "reconstruction_method"] - elif img["observatory"] == "PUNCH": - leafs = ["observatory", "instrument", "measurement"] - else: - leafs = ["observatory", "instrument", "detector", "measurement"] - + leafs = JP2parser.get_detection_keys(img) for leaf in leafs: - if img[leaf] != prev: source = source[str(img[leaf])] prev = img[leaf] diff --git a/install/helioviewer/jp2parser.py b/install/helioviewer/jp2parser.py index 6cd9fffde..9b72c01a1 100644 --- a/install/helioviewer/jp2parser.py +++ b/install/helioviewer/jp2parser.py @@ -4,6 +4,8 @@ JPEG 2000 Image XML Box parser class """ import sys +import os +from os.path import basename from xml.etree import cElementTree as ET import numpy as np from astropy.time import Time @@ -78,6 +80,7 @@ def getData(self): image['XCEN'] = self._data['XCEN'] if 'XCEN' in self._data else 'NULL' image['YCEN'] = self._data['YCEN'] if 'YCEN' in self._data else 'NULL' image['CROTA1'] = self._data['CROTA1'] if 'CROTA1' in self._data else 'NULL' + image['title'] = self._data['TITLE'] if 'TITLE' in self._data else 'NULL' #Fix FITS NaN parameters for key, value in image.items(): @@ -92,6 +95,10 @@ def getData(self): imageData.nickname = 'CCOR-1' elif image['instrument'] == "CCOR2": imageData.nickname = 'CCOR-2' + elif image['observatory'] == 'PUNCH': + # For PUNCH, the different types (e.g. CAM, PAM) are only in the file name, + # not anywhere inside the metadata. + image['detector'] = self._get_punch_file_type(self._filepath) # In sunpy V3, the nickname changed to include the filter. # Having the space in it breaks how helioviewer loads images due to # the space in the file name. To prevent this problem we're selecting @@ -128,8 +135,59 @@ def getData(self): if image["observatory"] == "RHESSI": image = self._process_rhessi_extras(image, imageData) + image['storage_path'] = self.get_storage_suffix(image) + return image + def get_storage_suffix(self, imageData): + """ + Returns the suffix which should be used when saving this image to disk. + e.g. by default it is /nickname/year/month/day/measurement. + This may be different depending on the data source + """ + date_str = imageData['date'].strftime('%Y/%m/%d') + + if imageData['observatory'] == "Hinode": + directory = os.path.join(imageData['nickname'], date_str, str(imageData['filter1']), str(imageData['filter2'])) + elif imageData['observatory'] == "RHESSI": + directory = os.path.join(imageData['nickname'], date_str, str(imageData['reconstruction_method'])) + elif imageData['observatory'] == "PUNCH": + directory = os.path.join(imageData['nickname'], date_str, self._get_punch_file_type(self._filepath)) + else: + directory = os.path.join(imageData['nickname'], date_str, str(imageData['measurement'])) + + return directory + + @staticmethod + def get_detection_keys(img): + """ + Returns the list of keys to be used to identify this file's source id. + + In various places throughout the ingestion pipeline, we have to identify + the image's source ID by iterating over its properties to determine which + data source it belongs to. The keys to look at are typically + observatory -> instrument -> detector -> measurement, but + not every observatory falls into this simple mapping. + + This function returns the list of keys to examine in order to find + the correct source id. + """ + if img['observatory'] == "Hinode": + leafs = ["observatory", "instrument", "detector", "filter1", "filter2"] + elif img["observatory"] == "RHESSI": + leafs = ["observatory", "energy_band", "reconstruction_method"] + elif img["observatory"] == "PUNCH": + leafs = ["observatory", "instrument", "title"] + else: + leafs = ["observatory", "instrument", "detector", "measurement"] + return leafs + + def _get_punch_file_type(self, filepath): + """ + Returns the PUNCH file type e.g. CAM/PAM based on the filename. + This information doesn't exist inside the metadata. + """ + return basename(filepath)[9:12] def get_observatory(self, imageData): """ diff --git a/management/schema/image_layer.schema.template.json b/management/schema/image_layer.schema.template.json index 3a6a13c67..0f106d100 100644 --- a/management/schema/image_layer.schema.template.json +++ b/management/schema/image_layer.schema.template.json @@ -59,7 +59,7 @@ "label": { "type": "string", "enum": [ - {{UILABEL_LABELS}} + "",{{UILABEL_LABELS}} ], "$comment": "Autogenerated from available sources" } diff --git a/scripts/gen_color_tables/HRILYA-color.txt b/scripts/gen_color_tables/HRILYA-color.txt deleted file mode 100644 index e613c1caa..000000000 --- a/scripts/gen_color_tables/HRILYA-color.txt +++ /dev/null @@ -1,256 +0,0 @@ -0 0 0 -1 0 0 -2 0 0 -4 0 0 -5 0 0 -7 0 0 -8 0 0 -10 0 0 -11 0 0 -13 0 0 -14 0 0 -15 0 0 -17 0 0 -18 0 0 -20 0 1 -21 0 1 -23 0 1 -24 0 1 -26 0 1 -27 0 2 -28 0 2 -30 0 2 -31 0 2 -33 0 2 -34 0 3 -36 0 3 -37 0 3 -39 0 4 -40 0 4 -42 0 4 -43 0 5 -44 0 5 -46 0 5 -47 0 6 -49 0 6 -50 0 6 -52 0 7 -53 0 7 -55 0 8 -56 0 8 -57 0 8 -59 0 9 -60 0 9 -62 0 10 -63 0 10 -65 0 11 -66 0 11 -68 0 12 -69 0 13 -70 0 13 -72 0 14 -73 0 14 -75 0 15 -76 0 15 -78 0 16 -79 0 17 -81 0 17 -82 0 18 -84 0 19 -85 0 19 -86 0 20 -88 0 21 -89 0 21 -91 0 22 -92 0 23 -94 0 24 -95 0 24 -97 0 25 -98 0 26 -99 0 26 -101 0 27 -102 0 28 -104 0 29 -105 0 30 -107 0 31 -108 0 31 -110 0 32 -111 0 33 -113 0 34 -114 0 35 -115 0 36 -117 0 37 -118 0 38 -120 0 39 -121 0 40 -123 0 41 -124 0 41 -126 0 43 -127 0 43 -128 0 44 -130 0 46 -131 0 46 -133 0 48 -134 0 49 -136 0 50 -137 0 51 -139 0 52 -140 0 53 -141 0 54 -143 0 55 -144 0 56 -146 0 58 -147 0 59 -149 0 60 -150 0 61 -152 0 62 -153 0 63 -155 0 65 -156 0 66 -157 0 67 -159 0 68 -160 0 69 -162 0 71 -163 0 72 -165 0 74 -166 0 75 -168 0 76 -169 0 77 -170 0 78 -172 0 80 -173 0 81 -175 1 83 -176 3 84 -178 5 86 -179 7 87 -181 9 89 -182 11 90 -184 13 91 -185 15 93 -186 17 94 -188 18 96 -189 20 97 -191 22 99 -192 24 100 -194 26 102 -195 28 103 -197 30 105 -198 32 106 -199 34 108 -201 35 109 -202 37 111 -204 39 113 -205 41 114 -207 43 116 -208 45 117 -210 47 119 -211 49 121 -212 51 122 -214 52 124 -215 54 126 -217 56 128 -218 58 129 -220 60 131 -221 62 133 -223 64 135 -224 66 136 -226 68 138 -227 69 140 -228 71 141 -230 73 143 -231 75 145 -233 77 147 -234 79 149 -236 81 151 -237 83 153 -239 85 155 -240 86 156 -241 88 158 -243 90 160 -244 92 162 -246 94 164 -247 96 166 -249 98 168 -250 100 170 -252 102 172 -253 103 174 -255 105 176 -255 107 177 -255 109 178 -255 111 179 -255 113 180 -255 115 181 -255 117 182 -255 119 183 -255 120 184 -255 122 185 -255 124 186 -255 126 187 -255 128 188 -255 130 189 -255 132 190 -255 134 191 -255 136 192 -255 137 193 -255 139 194 -255 141 195 -255 143 196 -255 145 197 -255 147 198 -255 149 199 -255 151 200 -255 153 201 -255 154 202 -255 156 203 -255 158 204 -255 160 205 -255 162 206 -255 164 207 -255 166 208 -255 168 209 -255 170 210 -255 171 211 -255 173 212 -255 175 213 -255 177 214 -255 179 215 -255 181 216 -255 183 217 -255 185 218 -255 187 219 -255 188 220 -255 190 221 -255 192 222 -255 194 223 -255 196 224 -255 198 225 -255 200 226 -255 202 227 -255 204 228 -255 205 229 -255 207 230 -255 209 231 -255 211 232 -255 213 233 -255 215 234 -255 217 235 -255 219 236 -255 221 237 -255 222 238 -255 224 239 -255 226 240 -255 228 241 -255 230 242 -255 232 243 -255 234 244 -255 236 245 -255 238 246 -255 239 247 -255 241 248 -255 243 249 -255 245 250 -255 247 251 -255 249 252 -255 251 253 -255 253 254 -255 255 255 \ No newline at end of file diff --git a/src/Helper/HelioviewerLayers.php b/src/Helper/HelioviewerLayers.php index 01aee09b9..ebb92162e 100644 --- a/src/Helper/HelioviewerLayers.php +++ b/src/Helper/HelioviewerLayers.php @@ -46,7 +46,7 @@ public function __construct($layerString) { $layerString); // validate layer string - if (!preg_match('/^[_\w\[\],\-:\.]+$/', $layerString)) { + if (!preg_match('/^[_\w\[\],\-:\.\+ ]+$/', $layerString)) { throw new Exception('Invalid layer string', 255); } diff --git a/src/Image/ImageType/PUNCHImage.php b/src/Image/ImageType/PUNCHImage.php index 72275ac02..d9d8bd3cb 100644 --- a/src/Image/ImageType/PUNCHImage.php +++ b/src/Image/ImageType/PUNCHImage.php @@ -27,6 +27,12 @@ class Image_ImageType_PUNCHImage extends Image_HelioviewerImage { * @return void */ public function __construct($jp2, $filepath, $roi, $uiLabels, $offsetX, $offsetY, $options) { + // Assign PUNCH color table + $colorTable = HV_ROOT_DIR + . '/resources/images/color-tables/' + . 'PUNCH.png'; + $this->setColorTable($colorTable); + parent::__construct($jp2, $filepath, $roi, $uiLabels, $offsetX, $offsetY, $options); } @@ -39,6 +45,22 @@ protected function setAlphaChannel(&$imagickImage) { } else { $this->applyOpacity($imagickImage); } + + // Attempt to make the center space transparent + $this->makeCenterTransparent($imagickImage); + } + + protected function makeCenterTransparent(IMagick &$imagickImage) { + // Ensure alpha channel is active before painting + $imagickImage->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE); + + // Paint exact black pixels fully transparent + $imagickImage->transparentPaintImage( + new ImagickPixel('black'), + 0.0, // target alpha: 0.0 = fully transparent + 0, // no fuzz: exact black only + false // don't invert (only affect matching pixels) + ); } protected function applyOpacity(IMagick &$imagickImage) { diff --git a/src/Module/Movies.php b/src/Module/Movies.php index 6e81d2079..c58d6f913 100644 --- a/src/Module/Movies.php +++ b/src/Module/Movies.php @@ -121,8 +121,8 @@ public function postMovie() { // Limit movies to three layers $layers = new Helper_HelioviewerLayers($json_params['layers']); - if ( $layers->length() < 1 || $layers->length() > 3 ) { - throw new Exception('Invalid layer choices! You must specify 1-3 comma-separated layer names.', 22); + if ( $layers->length() < 1 || $layers->length() > 5 ) { + throw new Exception('Invalid layer choices! You must specify 1-5 comma-separated layer names.', 22); } $events_manager = EventsStateManager::buildFromEventsState($json_params['eventsState']); @@ -294,8 +294,8 @@ public function queueMovie() { // Limit movies to three layers $layers = new Helper_HelioviewerLayers($this->_params['layers']); - if ( $layers->length() < 1 || $layers->length() > 3 ) { - throw new Exception('Invalid layer choices! You must specify 1-3 comma-separated layer names.', 22); + if ( $layers->length() < 1 || $layers->length() > 5 ) { + throw new Exception('Invalid layer choices! You must specify 1-5 comma-separated layer names.', 22); } // Event legacy string @@ -540,9 +540,9 @@ public function reQueueMovie($silent=false) { // Limit movies to three layers $layers = new Helper_HelioviewerLayers($movie['dataSourceString']); - if ( $layers->length() < 1 || $layers->length() > 3 ) { + if ( $layers->length() < 1 || $layers->length() > 5 ) { throw new Exception( - 'Invalid layer choices! You must specify 1-3 comma-separated '. + 'Invalid layer choices! You must specify 1-5 comma-separated '. 'layer names.', 22); } diff --git a/tests/unit_tests/validation/test_data/post_screenshot/valid/post_screenshot_punch.json b/tests/unit_tests/validation/test_data/post_screenshot/valid/post_screenshot_punch.json new file mode 100644 index 000000000..3b27a817c --- /dev/null +++ b/tests/unit_tests/validation/test_data/post_screenshot/valid/post_screenshot_punch.json @@ -0,0 +1 @@ +{"imageScale":210.11840895833043,"layers":"[PUNCH,WFI+NFI,Polarized Brightness,1,100,0,60,1,2026-04-22T14:59:57.000Z]","eventsState":{"tree_HEK":{"id":"HEK","visible":true,"markers_visible":true,"labels_visible":true,"layer_available_visible":true,"layers":[],"layers_v2":[]},"tree_CCMC":{"id":"CCMC","visible":true,"markers_visible":true,"labels_visible":true,"layer_available_visible":true,"layers":[],"layers_v2":[]},"tree_RHESSI":{"id":"RHESSI","visible":true,"markers_visible":true,"labels_visible":true,"layer_available_visible":true,"layers":[],"layers_v2":[]}},"scale":true,"scaleType":"earth","scaleX":28371,"scaleY":109542,"movieIcons":false,"date":"2026-04-22T15:59:57.497Z","display":false,"switchSources":false,"celestialBodiesLabels":"","celestialBodiesTrajectories":"","x1":-200172.4494534283,"x2":161231.21395490004,"y1":-126909.19775643574,"y2":133217.39253397734}