Loading src/gui.py +81 −39 Original line number Diff line number Diff line Loading @@ -61,17 +61,34 @@ class Ui(QtWidgets.QMainWindow): policy = QtWidgets.QSizePolicy.Policy gb_img:QtWidgets.QWidget = self.findChild(QtWidgets.QGroupBox, 'gb_img') if 'filename' in df.columns and 'fits' in df.columns: splitter_img = QtWidgets.QSplitter(Qt.Horizontal) splitter_img.splitterMoved.connect(self.splitterResizeEvent) else: splitter_img = QtWidgets.QSplitter(Qt.Horizontal) splitter_main = QtWidgets.QSplitter(Qt.Vertical) splitter_main.splitterMoved.connect(self.splitterResizeEvent) splitter_main.addWidget(splitter_img) gb_img.layout().addWidget(splitter_main) if 'fits' in df.columns: # Add clickable image widget widget = widgets.ClickableImage() gb_img.layout().addWidget(widget, 0, 0, 1, wcols/2) # Full row width wcount += wcols/2 splitter_img.addWidget(widget) #gb_img.layout().addWidget(widget, 0, 0, 1, wcols/2) # Full row width wcount += wcols # Save widget: self.widget_clickimg = widget if 'filename' in df.columns: # Add image container widget widget = widgets.StaticImage() gb_img.layout().addWidget(widget, wcount//wcols, wcount%wcols, 1, wcols/2) # Full row width wcount += wcols/2 splitter_img.addWidget(widget) #gb_img.layout().addWidget(widget, wcount//wcols, wcount%wcols, 1, wcols/2) # Full row width wcount += wcols # Save widget: self.defaultImgPath = Path("res") / Path("image_not_found.png") self.widget_staticimg = widget Loading @@ -83,7 +100,12 @@ class Ui(QtWidgets.QMainWindow): self.cb = {} # checkboxs # self.cbColumns, self.cbNames = [], [] self.tb = {} # textbox # Add dynamic widgets frame = QtWidgets.QFrame() frame.setLayout(QtWidgets.QGridLayout()) splitter_main.addWidget(frame) for tconf in config: tconf:dict cols = 2 # default cols inside each widget Loading Loading @@ -203,7 +225,8 @@ class Ui(QtWidgets.QMainWindow): else: print(f"{tconf['type']} not implemented") gb_img.layout().addWidget(gb, wcount//wcols, wcount%wcols) # gb_img.layout().addWidget(gb, wcount//wcols, wcount%wcols) frame.layout().addWidget(gb, wcount//wcols, wcount%wcols) wcount += 1 except: raise Loading @@ -211,7 +234,10 @@ class Ui(QtWidgets.QMainWindow): # Dynamic COLUMNS utils.IDS['FILE'] = list(df.columns) utils.IDS['WIDGET'] = ['fits_coords'] utils.IDS['WIDGET'] = [] if 'fits' in df.columns: utils.IDS['WIDGET'].extend(['fits_coords']) utils.IDS['TB'] = list(self.tb.keys()) for _, cb in self.cb.items(): Loading @@ -224,11 +250,14 @@ class Ui(QtWidgets.QMainWindow): utils.COLUMNS = utils.IDS['FILE'] + utils.IDS['WIDGET'] + utils.IDS['RBG'] + utils.IDS['CB'] + utils.IDS['TB'] for v in ['ra', 'dec']: try: if v in utils.COLUMNS: utils.COLUMNS.remove(v) except ValueError: pass utils.COLUMNS.extend(['processed', 'fullpath', 'ra', 'dec']) utils.COLUMNS.extend(['processed']) if 'filename' in df.columns: utils.COLUMNS.extend(['fullpath']) if 'ra' in df.columns and 'dec' in df.columns: utils.COLUMNS.extend(['ra', 'dec']) ## OLD # Find checkboxes: Loading Loading @@ -274,15 +303,21 @@ class Ui(QtWidgets.QMainWindow): self.showImage(self.imgPath) # img appears streched, so refresh img # List helpers: def create_table_item(self, value, role=Qt.DisplayRole): item = QtWidgets.QTableWidgetItem() item.setData(role, value) return item def fillList(self, showAllSavedData:bool = False) -> None: header = ['Group', 'Galaxy', 'Processed', 'Ra', 'Dec', 'Filename'] if not self.has_groups: header.remove('Group') header_full = ['Group', 'Galaxy', 'Processed', 'Ra', 'Dec', 'Filename', 'Fits'] # base header header_valids =[h.capitalize() for h in utils.IDS['FILE'] + ['Processed']] # input file cols + 'Processed' header = [h for h in header_full if h in header_valids] # contruct header #self.fileList.setRowCount(len(self.df.index)) self.fileList.setColumnCount(len(header)) self.fileList.setHorizontalHeaderLabels(header) self.fileList.horizontalHeaderItem(2).setTextAlignment(Qt.AlignmentFlag.AlignHCenter) self.fileList.horizontalHeaderItem(header.index('Processed')).setTextAlignment(Qt.AlignmentFlag.AlignHCenter) # Make table not editable: self.fileList.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) Loading @@ -303,25 +338,29 @@ class Ui(QtWidgets.QMainWindow): c = 0 # Place group data in integer format: if self.has_groups: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, int(row['group'])) self.fileList.setItem(i, c, self.create_table_item(int(row['group']))) c += 1 # Place galaxy data in integer format: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, int(row['galaxy'])) self.fileList.setItem(i, c, self.create_table_item(int(row['galaxy']))) c += 1 # Set Icon in the row: self.fileList.setCellWidget(i, c, self.getIconCell(row['processed'])) c += 1 #Set RA and Dec data: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, float(row['ra'])) if 'ra' in row and 'dec' in row: self.fileList.setItem(i, c, self.create_table_item(float(row['ra']))) c += 1 self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, float(row['dec'])) self.fileList.setItem(i, c, self.create_table_item(float(row['dec']))) c += 1 # Place filename in the row: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem(str(row['filename']))) if 'filename' in row: self.fileList.setItem(i, c, self.create_table_item(str(row['filename']))) c += 1 # Place fits in the row: if 'fits' in row: self.fileList.setItem(i, c, self.create_table_item(str(row['fits']))) c += 1 if self.has_groups: if (not showAllSavedData) and (int(row['group']) not in self.groups): Loading Loading @@ -426,7 +465,10 @@ class Ui(QtWidgets.QMainWindow): item = self.df.loc[item_index] if 'fullpath' in item: self.imgPath = item['fullpath'].item() else: self.imgPath = None windowTitle = '' if self.has_groups: Loading Loading @@ -476,7 +518,7 @@ class Ui(QtWidgets.QMainWindow): # self.commentBox.setPlainText('') if hasattr(self, 'widget_clickimg'): fname = item['fullpath'].item().parent / Path(item['fits'].item()) fname = Path(utils.args.path) / Path(item['fits'].item()) coords = item['fits_coords'].item() self.widget_clickimg.new_file(fname, coords) Loading src/gui.ui +1 −1 Original line number Diff line number Diff line Loading @@ -151,7 +151,7 @@ <property name="title"> <string>Image</string> </property> <layout class="QGridLayout" name="gridLayout"/> <layout class="QVBoxLayout" name="imgLayout"/> </widget> </widget> </item> Loading src/utils.py +51 −39 Original line number Diff line number Diff line Loading @@ -40,8 +40,8 @@ def getOptions(version): help="List selected files only and exit.\n") parser.add_argument('-i', '--inputfile', default='galaxies.csv', type=dir_file, help="""Galaxy database file in *.csv format. Minimum required columns: ['galaxy', 'ra', 'dec']. Recomended columns: ['group', 'galaxy', 'ra', 'dec', 'filename']""") Minimum required columns: ['galaxy', 'filename' or 'fits' ]. Recomended columns: ['group', 'galaxy', 'ra', 'dec', 'filename' or 'fits']""") parser.add_argument('group', metavar='GROUP', type=int, nargs='*', help="Group number. Selects images with name format: img_<group>_*.png\n") Loading Loading @@ -141,19 +141,22 @@ def readInputFile(fname:str) -> pd.DataFrame: sortby.insert(0, 'group') df = df.sort_values(by=sortby) if 'filename' not in df.columns: imgpath = Path(args.path) df['filename'] = '' for i, row in df.iterrows(): if 'group' in df.columns: imgfile = f'img_{row.group}_{row.galaxy}.*' if 'filename' in df.columns: not_found = sum(df['filename']=='') else: imgfile = f'img_*_{row.galaxy}.*' image = glob.glob(f"{imgpath.absolute()}/{imgfile}") if len(image)>0: df.loc[i, 'filename'] = image[0] not_found = 0 # imgpath = Path(args.path) # df['filename'] = '' # for i, row in df.iterrows(): # if 'group' in df.columns: # imgfile = f'img_{row.group}_{row.galaxy}.*' # else: # imgfile = f'img_*_{row.galaxy}.*' # image = glob.glob(f"{imgpath.absolute()}/{imgfile}") # if len(image)>0: # df.loc[i, 'filename'] = image[0] not_found = sum(df['filename']=='') if not_found >0: print(f'\nWARNING: {not_found} images where not found. Check if the provided path is correct. Or download the images using the provided tool.') else: Loading Loading @@ -257,7 +260,9 @@ def expand_df(selectedFiles): checkColumnsMismatch(importData.columns.values) importData['processed'] = True if 'filename' in importData.columns: importData['fullpath'] = '' if 'ra' in importData.columns and 'dec' in importData.columns: importData['ra'] = 0 importData['dec'] = 0 Loading @@ -265,11 +270,13 @@ def expand_df(selectedFiles): try: # Saved and selected data: for i, row in selectedFiles.iterrows(): file = Path(args.path) / Path(row['filename']) item = importData.loc[importData.galaxy == row.galaxy] # If selected row is imported in savefile: if (item.size > 0): if 'filename' in importData.columns: file = Path(args.path) / Path(row['filename']) importData.loc[importData.galaxy == row.galaxy, 'fullpath'] = file.absolute() if 'ra' in importData.columns and 'dec' in importData.columns: importData.loc[importData.galaxy == row.galaxy, 'ra'] = row.ra importData.loc[importData.galaxy == row.galaxy, 'dec'] = row.dec # If selected row is not in savefile: Loading @@ -279,10 +286,12 @@ def expand_df(selectedFiles): # Add the full path to imported but unselected data: processedUnselectedData = importData[importData.fullpath == ''] for i, row in processedUnselectedData.iterrows(): if 'filename' in importData.columns: file = Path(args.path) / Path(row['filename']) importData.loc[importData.galaxy == row.galaxy, 'fullpath'] = file.absolute() if 'ra' in importData.columns and 'dec' in importData.columns: ra = groups.loc[groups.galaxy == row.galaxy].ra.item() dec = groups.loc[groups.galaxy == row.galaxy].dec.item() importData.loc[importData.galaxy == row.galaxy, 'fullpath'] = file.absolute() importData.loc[importData.galaxy == row.galaxy, 'ra'] = ra importData.loc[importData.galaxy == row.galaxy, 'dec'] = dec Loading @@ -298,11 +307,11 @@ def expand_df(selectedFiles): def newEntry(row:pd.Series) -> dict: entry = {'galaxy': row.galaxy} if 'filename' in row: file = Path(args.path) / Path(row['filename']) entry = { 'galaxy': row.galaxy, 'filename': file.name, } entry.update({'filename': file.name}) # default value if 'group' in row: entry.update({'group': row.group}) Loading @@ -321,14 +330,17 @@ def newEntry(row:pd.Series) -> dict: entry.update({'fits': row.fits}) entry.update({'fits_coords': []}) entry.update( { 'processed': False, 'fullpath': file.absolute(), entry.update({'processed': False}) if 'filename' in row: entry.update({'fullpath': file.absolute()}) if 'ra' in row and 'dec' in row: entry.update({ 'ra': row.ra, 'dec': row.dec } ) }) return entry Loading Loading
src/gui.py +81 −39 Original line number Diff line number Diff line Loading @@ -61,17 +61,34 @@ class Ui(QtWidgets.QMainWindow): policy = QtWidgets.QSizePolicy.Policy gb_img:QtWidgets.QWidget = self.findChild(QtWidgets.QGroupBox, 'gb_img') if 'filename' in df.columns and 'fits' in df.columns: splitter_img = QtWidgets.QSplitter(Qt.Horizontal) splitter_img.splitterMoved.connect(self.splitterResizeEvent) else: splitter_img = QtWidgets.QSplitter(Qt.Horizontal) splitter_main = QtWidgets.QSplitter(Qt.Vertical) splitter_main.splitterMoved.connect(self.splitterResizeEvent) splitter_main.addWidget(splitter_img) gb_img.layout().addWidget(splitter_main) if 'fits' in df.columns: # Add clickable image widget widget = widgets.ClickableImage() gb_img.layout().addWidget(widget, 0, 0, 1, wcols/2) # Full row width wcount += wcols/2 splitter_img.addWidget(widget) #gb_img.layout().addWidget(widget, 0, 0, 1, wcols/2) # Full row width wcount += wcols # Save widget: self.widget_clickimg = widget if 'filename' in df.columns: # Add image container widget widget = widgets.StaticImage() gb_img.layout().addWidget(widget, wcount//wcols, wcount%wcols, 1, wcols/2) # Full row width wcount += wcols/2 splitter_img.addWidget(widget) #gb_img.layout().addWidget(widget, wcount//wcols, wcount%wcols, 1, wcols/2) # Full row width wcount += wcols # Save widget: self.defaultImgPath = Path("res") / Path("image_not_found.png") self.widget_staticimg = widget Loading @@ -83,7 +100,12 @@ class Ui(QtWidgets.QMainWindow): self.cb = {} # checkboxs # self.cbColumns, self.cbNames = [], [] self.tb = {} # textbox # Add dynamic widgets frame = QtWidgets.QFrame() frame.setLayout(QtWidgets.QGridLayout()) splitter_main.addWidget(frame) for tconf in config: tconf:dict cols = 2 # default cols inside each widget Loading Loading @@ -203,7 +225,8 @@ class Ui(QtWidgets.QMainWindow): else: print(f"{tconf['type']} not implemented") gb_img.layout().addWidget(gb, wcount//wcols, wcount%wcols) # gb_img.layout().addWidget(gb, wcount//wcols, wcount%wcols) frame.layout().addWidget(gb, wcount//wcols, wcount%wcols) wcount += 1 except: raise Loading @@ -211,7 +234,10 @@ class Ui(QtWidgets.QMainWindow): # Dynamic COLUMNS utils.IDS['FILE'] = list(df.columns) utils.IDS['WIDGET'] = ['fits_coords'] utils.IDS['WIDGET'] = [] if 'fits' in df.columns: utils.IDS['WIDGET'].extend(['fits_coords']) utils.IDS['TB'] = list(self.tb.keys()) for _, cb in self.cb.items(): Loading @@ -224,11 +250,14 @@ class Ui(QtWidgets.QMainWindow): utils.COLUMNS = utils.IDS['FILE'] + utils.IDS['WIDGET'] + utils.IDS['RBG'] + utils.IDS['CB'] + utils.IDS['TB'] for v in ['ra', 'dec']: try: if v in utils.COLUMNS: utils.COLUMNS.remove(v) except ValueError: pass utils.COLUMNS.extend(['processed', 'fullpath', 'ra', 'dec']) utils.COLUMNS.extend(['processed']) if 'filename' in df.columns: utils.COLUMNS.extend(['fullpath']) if 'ra' in df.columns and 'dec' in df.columns: utils.COLUMNS.extend(['ra', 'dec']) ## OLD # Find checkboxes: Loading Loading @@ -274,15 +303,21 @@ class Ui(QtWidgets.QMainWindow): self.showImage(self.imgPath) # img appears streched, so refresh img # List helpers: def create_table_item(self, value, role=Qt.DisplayRole): item = QtWidgets.QTableWidgetItem() item.setData(role, value) return item def fillList(self, showAllSavedData:bool = False) -> None: header = ['Group', 'Galaxy', 'Processed', 'Ra', 'Dec', 'Filename'] if not self.has_groups: header.remove('Group') header_full = ['Group', 'Galaxy', 'Processed', 'Ra', 'Dec', 'Filename', 'Fits'] # base header header_valids =[h.capitalize() for h in utils.IDS['FILE'] + ['Processed']] # input file cols + 'Processed' header = [h for h in header_full if h in header_valids] # contruct header #self.fileList.setRowCount(len(self.df.index)) self.fileList.setColumnCount(len(header)) self.fileList.setHorizontalHeaderLabels(header) self.fileList.horizontalHeaderItem(2).setTextAlignment(Qt.AlignmentFlag.AlignHCenter) self.fileList.horizontalHeaderItem(header.index('Processed')).setTextAlignment(Qt.AlignmentFlag.AlignHCenter) # Make table not editable: self.fileList.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) Loading @@ -303,25 +338,29 @@ class Ui(QtWidgets.QMainWindow): c = 0 # Place group data in integer format: if self.has_groups: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, int(row['group'])) self.fileList.setItem(i, c, self.create_table_item(int(row['group']))) c += 1 # Place galaxy data in integer format: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, int(row['galaxy'])) self.fileList.setItem(i, c, self.create_table_item(int(row['galaxy']))) c += 1 # Set Icon in the row: self.fileList.setCellWidget(i, c, self.getIconCell(row['processed'])) c += 1 #Set RA and Dec data: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, float(row['ra'])) if 'ra' in row and 'dec' in row: self.fileList.setItem(i, c, self.create_table_item(float(row['ra']))) c += 1 self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem()) self.fileList.item(i, c).setData(Qt.DisplayRole, float(row['dec'])) self.fileList.setItem(i, c, self.create_table_item(float(row['dec']))) c += 1 # Place filename in the row: self.fileList.setItem(i, c, QtWidgets.QTableWidgetItem(str(row['filename']))) if 'filename' in row: self.fileList.setItem(i, c, self.create_table_item(str(row['filename']))) c += 1 # Place fits in the row: if 'fits' in row: self.fileList.setItem(i, c, self.create_table_item(str(row['fits']))) c += 1 if self.has_groups: if (not showAllSavedData) and (int(row['group']) not in self.groups): Loading Loading @@ -426,7 +465,10 @@ class Ui(QtWidgets.QMainWindow): item = self.df.loc[item_index] if 'fullpath' in item: self.imgPath = item['fullpath'].item() else: self.imgPath = None windowTitle = '' if self.has_groups: Loading Loading @@ -476,7 +518,7 @@ class Ui(QtWidgets.QMainWindow): # self.commentBox.setPlainText('') if hasattr(self, 'widget_clickimg'): fname = item['fullpath'].item().parent / Path(item['fits'].item()) fname = Path(utils.args.path) / Path(item['fits'].item()) coords = item['fits_coords'].item() self.widget_clickimg.new_file(fname, coords) Loading
src/gui.ui +1 −1 Original line number Diff line number Diff line Loading @@ -151,7 +151,7 @@ <property name="title"> <string>Image</string> </property> <layout class="QGridLayout" name="gridLayout"/> <layout class="QVBoxLayout" name="imgLayout"/> </widget> </widget> </item> Loading
src/utils.py +51 −39 Original line number Diff line number Diff line Loading @@ -40,8 +40,8 @@ def getOptions(version): help="List selected files only and exit.\n") parser.add_argument('-i', '--inputfile', default='galaxies.csv', type=dir_file, help="""Galaxy database file in *.csv format. Minimum required columns: ['galaxy', 'ra', 'dec']. Recomended columns: ['group', 'galaxy', 'ra', 'dec', 'filename']""") Minimum required columns: ['galaxy', 'filename' or 'fits' ]. Recomended columns: ['group', 'galaxy', 'ra', 'dec', 'filename' or 'fits']""") parser.add_argument('group', metavar='GROUP', type=int, nargs='*', help="Group number. Selects images with name format: img_<group>_*.png\n") Loading Loading @@ -141,19 +141,22 @@ def readInputFile(fname:str) -> pd.DataFrame: sortby.insert(0, 'group') df = df.sort_values(by=sortby) if 'filename' not in df.columns: imgpath = Path(args.path) df['filename'] = '' for i, row in df.iterrows(): if 'group' in df.columns: imgfile = f'img_{row.group}_{row.galaxy}.*' if 'filename' in df.columns: not_found = sum(df['filename']=='') else: imgfile = f'img_*_{row.galaxy}.*' image = glob.glob(f"{imgpath.absolute()}/{imgfile}") if len(image)>0: df.loc[i, 'filename'] = image[0] not_found = 0 # imgpath = Path(args.path) # df['filename'] = '' # for i, row in df.iterrows(): # if 'group' in df.columns: # imgfile = f'img_{row.group}_{row.galaxy}.*' # else: # imgfile = f'img_*_{row.galaxy}.*' # image = glob.glob(f"{imgpath.absolute()}/{imgfile}") # if len(image)>0: # df.loc[i, 'filename'] = image[0] not_found = sum(df['filename']=='') if not_found >0: print(f'\nWARNING: {not_found} images where not found. Check if the provided path is correct. Or download the images using the provided tool.') else: Loading Loading @@ -257,7 +260,9 @@ def expand_df(selectedFiles): checkColumnsMismatch(importData.columns.values) importData['processed'] = True if 'filename' in importData.columns: importData['fullpath'] = '' if 'ra' in importData.columns and 'dec' in importData.columns: importData['ra'] = 0 importData['dec'] = 0 Loading @@ -265,11 +270,13 @@ def expand_df(selectedFiles): try: # Saved and selected data: for i, row in selectedFiles.iterrows(): file = Path(args.path) / Path(row['filename']) item = importData.loc[importData.galaxy == row.galaxy] # If selected row is imported in savefile: if (item.size > 0): if 'filename' in importData.columns: file = Path(args.path) / Path(row['filename']) importData.loc[importData.galaxy == row.galaxy, 'fullpath'] = file.absolute() if 'ra' in importData.columns and 'dec' in importData.columns: importData.loc[importData.galaxy == row.galaxy, 'ra'] = row.ra importData.loc[importData.galaxy == row.galaxy, 'dec'] = row.dec # If selected row is not in savefile: Loading @@ -279,10 +286,12 @@ def expand_df(selectedFiles): # Add the full path to imported but unselected data: processedUnselectedData = importData[importData.fullpath == ''] for i, row in processedUnselectedData.iterrows(): if 'filename' in importData.columns: file = Path(args.path) / Path(row['filename']) importData.loc[importData.galaxy == row.galaxy, 'fullpath'] = file.absolute() if 'ra' in importData.columns and 'dec' in importData.columns: ra = groups.loc[groups.galaxy == row.galaxy].ra.item() dec = groups.loc[groups.galaxy == row.galaxy].dec.item() importData.loc[importData.galaxy == row.galaxy, 'fullpath'] = file.absolute() importData.loc[importData.galaxy == row.galaxy, 'ra'] = ra importData.loc[importData.galaxy == row.galaxy, 'dec'] = dec Loading @@ -298,11 +307,11 @@ def expand_df(selectedFiles): def newEntry(row:pd.Series) -> dict: entry = {'galaxy': row.galaxy} if 'filename' in row: file = Path(args.path) / Path(row['filename']) entry = { 'galaxy': row.galaxy, 'filename': file.name, } entry.update({'filename': file.name}) # default value if 'group' in row: entry.update({'group': row.group}) Loading @@ -321,14 +330,17 @@ def newEntry(row:pd.Series) -> dict: entry.update({'fits': row.fits}) entry.update({'fits_coords': []}) entry.update( { 'processed': False, 'fullpath': file.absolute(), entry.update({'processed': False}) if 'filename' in row: entry.update({'fullpath': file.absolute()}) if 'ra' in row and 'dec' in row: entry.update({ 'ra': row.ra, 'dec': row.dec } ) }) return entry Loading