Пример #1
0
def clearProcFiles():
    # Grab a reference to the existing logger.
    # This only works if the script calling this function has
    # already called mosHelper.setUpTheLogger().
    module_logger = logging.getLogger('mosgraphics.clearProc')

    dictDirNames = mosHelper.getDirNames()
    contents = os.listdir(dictDirNames['proc'])

    module_logger.info('Deleting %s processed files', len(contents))
    for fn in contents:
        fullname = os.path.join(dictDirNames['proc'], fn)
        os.remove(fullname)
Пример #2
0
def GrabEm():

    # Grab a reference to the existing logger.
    # This only works if the script calling this function has
    # already called mosHelper.setUpTheLogger().
    module_logger = logging.getLogger('mosgraphics.GrabEm')

    dictDirNames = mosHelper.getDirNames()

    moslist = ['MET', 'MEX', 'MAV']

    # Use this to keep track of which files were written and still need
    # to be processed with mosHelper.parseStations.
    rawfiles = []
    
    for mosname in moslist:
        tempObj = MOS(mosname)
        tempObj.set_filethresh()
        
        module_logger.info('Asking MDL for the {}'.format(mosname))
        status = tempObj.check_primary()

        if status is not 1:
            module_logger.warning('Lost the connection with MDL. File was not downloaded.')
            module_logger.info('Trying another server')
            status2 = tempObj.check_backup()
            
            if status2 is not 1:
                module_logger.warning('Bummer. Unable to download {}'.format(mosname))
            elif status2 is 1:
                module_logger.info('Success!')
            else:
                module_logger.info('? ? ? ? ? ?')

        if tempObj.fileurls is not None:
            for furl, localfilename in zip(tempObj.fileurls, tempObj.localfnames):
                # If there already exists a file with the intended localfilename,
                # check to see if it has an appropriate size. If the file seems
                # too small, then try downloading it again. Otherwise, don't bother
                # because it's probably OK.
                existingRawFiles = mosHelper.listRawFiles(mosname)
                if localfilename in existingRawFiles:
                    module_logger.info('{} already exists on disk.'.format(localfilename))
                    fpath = os.path.join(dictDirNames['raw'], localfilename)

                    # If the file size is too small, then something went wrong the
                    # last time the file was downloaded. Try to download it again
                    # now so that it will be available for the next script run.
                    thresh = tempObj.filethresh * 1000
                    if os.path.getsize(fpath) > thresh:
                        module_logger.info('Skipping. It\'s probably OK.')
                        # 'continue': the current iteration of the loop terminates
                        # and execution continues with the next iteration of the loop.
                        continue
                    else:
                        module_logger.info('Downloading. The copy on disk seems too small.')

                # Note that in order to reach this part of the script, the file
                # size must pass the above if-else.
                response = urllib2.urlopen(furl)
                contents = response.read()
                response.close()

                module_logger.info('Writing to %s', localfilename)
                fullname = os.path.join(dictDirNames['raw'], localfilename)
                output = open(fullname, 'w')
                output.write(contents)
                output.close()
                rawfiles.append(localfilename)
                    
        else:
            module_logger.info('A rolling stone gathers no {} MOS.'.format(mosname))

    return(rawfiles)
Пример #3
0
def cleanHouse():
    # Grab a reference to the existing logger.
    # This only works if the script calling this function has
    # already called mosHelper.setUpTheLogger().
    module_logger = logging.getLogger('mosgraphics.cleanHouse')

    dictDirNames = mosHelper.getDirNames()

    # Can probably rewrite mosplots.calc_dates based on the work here. Perhaps
    # in the ample free time with which all forecasters are blessed. Maybe use
    # xrange instead, also?
    #
    # Alright, here's the deal. The UTC date may be in the future compared to the
    # local date at times, so we'll toss in some negative numbers just to be sure the
    # early day runs (00z, 06z) won't get accidentally deleted.
    # Next, grab the current YYYY-MM-DD (local time) and use that as a starting point
    # from which to calculate which files to keep. Some of the filenames may not exist
    # yet, but that's OK. The important thing is that they won't be deleted. Yeah, that
    # makes total sense...
    hrsToKeep = {}
    hrsToKeep['MEX'] = [-24, -12, 0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 168, 180, 192, 204, 216]
    hrsToKeep['MAV'] = [-24, -18, -12, -6, 0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72]
    hrsToKeep['MET'] = [-36, -24, -12, 0, 12, 24, 36, 48, 60, 72, 84]
    #hrsToKeep['ECE'] = [-24, 0, 24, 48, 72, 96, 120, 144, 168, 192, 216]
    #hrsToKeep['ECS'] = [-24, 0, 24, 48, 72, 96]

    # Now. You're looking at now, sir. Everything that happens now, is happening now.
    # What happened to then?
    # We passed then.
    # When?
    # Just now. We're at now now.     
    rightnow = dt.datetime.now()

    # But not anymore! Choose 0 o'clock as a baseline. It makes the math easier.
    nowish = dt.datetime(year = rightnow.year, month = rightnow.month, day = rightnow.day, hour = 0)

    keyIter = hrsToKeep.iterkeys()

    # Loop over time to create filenames to keep
    for key in keyIter:
        keepfiles = []
        for hr in hrsToKeep[key]:
            mostype = key.lower()
            goback = dt.timedelta(hours = hr)
            prev = nowish - goback
            Y = prev.strftime('%Y')
            M = prev.strftime('%m')
            D = prev.strftime('%d')
            H = prev.strftime('%H')
            appendme = mosHelper.makeFilenames(mostype, 'ABCD', Y, M, D, H)['raw']
            keepfiles.append(appendme)
            
        keepfiles = set(keepfiles)
        
        # get the contents of the raw files directory for this mostype
        rawfiles = set(mosHelper.listRawFiles(mostype))
        
        # Suppose set1 = ([f1, f2, f3, etc.]) contains the filenames to keep, and
        # set2 = ([f0, f1, f2, f3, f4, f5, f6]) has the names of all raw files.
        # Then set2.difference(set1) is the set of files to delete.
        delme = rawfiles.difference(keepfiles)

        module_logger.info('%s are marked for deletion from %s', delme, mostype.upper())

        for fn in delme:
            fullname = os.path.join(dictDirNames['raw'], fn)
            os.remove(fullname)
Пример #4
0
def makeDisplayArrays(filename):
    # Given a filename of the expected form, figure out which previous
    # files are needed to construct complete arrays of all data needed
    # for the display of each configured fcst element. Load those files,
    # if they exist, and build the complete arrays. Then, construct display
    # arrays for each fcst element by selecting array elements from the complete
    # arrays based on mostype and model run date/time.
    # Returns the following:
    #   dispArr, a dictionary of display arrays with keys 'X', 'N', 'P12'
    #   dtXaxis, a dictionary with datetime objects to be used for creating x-axis labels
    #   infoDict, a dictionary returned by find_info (needed to construct the title and axes annotations)
    #   prevruns, a list of datetime objects (including the current run)
    #
    # 'filename' is a complete filename, including the extension, suitable for passing to load_file.
    # 'filename' should not include the file path b/c that is added during this function.

    dictDirNames = mosHelper.getDirNames()
    fullname = os.path.join(dictDirNames['proc'], filename)
    d = load_file(fullname)
    infoDict = find_info(d)
    prevruns, prevfiles = calc_dates(filename, infoDict)
    #allf = []
    allxn = []
    allp12 = []
    allwsp = []
    allq12 = []
    for fn in prevfiles:
        try:
            fullname2 = os.path.join(dictDirNames['proc'], fn)
            dd = load_file(fullname2)
            #fhr = yoinkFromMOS(dd, 'FHR')
            xn = yoinkFromMOS(dd, 'XN')
            p12 = yoinkFromMOS(dd, 'P12')
            wsp = yoinkFromMOS(dd, 'WSP')
            q12 = yoinkFromMOS(dd, 'Q12')
            # As a side note, fhr, xn, p12 will have length 0 if the
            # wxelement was not found in MOS. This matters later.
            #allf.append(fhr)
            allxn.append(xn)
            allp12.append(p12)
            allwsp.append(wsp)
            allq12.append(q12)
        except:
            # If the file does not exist, use empty np arrays as placeholders
            # On reflection, this may not be an entirely kosher use of try/except. Read up on this.
            #allf.append(np.array([]))
            allxn.append(np.array([]))
            allp12.append(np.array([]))
            allwsp.append(np.array([]))
            allq12.append(np.array([]))

    # At this point, if the wxelement was not found in that MOS type (e.g., NSTU has no
    # X/N line in the MAV), then allElem will be an array whose elements each have size 0.
    # This matters because we don't want to try constructing a display array with no data.
    # Handle this case by checking to see which elements are present and only constructing
    # display arrays for those ones. Note that a different situation is a site where
    # all entries for a given element are 999 (missing), such as TJMZ's minT, and this is not
    # handled here (the plot will be created and it will be empty).

    # These variables are used below to construct display arrays for the given wxelements and data
    wxelements = []
    alldata = {}

    # find a way to consolidate these loops to avoid repetition
    count = 0
    for item in allxn:
        if len(item) == 0:
            count = count + 1
    if count < len(allxn):
        wxelements.append('X')
        alldata['X'] = allxn
        wxelements.append('N')
        alldata['N'] = allxn
        
    count = 0
    for item in allp12:
        if len(item) == 0:
            count = count + 1
    if count < len(allp12):
        wxelements.append('P12')
        alldata['P12'] = allp12

    count = 0
    for item in allwsp:
        if len(item) == 0:
            count = count + 1
    if count < len(allwsp):
        wxelements.append('WSP')
        alldata['WSP'] = allwsp

    count = 0
    for item in allq12:
        if len(item) == 0:
            count = count + 1
    if count < len(allq12):
        wxelements.append('Q12')
        alldata['Q12'] = allq12

    # Make life easy by using keys that are constructed from information returned by find_info.
    modelKey = infoDict['MOSTYPE'] + ' ' + infoDict['RUNTIME']

    # display array size: [row, col]
    # start, stop, step, and jump are indices:
    # start = index for the first element of the first row
    # stop = index for the last element of the first row (may be +/-1 because of Python slicing)
    # step = count by this many to go from start to stop (e.g, 0 to 14 by 2 means step = 2)
    # jump = count by this many to go from start to the first index of the next row (e.g., ECE X: first row begins with 0, next row begins with 2, therefore jump = 2)
    # These indices assume no leading 'X/N', which is OK because the leading label is dropped in the loops below.
    # firsthr = the first fcst of this element in the array is fcst at this many hours from the model cycle (e.g., ECE 00z X is 24, ECE 00z N is 36, MEX 12z X is 12 (even though the entry is blank), etc.)
    # xstep = number of hours to increment to generate x-axis labels
    #
    # For the MAV, jump has 2 values: what's needed to get to first index of the next line and
    # what's needed to get to the first index of the line after that. MAV is special because
    # successive lines sometimes need the same index.
    #
    # MET, MEX 00z -> first fcst is X of that date
    #   off-cycle (00z) N plot is the same as the previous (12z) cycle's N plus a special first line
    # MET, MEX 12z -> first fcst is N of that night (next date in Z)
    #   off-cycle (12z) X plot is the same as the previous (00z) cycle's X plus a special first line
    # MAV 00z, 06z -> first fcst is X of that date
    # MAV 12z, 18z -> first fcst is N of that date (next date in Z)
    #
    # Handle off-cycle X/N plots as follows:
    # display array size is [1,#] where # is the correct number of columns.
    # start = index of the first non-nan entry of that first special line
    # stop = index of the last element of that first special line (+/-1 for Python slicing)
    # step = count by this many to go from start to stop for that first special line
    # jump = nan
    
    dictSize = { # ~ahem~
        'ECMX MOS GUIDANCE 0000 UTC':{ #ECE 00z
            'X':{'size':[8,8], 'start':0, 'stop':15, 'step':2, 'jump':2, 'firsthr':24, 'xstep': 24},
            'N':{'size':[7,7], 'start':1, 'stop':14, 'step':2, 'jump':2, 'firsthr':36, 'xstep': 24},
            'P12':{'size':[8,15], 'start':0, 'stop':15, 'step':1, 'jump':2, 'firsthr':24, 'xstep': 12}
            }, 
        'ECM MOS GUIDANCE 0000 UTC':{ #ECS 00z
            'X':{'size':[3,3], 'start':0, 'stop':5, 'step':2, 'jump':2, 'firsthr':24, 'xstep': 24},
            'N':{'size':[1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'P12':{'size':[3,5], 'start':0, 'stop':5, 'step':1, 'jump':2, 'firsthr':24, 'xstep': 12}
            },
        'GFS MOS GUIDANCE 0000 UTC':{ #MAV 00z
            'X':{'size':[9,3], 'start':0, 'stop':5, 'step':2, 'jump':[1,0], 'firsthr':24, 'xstep': 24},
            'N':{'size':[1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'P12':{'size':[9,5], 'start':0, 'stop':5, 'step':1, 'jump':[1,0], 'firsthr':24, 'xstep': 12},
            'WSP':{'size':[10,19], 'start':0, 'stop':19, 'step':1, 'jump':2, 'firsthr':6, 'xstep': 3},
            'Q12':{'size':[9,5], 'start':0, 'stop':5, 'step':1, 'jump':[1,0], 'firsthr':24, 'xstep': 12},
            },
        'GFS MOS GUIDANCE 0600 UTC':{ #MAV 06z
            'X':{'size':[10,3], 'start':0, 'stop':5, 'step':2, 'jump':[0,1], 'firsthr':18, 'xstep': 24},
            'N':{'size': [1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':6, 'xstep': 24},
            'P12':{'size':[10,5], 'start':0, 'stop':5, 'step':1, 'jump':[0,1], 'firsthr':18, 'xstep': 12},
            'WSP':{'size':[10,19], 'start':0, 'stop':19, 'step':1, 'jump':2, 'firsthr':6, 'xstep': 3},
            'Q12':{'size':[10,5], 'start':0, 'stop':5, 'step':1, 'jump':[0,1], 'firsthr':18, 'xstep': 12},
            },
        'GFS MOS GUIDANCE 1200 UTC':{ #MAV 12z
            'X':{'size':[1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'N':{'size':[9,3], 'start':0, 'stop':5, 'step':2, 'jump':[1,0], 'firsthr':24, 'xstep': 24},
            'P12':{'size':[9,5], 'start':0, 'stop':5, 'step':1, 'jump':[1,0], 'firsthr':24, 'xstep': 12},
            'WSP':{'size':[10,19], 'start':0, 'stop':19, 'step':1, 'jump':2, 'firsthr':6, 'xstep': 3},
            'Q12':{'size':[9,5], 'start':0, 'stop':5, 'step':1, 'jump':[1,0], 'firsthr':24, 'xstep': 12},
            },
        'GFS MOS GUIDANCE 1800 UTC':{ #MAV 18z
            'X':{'size':[1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':6, 'xstep': 24},
            'N':{'size':[10,3], 'start':0, 'stop':5, 'step':2, 'jump':[0,1], 'firsthr':18, 'xstep': 24},
            'P12':{'size':[10,5], 'start':0, 'stop':5, 'step':1, 'jump':[0,1], 'firsthr':18, 'xstep': 12},
            'WSP':{'size':[10,19], 'start':0, 'stop':19, 'step':1, 'jump':2, 'firsthr':6, 'xstep': 3},
            'Q12':{'size':[10,5], 'start':0, 'stop':5, 'step':1, 'jump':[0,1], 'firsthr':18, 'xstep': 12},
            },
        'NAM MOS GUIDANCE 0000 UTC':{ #MET 00z
            'X':{'size':[5,3], 'start':0, 'stop':5, 'step':2, 'jump':1, 'firsthr':24, 'xstep': 24},
            'N':{'size':[1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'P12':{'size':[5,5], 'start':0, 'stop':5, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            'WSP':{'size':[5,19], 'start':0, 'stop':19, 'step':1, 'jump':4, 'firsthr':6, 'xstep': 3},
            'Q12':{'size':[5,5], 'start':0, 'stop':5, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            },
        'NAM MOS GUIDANCE 1200 UTC':{ #MET 12z
            'X':{'size':[1,3], 'start':1, 'stop':4, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'N':{'size':[5,3], 'start':0, 'stop':5, 'step':2, 'jump':1, 'firsthr':24, 'xstep': 24},
            'P12':{'size':[5,5], 'start':0, 'stop':5, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            'WSP':{'size':[5,19], 'start':0, 'stop':19, 'step':1, 'jump':4, 'firsthr':6, 'xstep': 3},
            'Q12':{'size':[5,5], 'start':0, 'stop':5, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            },
        'GFSX MOS GUIDANCE 0000 UTC':{ #MEX 00z
            'X':{'size':[15,8], 'start':0, 'stop':15, 'step':2, 'jump':1, 'firsthr':24, 'xstep': 24},
            'N':{'size':[1,8], 'start':1, 'stop':14, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'P12':{'size':[15,15], 'start':0, 'stop':15, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            'WSP':{'size':[15,15], 'start':0, 'stop':15, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            'Q12':{'size':[12,12], 'start':0, 'stop':12, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12}
            },
        'GFSX MOS GUIDANCE 1200 UTC':{ #MEX 12z
            'X':{'size':[1,8], 'start':1, 'stop':14, 'step':2, 'jump':np.nan, 'firsthr':12, 'xstep': 24},
            'N':{'size':[15,8], 'start':0, 'stop':15, 'step':2, 'jump':1, 'firsthr':24, 'xstep': 24},
            'P12':{'size':[15,15], 'start':0, 'stop':15, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            'WSP':{'size':[15,15], 'start':0, 'stop':15, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12},
            'Q12':{'size':[12,12], 'start':0, 'stop':12, 'step':1, 'jump':1, 'firsthr':24, 'xstep': 12}
            }
        }

    dispArr = {} #init it here, add to it later in the loop below
    dtXaxis = {} #ditto
    
    # Recall that wxelements and alldata were defined above to handle missing cases.
    
    for wx in wxelements:
        if dictSize[modelKey][wx]['size'][0] == 1:
            # Handle the special case of an off-cycle X or N.
            #
            # As usual, it turns out that this is more complicated than it looks at first.
            # PITA cases: ECS 00z N, MAV 06z N, MAV 18z X. Here's what happens:
            # 00z ECS N -> look back at the previous run (00z prev day) to get the size of
            # the array. Since the 00z prev day has the same dictSize entry as 00z current run,
            # it also has size [1,#]. The array is too short.
            # 06z MAV N -> look back 6 hrs to the 00z run's N. The size of that array is also [1,#]. The
            # array is too short.
            # 18z MAV X -> look back 6 hours to the 12z run's X. Same thing happens.
            # The solution is to first check dictSize[modelKey][wx]['size'][0] == 1. If so,
            # enter a bounded loop (a while loop will cause an infinite loop for the ECS) limited
            # to the number of columns given by dictSize[modelKey][wx]['size'][1] since there can't be more
            # special leading lines than cols. Within that loop, calculate prevKey and append it to a
            # storage list. Break the loop once dictSize[modelKey][wx]['size'][0] != 1. Now we have a list,
            # possibly of length 1, containing a list of prevKeys for which to calculate special lines. In
            # fact, go ahead and calculate the speciallines within the loop and append to another storage
            # list. Once the loop ends, the value of prevKey is the key to use to construct the bulk of
            # the array. At least, that's the hope.
            thisrun = thisrun_as_dt(infoDict)
            storePrevKeys = []
            storeSpecialLines = []
            origKey = modelKey #save for later to restore, otherwise can't do more than 1 graph at a time
            for c in range(dictSize[origKey][wx]['size'][1]):
                if dictSize[modelKey][wx]['size'][0] == 1:
                    if 'ECM' in infoDict['MOSTYPE']:
                        backtrack = 24 #ECS
                    elif ('GFS' in infoDict['MOSTYPE'] and 'GFSX' not in infoDict['MOSTYPE']):
                        backtrack = 6 #MAV
                    else:
                        backtrack = 12 #MEX, MET
                    prevrun = thisrun - dt.timedelta(hours = backtrack)
                    prevRUNTIME = prevrun.strftime('%H%M UTC')
                    prevKey = '%s %s' % (infoDict['MOSTYPE'], prevRUNTIME)
                    storePrevKeys.append(prevKey)
                    firstline = alldata[wx][c][1:] #toss leading 'X/N', 'P12', etc.
                    inds = np.arange(1, dictSize[modelKey][wx]['stop'], dictSize[modelKey][wx]['step'])
                    specialline = np.insert(firstline[inds], [0], np.nan)
                    storeSpecialLines.append(specialline)
                    thisrun = prevrun #on the next iteration, go back farther
                    modelKey = prevKey #on the next iteration, go back farther
                else:
                    break
            # 'varResult' is X, N, P12, etc. to return. 'varData' is the working array (allxn, allp12, etc.)
            varResult = np.empty(dictSize[modelKey][wx]['size']) * np.nan
            # toss the first line(s) from allxn since it's associated with origKey, not prevKey
            varData = alldata[wx][c:][:]
            
        else:
            origKey = modelKey
            varResult = np.empty(dictSize[modelKey][wx]['size']) * np.nan
            varData = alldata[wx]
            
        # Secure in the knowledge that an off-cycle case has been handled (if it exists),
        # proceed with creating the display array. Note that if it is an off-cycle case,
        # the array created below is for the previous cycle, and the special line(s) will be added
        # back in afterwards.
        flag = 0
        jumpindex = dictSize[modelKey][wx]['start']
        for row in range(0, len(varResult)):
            validline = varData[row][1:] #toss leading 'X/N' or 'N/X'
            inds = np.arange(jumpindex, dictSize[modelKey][wx]['stop'], dictSize[modelKey][wx]['step'])
            if validline != []:
                varResult[row, 0:len(inds)] = validline[inds]
            # dictSize[modelKey][wx]['jump'] is a single number unless MOSTYPE is the MAV.
            # For the MAV, need to alternate between the two jump indices.
            if (np.size(dictSize[modelKey][wx]['jump']) > 1) and (flag == 0):
                jumpindex = jumpindex + dictSize[modelKey][wx]['jump'][0]
                flag = 1
            elif flag == 1:
                jumpindex = jumpindex + dictSize[modelKey][wx]['jump'][1]
                flag = 0
            else:
                jumpindex = jumpindex + dictSize[modelKey][wx]['jump']

        # Create the fcst valid date/time entries for the x-axis labels.
        # The x-axis labels increase differently depending on the wx element
        # and the MOS type. 
        thisrun = thisrun_as_dt(infoDict)
        tempdt = []
        #if wx in ['P12', 'WSP']:
        #    fcsthrs = range(dictSize[origKey][wx]['firsthr'], 216, 12)
        #else:
        #    fcsthrs = range(dictSize[origKey][wx]['firsthr'], 216, 24)
        fcsthrs = range(dictSize[origKey][wx]['firsthr'], 216, dictSize[origKey][wx]['xstep'])
        for hr in fcsthrs:
            future = dt.timedelta(hours = hr)
            tempdt.append(thisrun + future)
        dtXaxis[wx] = tempdt

        # For off-cycle plots, add the special line back in.
        if dictSize[origKey][wx]['size'][0] == 1:
            # insert the special line(s) defined above
            storeSpecialLines.reverse()
            for c in range(0, len(storeSpecialLines)):
                varResult = np.insert(varResult, [0], storeSpecialLines[c], axis = 0)
            # Guess what. ECS N plot fails miserably because of the chosen data structure (size
            # [1,#] and the off-cycle run is itself). Out of 10 test cases x 3 plots each =
            # 30 plots, only 1 doesn't work. Sigh. Get around this by explicitly defining
            # each element of varResult for ECS N, which is a 3x3 array.
            if 'ECM' in origKey:
                varResult = np.array([
                    [np.nan, alldata[wx][0][2], alldata[wx][0][4]],
                    [alldata[wx][1][2], alldata[wx][1][4], np.nan],
                    [alldata[wx][2][5], np.nan, np.nan]
                    ], dtype = 'f')
            # ...and all is as it was, for the next value of wx in the loop
            varData = alldata[wx]
            modelKey = origKey
        
        dispArr[wx] = varResult
            
    return dispArr, dtXaxis, infoDict, prevruns