def __init__(self, address, detz_offset=575, check_beam_status=True, verbose=False, delta_k=0.0, override_energy=None, **kwds): """The mod_event_info class constructor stores the parameters passed from the pyana configuration file in instance variables. @param address Full data source address of the DAQ device @param detz_offset The distance from the interaction region to the back of the detector stage, in mm @param check_beam_status Flag used to skip checking the beam parameters @param delta_k Correction to the K value used when calculating wavelength @param override_energy Use this energy instead of what is in the XTC stream """ logging.basicConfig() self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.INFO) # The subclasses accept keyword arguments; warn about any # unhandled arguments at the end of the inheritance chain. if len(kwds) > 0: self.logger.warning("Ignored unknown arguments: " + ", ".join(kwds)) # This is for messages that are picked up by Nat's monitoring program self.stats_logger = logging.getLogger("stats logger") handler = logging.StreamHandler() formatter = logging.Formatter('%(message)s') handler.setFormatter(formatter) self.stats_logger.addHandler(handler) self.stats_logger.removeHandler(self.stats_logger.handlers[0]) self.stats_logger.setLevel(logging.INFO) self._detz_offset = cspad_tbx.getOptFloat(detz_offset) self.delta_k = cspad_tbx.getOptFloat(delta_k) self.override_energy = cspad_tbx.getOptFloat(override_energy) self.address = cspad_tbx.getOptString(address) self.verbose = cspad_tbx.getOptBool(verbose) self.check_beam_status = cspad_tbx.getOptBool(check_beam_status) self.distance = None self.sifoil = None self.wavelength = None # The current wavelength - set by self.event() self.laser_1_status = laser_status(laser_id=1) self.laser_4_status = laser_status(laser_id=4)
def __init__(self, address, n_collate=None, n_update=120, common_mode_correction="none", wait=None, photon_counting=False, sigma_scaling=False, **kwds): """The mod_view class constructor XXX. @param address Full data source address of the DAQ device @param calib_dir Directory with calibration information @param common_mode_correction The type of common mode correction to apply @param dark_path Path to input average dark image @param dark_stddev Path to input standard deviation dark image, required if @p dark_path is given @param wait Minimum time (in seconds) to wait on the current image before moving on to the next @param n_collate Number of shots to average, or <= 0 to average all shots @param n_update Number of shots between updates """ super(mod_view, self).__init__(address=address, common_mode_correction=common_mode_correction, **kwds) self.detector = cspad_tbx.address_split(address)[0] self.nvalid = 0 self.ncollate = cspad_tbx.getOptInteger(n_collate) self.nupdate = cspad_tbx.getOptInteger(n_update) self.photon_counting = cspad_tbx.getOptBool(photon_counting) self.sigma_scaling = cspad_tbx.getOptBool(sigma_scaling) if (self.ncollate is None): self.ncollate = self.nupdate if (self.ncollate > self.nupdate): self.ncollate = self.nupdate self.logger.warning("n_collate capped to %d" % self.nupdate) linger = True # XXX Make configurable wait = cspad_tbx.getOptFloat(wait) # Create a managed FIFO queue shared between the viewer and the # current process. The current process will produce images, while # the viewer process will consume them. manager = multiprocessing.Manager() self._queue = manager.Queue() self._proc = multiprocessing.Process(target=_xray_frame_process, args=(self._queue, linger, wait)) self._proc.start() self.n_shots = 0
def __init__(self, address, n_collate = None, n_update = 120, common_mode_correction = "none", wait=None, photon_counting=False, sigma_scaling=False, **kwds): """The mod_view class constructor XXX. @param address Full data source address of the DAQ device @param calib_dir Directory with calibration information @param common_mode_correction The type of common mode correction to apply @param dark_path Path to input average dark image @param dark_stddev Path to input standard deviation dark image, required if @p dark_path is given @param wait Minimum time (in seconds) to wait on the current image before moving on to the next @param n_collate Number of shots to average, or <= 0 to average all shots @param n_update Number of shots between updates """ super(mod_view, self).__init__( address=address, common_mode_correction=common_mode_correction, **kwds) self.detector = cspad_tbx.address_split(address)[0] self.nvalid = 0 self.ncollate = cspad_tbx.getOptInteger(n_collate) self.nupdate = cspad_tbx.getOptInteger(n_update) self.photon_counting = cspad_tbx.getOptBool(photon_counting) self.sigma_scaling = cspad_tbx.getOptBool(sigma_scaling) if (self.ncollate is None): self.ncollate = self.nupdate if (self.ncollate > self.nupdate): self.ncollate = self.nupdate self.logger.warning("n_collate capped to %d" % self.nupdate) linger = True # XXX Make configurable wait = cspad_tbx.getOptFloat(wait) # Create a managed FIFO queue shared between the viewer and the # current process. The current process will produce images, while # the viewer process will consume them. manager = multiprocessing.Manager() self._queue = manager.Queue() self._proc = multiprocessing.Process( target=_xray_frame_process, args=(self._queue, linger, wait)) self._proc.start() self.n_shots = 0
def __init__(self, address, detz_offset=575, check_beam_status=True, verbose=False, delta_k=0.0, override_energy=None, **kwds): """The mod_event_info class constructor stores the parameters passed from the pyana configuration file in instance variables. @param address Full data source address of the DAQ device @param detz_offset The distance from the interaction region to the back of the detector stage, in mm @param check_beam_status Flag used to skip checking the beam parameters @param delta_k Correction to the K value used when calculating wavelength @param override_energy Use this energy instead of what is in the XTC stream """ logging.basicConfig() self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.INFO) # The subclasses accept keyword arguments; warn about any # unhandled arguments at the end of the inheritance chain. if len(kwds) > 0: self.logger.warning("Ignored unknown arguments: " + ", ".join(kwds.keys())) # This is for messages that are picked up by Nat's monitoring program self.stats_logger = logging.getLogger("stats logger") handler = logging.StreamHandler() formatter = logging.Formatter('%(message)s') handler.setFormatter(formatter) self.stats_logger.addHandler(handler) self.stats_logger.removeHandler(self.stats_logger.handlers[0]) self.stats_logger.setLevel(logging.INFO) self._detz_offset = cspad_tbx.getOptFloat(detz_offset) self.delta_k = cspad_tbx.getOptFloat(delta_k) self.override_energy = cspad_tbx.getOptFloat(override_energy) self.address = cspad_tbx.getOptString(address) self.verbose = cspad_tbx.getOptBool(verbose) self.check_beam_status = cspad_tbx.getOptBool(check_beam_status) self.distance = None self.sifoil = None self.wavelength = None # The current wavelength - set by self.event() self.laser_1_status = laser_status(laser_id=1) self.laser_4_status = laser_status(laser_id=4)
def __init__(self, address, display, mat_path, table_path, template_path=None, **kwds): """The mod_average class constructor stores the parameters passed from the pyana configuration file in instance variables. All parameters, except @p address are optional, and hence need not be defined in pyana.cfg. @param address Address string XXX Que?! """ super(mod_ledge, self).__init__(address=address, **kwds) # XXX Should be forced to false if graphics unavailable (e.g. when # running on the cluster). self._display = cspad_tbx.getOptBool(display) # Use line buffering to allow tailing the output in real time. # XXX Make name configurable. self._mat_path = cspad_tbx.getOptString(mat_path) self._table_path = cspad_tbx.getOptString(table_path) # Ring buffers for history-keeping. self._history_injector_xyz = ring_buffer() self._history_spectrometer_xyz = ring_buffer() self._history_energy = ring_buffer() # Get the template for image registration. if template_path is not None: from libtbx import easy_pickle self._template = easy_pickle.load(template_path) else: self._template = None # Optionally, initialise the viewer with a custom callback, see # mod_view. The regions of interest are communicated to the # viewer through a shared multiprocessing array. XXX This needs a # bit more thought to come up with a sensible interface. if self._display: from mod_view import _xray_frame_process from multiprocessing import Array, Process, Manager from rstbx.viewer import display manager = Manager() self._queue = manager.Queue() self._proc = Process(target=_xray_frame_process, args=(self._queue, True, 0)) self._roi = Array("i", 15 * 4, lock=False) # XXX Make variable! display.user_callback = self._callback self._proc.start()
def __init__(self, address, calib_dir=None, common_mode_correction="none", photon_threshold=None, two_photon_threshold=None, dark_path=None, dark_stddev=None, mask_path=None, gain_map_path=None, gain_map_level=None, cache_image=True, roi=None, laser_1_status=None, laser_4_status=None, laser_wait_time=None, override_beam_x=None, override_beam_y=None, bin_size=None, crop_rayonix=False, **kwds): """The common_mode_correction class constructor stores the parameters passed from the pyana configuration file in instance variables. @param address Full data source address of the DAQ device @param calib_dir Directory with calibration information @param common_mode_correction The type of common mode correction to apply @param dark_path Path to input average dark image @param dark_stddev Path to input standard deviation dark image, required if @p dark_path is given @param mask_path Path to input mask. Pixels to mask out should be set to -2 @param gain_map_path Path to input gain map. Multiplied times the image. @param gain_map_level If set, all the '1' pixels in the gain_map are set to this multiplier and all the '0' pixels in the gain_map are set to '1'. If not set, use the values in the gain_map directly @param laser_1_status 0 or 1 to indicate that the laser should be off or on respectively @param laser_4_status 0 or 1 to indicate that the laser should be off or on respectively @param laser_wait_time Length of time in milliseconds to wait after a laser change of status to begin accepting images again. (rejection of images occurs immediately after status change). @param override_beam_x override value for x coordinate of beam center in pixels @param override_beam_y override value for y coordinate of beam center in pixels @param bin_size bin size for rayonix detector used to determin pixel size @param crop_rayonix whether to crop rayonix images such that image center is the beam center """ # Cannot use the super().__init__() construct here, because # common_mode_correction refers to the argument, and not the # class. mod_event_info.__init__(self, address=address, **kwds) # The paths will be substituted in beginjob(), where evt and env # are available. self._dark_path = cspad_tbx.getOptString(dark_path) self._dark_stddev_path = cspad_tbx.getOptString(dark_stddev) self._gain_map_path = cspad_tbx.getOptString(gain_map_path) self._mask_path = cspad_tbx.getOptString(mask_path) self.gain_map_level = cspad_tbx.getOptFloat(gain_map_level) self.common_mode_correction = cspad_tbx.getOptString(common_mode_correction) self.photon_threshold = cspad_tbx.getOptFloat(photon_threshold) self.two_photon_threshold = cspad_tbx.getOptFloat(two_photon_threshold) self.cache_image = cspad_tbx.getOptBool(cache_image) self.filter_laser_1_status = cspad_tbx.getOptInteger(laser_1_status) self.filter_laser_4_status = cspad_tbx.getOptInteger(laser_4_status) if self.filter_laser_1_status is not None: self.filter_laser_1_status = bool(self.filter_laser_1_status) if self.filter_laser_4_status is not None: self.filter_laser_4_status = bool(self.filter_laser_4_status) self.filter_laser_wait_time = cspad_tbx.getOptInteger(laser_wait_time) self.override_beam_x = cspad_tbx.getOptFloat(override_beam_x) self.override_beam_y = cspad_tbx.getOptFloat(override_beam_y) self.bin_size = cspad_tbx.getOptInteger(bin_size) self.crop_rayonix = cspad_tbx.getOptBool(crop_rayonix) self.cspad_img = None # The current image - set by self.event() self.sum_common_mode = 0 self.sumsq_common_mode = 0 self.roi = cspad_tbx.getOptROI(roi) # used to ignore the signal region in chebyshev fit assert self.common_mode_correction in \ ("gaussian", "mean", "median", "mode", "none", "chebyshev") # Get and parse metrology. self.sections = None device = cspad_tbx.address_split(self.address)[2] if device == 'Andor': self.sections = [] # XXX FICTION elif device == 'Cspad': if self.address == 'XppGon-0|Cspad-0': self.sections = [] # Not used for XPP else: self.sections = calib2sections(cspad_tbx.getOptString(calib_dir)) elif device == 'Cspad2x2': # There is no metrology information for the Sc1 detector, so # make it up. The sections are rotated by 90 degrees with # respect to the "standing up" convention. self.sections = [[Section(90, (185 / 2 + 0, (2 * 194 + 3) / 2)), Section(90, (185 / 2 + 185, (2 * 194 + 3) / 2))]] elif device == 'marccd': self.sections = [] # XXX FICTION elif device == 'pnCCD': self.sections = [] # XXX FICTION elif device == 'Rayonix': self.sections = [] # XXX FICTION elif device == 'Opal1000': self.sections = [] # XXX FICTION if self.sections is None: raise RuntimeError("Failed to load metrology")
def __init__(self, timestamps_path=None, timestamps_interval=None, negate="False"): """Extracts timestamps from the file whose name is the string pointed to by @p timestamps_path. @param timestamps_path Path to file containing timestamps @param timestamps_interval Comma-separated inclusive endpoints of a timestamp interval. The lower or the upper endpoint may be omitted, in which case it will be treated as -infinity or +infinity. @param negate Select shots not matching any of the timestamps """ self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.INFO) if (not ((timestamps_path is None) ^ (timestamps_interval is None))): raise RuntimeError( "Must specify either timestamps_path or timestamps_interval") self.negate = cspad_tbx.getOptBool(negate) if (timestamps_path is not None): p_old = re.compile("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\d{2}\.\d{3}") p_new = re.compile("\d{17}") f = open(timestamps_path, "r") self.timestamps_list = [] for line in f.readlines(): s = line.strip() if (len(s) == 0 or s[0] == "#"): continue for t in s.split(): m = p_old.findall(t) if len(m) == 0: m = p_new.findall(t) if (len(m) == 1): self.timestamps_list.append(self._ts2sms(m[0])) f.close() self.timestamps_interval = None else: try: s = timestamps_interval.split(",") if (len(s) == 1 or len(s) == 2 and len(s[1]) == 0): self.timestamps_interval = (self._ts2sms(s[0]), None) elif (len(s) == 2 and len(s[0]) == 0): self.timestamps_interval = (None, self._ts2sms(s[1])) elif (len(s) == 2): self.timestamps_interval = (self._ts2sms(s[0]), self._ts2sms(s[1])) else: raise ValueError() # Ensure lower endpoint is earlier than upper endpoint. if (self.timestamps_interval[0] is not None and self.timestamps_interval[1] is not None and self._timestamp_compar( self.timestamps_interval[0], self.timestamps_interval[1]) > 0): raise ValueError() except ValueError: raise RuntimeError("Failed to parse timestamp interval %s" % timestamps_interval) self.timestamps_list = None self.naccepted = 0 self.nshots = 0
def __init__( self, timestamps_path=None, timestamps_interval=None, negate="False"): """Extracts timestamps from the file whose name is the string pointed to by @p timestamps_path. @param timestamps_path Path to file containing timestamps @param timestamps_interval Comma-separated inclusive endpoints of a timestamp interval. The lower or the upper endpoint may be omitted, in which case it will be treated as -infinity or +infinity. @param negate Select shots not matching any of the timestamps """ self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.INFO) if (not((timestamps_path is None) ^ (timestamps_interval is None))): raise RuntimeError( "Must specify either timestamps_path or timestamps_interval") self.negate = cspad_tbx.getOptBool(negate) if (timestamps_path is not None): p_old = re.compile("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\d{2}\.\d{3}") p_new = re.compile("\d{17}") f = open(timestamps_path, "r") self.timestamps_list = [] for line in f.readlines(): s = line.strip() if (len(s) == 0 or s[0] == "#"): continue for t in s.split(): m = p_old.findall(t) if len(m) == 0: m = p_new.findall(t) if (len(m) == 1): self.timestamps_list.append(self._ts2sms(m[0])) f.close() self.timestamps_interval = None else: try: s = timestamps_interval.split(",") if (len(s) == 1 or len(s) == 2 and len(s[1]) == 0): self.timestamps_interval = (self._ts2sms(s[0]), None) elif (len(s) == 2 and len(s[0]) == 0): self.timestamps_interval = (None, self._ts2sms(s[1])) elif (len(s) == 2): self.timestamps_interval = (self._ts2sms(s[0]), self._ts2sms(s[1])) else: raise ValueError() # Ensure lower endpoint is earlier than upper endpoint. if (self.timestamps_interval[0] is not None and self.timestamps_interval[1] is not None and self._timestamp_compar(self.timestamps_interval[0], self.timestamps_interval[1]) > 0): raise ValueError() except ValueError: raise RuntimeError( "Failed to parse timestamp interval %s" % timestamps_interval) self.timestamps_list = None self.naccepted = 0 self.nshots = 0
def __init__(self, address, dispatch=None, integration_dirname=None, integration_basename=None, out_dirname=None, out_basename=None, roi=None, distl_min_peaks=None, distl_flags=None, threshold=None, xtal_target=None, negate_hits=False, trial_id=None, db_logging=False, progress_logging=False, sql_buffer_size=1, db_host=None, db_name=None, db_table_name=None, db_experiment_tag=None, db_user=None, db_password=None, db_tags=None, trial=None, rungroup_id=None, db_version='v1', **kwds): """The mod_hitfind class constructor stores the parameters passed from the pyana configuration file in instance variables. All parameters, except @p address are optional, and hence need not be defined in pyana.cfg. @param address Full data source address of the DAQ device @param dispatch Function to call @param integration_dirname Directory portion of output integration file pathname @param integration_basename Filename prefix of output integration file pathname @param out_dirname Directory portion of output image pathname @param out_basename Filename prefix of output image pathname @param roi Region of interest for thresholding, on the form fast_low:fast_high,slow_low:slow_high @param threshold Minimum value in region of interest to pass """ super(mod_hitfind, self).__init__(address=address, **kwds) self.m_dispatch = cspad_tbx.getOptString(dispatch) self.m_integration_basename = cspad_tbx.getOptString( integration_basename) self.m_integration_dirname = cspad_tbx.getOptString( integration_dirname) self.m_out_basename = cspad_tbx.getOptString(out_basename) self.m_out_dirname = cspad_tbx.getOptString(out_dirname) self.m_distl_min_peaks = cspad_tbx.getOptInteger(distl_min_peaks) self.m_distl_flags = cspad_tbx.getOptStrings(distl_flags) self.m_threshold = cspad_tbx.getOptInteger(threshold) self.m_xtal_target = cspad_tbx.getOptString(xtal_target) self.m_negate_hits = cspad_tbx.getOptBool(negate_hits) self.m_trial_id = cspad_tbx.getOptInteger(trial_id) self.m_db_logging = cspad_tbx.getOptBool(db_logging) self.m_progress_logging = cspad_tbx.getOptBool(progress_logging) self.m_sql_buffer_size = cspad_tbx.getOptInteger(sql_buffer_size) self.m_db_host = cspad_tbx.getOptString(db_host) self.m_db_name = cspad_tbx.getOptString(db_name) self.m_db_table_name = cspad_tbx.getOptString(db_table_name) self.m_db_experiment_tag = cspad_tbx.getOptString(db_experiment_tag) self.m_db_user = cspad_tbx.getOptString(db_user) self.m_db_password = cspad_tbx.getOptString(db_password) self.m_db_tags = cspad_tbx.getOptString(db_tags) self.m_trial = cspad_tbx.getOptInteger(trial) self.m_rungroup_id = cspad_tbx.getOptInteger(rungroup_id) self.m_db_version = cspad_tbx.getOptString(db_version) # A ROI should not contain any ASIC boundaries, as these are # noisy. Hence circular ROI:s around the beam centre are probably # not such a grand idea. self.m_roi = cspad_tbx.getOptROI(roi) # Verify that dist_min_peaks is either "restrictive" or # "permissive", but not both. ^ is the logical xor operator if self.m_distl_min_peaks is not None: if (not (('permissive' in self.m_distl_flags) ^ ('restrictive' in self.m_distl_flags))): raise RuntimeError("""Sorry, with the distl_min_peaks option, distl_flags must be set to 'permissive' or 'restrictive'.""") if (self.m_roi is not None): raise RuntimeError("""Sorry, either specify region of interest (roi) or distl_min_peaks, but not both.""") if self.m_db_logging: self.buffered_sql_entries = [] assert self.m_sql_buffer_size >= 1 if self.m_progress_logging: if self.m_db_version == 'v1': self.buffered_progress_entries = [] assert self.m_sql_buffer_size >= 1 self.isoforms = {} elif self.m_db_version == 'v2': from xfel.ui import db_phil_str from libtbx.phil import parse extra_phil = """ input { trial = None .type = int trial_id = None .type = int rungroup = None .type = int } """ self.db_params = parse(db_phil_str + extra_phil).extract() self.db_params.experiment_tag = self.m_db_experiment_tag self.db_params.db.host = self.m_db_host self.db_params.db.name = self.m_db_name self.db_params.db.user = self.m_db_user self.db_params.db.password = self.m_db_password self.db_params.input.trial = self.m_trial self.db_params.input.rungroup = self.m_rungroup_id if self.m_db_tags is None: self.m_db_tags = ""
def __init__(self, address, dispatch = None, integration_dirname = None, integration_basename = None, out_dirname = None, out_basename = None, roi = None, distl_min_peaks = None, distl_flags = None, threshold = None, xtal_target = None, negate_hits = False, trial_id = None, db_logging = False, progress_logging = False, sql_buffer_size = 1, db_host = None, db_name = None, db_table_name = None, db_experiment_tag = None, db_user = None, db_password = None, db_tags = None, rungroup_id = None, **kwds): """The mod_hitfind class constructor stores the parameters passed from the pyana configuration file in instance variables. All parameters, except @p address are optional, and hence need not be defined in pyana.cfg. @param address Full data source address of the DAQ device @param dispatch Function to call @param integration_dirname Directory portion of output integration file pathname @param integration_basename Filename prefix of output integration file pathname @param out_dirname Directory portion of output image pathname @param out_basename Filename prefix of output image pathname @param roi Region of interest for thresholding, on the form fast_low:fast_high,slow_low:slow_high @param threshold Minimum value in region of interest to pass """ super(mod_hitfind, self).__init__(address=address, **kwds) self.m_dispatch = cspad_tbx.getOptString(dispatch) self.m_integration_basename = cspad_tbx.getOptString(integration_basename) self.m_integration_dirname = cspad_tbx.getOptString(integration_dirname) self.m_out_basename = cspad_tbx.getOptString(out_basename) self.m_out_dirname = cspad_tbx.getOptString(out_dirname) self.m_distl_min_peaks = cspad_tbx.getOptInteger(distl_min_peaks) self.m_distl_flags = cspad_tbx.getOptStrings(distl_flags) self.m_threshold = cspad_tbx.getOptInteger(threshold) self.m_xtal_target = cspad_tbx.getOptString(xtal_target) self.m_negate_hits = cspad_tbx.getOptBool(negate_hits) self.m_trial_id = cspad_tbx.getOptInteger(trial_id) self.m_db_logging = cspad_tbx.getOptBool(db_logging) self.m_progress_logging = cspad_tbx.getOptBool(progress_logging) self.m_sql_buffer_size = cspad_tbx.getOptInteger(sql_buffer_size) self.m_db_host = cspad_tbx.getOptString(db_host) self.m_db_name = cspad_tbx.getOptString(db_name) self.m_db_table_name = cspad_tbx.getOptString(db_table_name) self.m_db_experiment_tag = cspad_tbx.getOptString(db_experiment_tag) self.m_db_user = cspad_tbx.getOptString(db_user) self.m_db_password = cspad_tbx.getOptString(db_password) self.m_db_tags = cspad_tbx.getOptString(db_tags) self.m_rungroup_id = cspad_tbx.getOptInteger(rungroup_id) # A ROI should not contain any ASIC boundaries, as these are # noisy. Hence circular ROI:s around the beam centre are probably # not such a grand idea. self.m_roi = cspad_tbx.getOptROI(roi) # Verify that dist_min_peaks is either "restrictive" or # "permissive", but not both. ^ is the logical xor operator if self.m_distl_min_peaks is not None: if (not (('permissive' in self.m_distl_flags) ^ ('restrictive' in self.m_distl_flags))): raise RuntimeError("""Sorry, with the distl_min_peaks option, distl_flags must be set to 'permissive' or 'restrictive'.""") if (self.m_roi is not None): raise RuntimeError("""Sorry, either specify region of interest (roi) or distl_min_peaks, but not both.""") if self.m_db_logging: self.buffered_sql_entries = [] assert self.m_sql_buffer_size >= 1 if self.m_progress_logging: self.buffered_progress_entries = [] assert self.m_sql_buffer_size >= 1 self.isoforms = {} if self.m_db_tags is None: self.m_db_tags = ""