예제 #1
0
    def __call__(self, projectables, **kwargs):
        """Generate the composite."""
        projectables = self.match_data_arrays(projectables)

        day_data = projectables[0]
        night_data = projectables[1]

        lim_low = np.cos(np.deg2rad(self.lim_low))
        lim_high = np.cos(np.deg2rad(self.lim_high))
        try:
            coszen = np.cos(np.deg2rad(projectables[2]))
        except IndexError:
            from pyorbital.astronomy import cos_zen
            LOG.debug("Computing sun zenith angles.")
            # Get chunking that matches the data
            try:
                chunks = day_data.sel(bands=day_data['bands'][0]).chunks
            except KeyError:
                chunks = day_data.chunks
            lons, lats = day_data.attrs["area"].get_lonlats(chunks=chunks)
            coszen = xr.DataArray(cos_zen(day_data.attrs["start_time"], lons,
                                          lats),
                                  dims=['y', 'x'],
                                  coords=[day_data['y'], day_data['x']])
        # Calculate blending weights
        coszen -= np.min((lim_high, lim_low))
        coszen /= np.abs(lim_low - lim_high)
        coszen = coszen.clip(0, 1)

        # Apply enhancements to get images
        day_data = enhance2dataset(day_data)
        night_data = enhance2dataset(night_data)

        # Adjust bands so that they match
        # L/RGB -> RGB/RGB
        # LA/RGB -> RGBA/RGBA
        # RGB/RGBA -> RGBA/RGBA
        day_data = add_bands(day_data, night_data['bands'])
        night_data = add_bands(night_data, day_data['bands'])

        # Replace missing channel data with zeros
        day_data = zero_missing_data(day_data, night_data)
        night_data = zero_missing_data(night_data, day_data)

        # Get merged metadata
        attrs = combine_metadata(day_data, night_data)

        # Blend the two images together
        data = (1 - coszen) * night_data + coszen * day_data
        data.attrs = attrs

        # Split to separate bands so the mode is correct
        data = [data.sel(bands=b) for b in data['bands']]

        return super(DayNightCompositor, self).__call__(data, **kwargs)
예제 #2
0
    def sunzen_corr(self, time_slot, lonlats=None, limit=80., mode='cos'):
        '''Perform Sun zenith angle correction for the channel at
        *time_slot* (datetime.datetime() object) and return the
        corrected channel.  The parameter *limit* can be used to set
        the maximum zenith angle for which the correction is
        calculated.  For larger angles, the correction is the same as
        at the *limit* (default: 80.0 degrees).  Coordinate values can
        be given as a 2-tuple or a two-element list *lonlats* of numpy
        arrays; if None, the coordinates will be read from the channel
        data.  Parameter *mode* is a placeholder for other possible
        illumination corrections. The name of the new channel will be
        *original_chan.name+'_SZC'*, eg. "VIS006_SZC".  This name is
        also stored to the info dictionary of the originating channel.
        '''

        import mpop.tools

        try:
            from pyorbital import astronomy
        except ImportError:
            LOG.warning("Could not load pyorbital.astronomy")
            return None

        if lonlats is None or len(lonlats) != 2:
            # Read coordinates
            LOG.debug("No valid coordinates given, reading from the "
                      "channel data")
            lons, lats = self.area.get_lonlats()
        else:
            lons, lats = lonlats

        # Calculate Sun zenith angles and the cosine
        cos_zen = astronomy.cos_zen(time_slot, lons, lats)

        # Copy the channel
        new_ch = copy.deepcopy(self)

        # Set the name
        new_ch.name += '_SZC'

        if mode == 'cos':
            new_ch.data = mpop.tools.sunzen_corr_cos(new_ch.data,
                                                     cos_zen, limit=limit)
        else:
            # Placeholder for other correction methods
            pass

        # Add information about the corrected version to original
        # channel
        self.info["sun_zen_corrected"] = self.name + '_SZC'

        return new_ch
예제 #3
0
    def __call__(self, projectables, **kwargs):

        day_data = projectables[0]
        night_data = projectables[1]

        lim_low = np.cos(np.deg2rad(self.lim_low))
        lim_high = np.cos(np.deg2rad(self.lim_high))
        try:
            coszen = xu.cos(xu.deg2rad(projectables[2]))
        except IndexError:
            from pyorbital.astronomy import cos_zen
            LOG.debug("Computing sun zenith angles.")
            # Get chunking that matches the data
            try:
                chunks = day_data.sel(bands=day_data['bands'][0]).chunks
            except KeyError:
                chunks = day_data.chunks
            lons, lats = day_data.attrs["area"].get_lonlats_dask(chunks)
            coszen = xr.DataArray(cos_zen(day_data.attrs["start_time"],
                                          lons, lats),
                                  dims=['y', 'x'],
                                  coords=[day_data['y'], day_data['x']])
        # Calculate blending weights
        coszen -= np.min((lim_high, lim_low))
        coszen /= np.abs(lim_low - lim_high)
        coszen = coszen.clip(0, 1)

        # Apply enhancements to get images
        day_data = enhance2dataset(day_data)
        night_data = enhance2dataset(night_data)

        # Adjust bands so that they match
        # L/RGB -> RGB/RGB
        # LA/RGB -> RGBA/RGBA
        # RGB/RGBA -> RGBA/RGBA
        day_data = add_bands(day_data, night_data['bands'])
        night_data = add_bands(night_data, day_data['bands'])

        # Get merged metadata
        attrs = combine_metadata(day_data, night_data)

        # Blend the two images together
        data = (1 - coszen) * night_data + coszen * day_data
        data.attrs = attrs

        # Split to separate bands so the mode is correct
        data = [data.sel(bands=b) for b in data['bands']]

        res = super(DayNightCompositor, self).__call__(data, **kwargs)

        return res
예제 #4
0
    def __call__(self, projectables, **info):
        vis = projectables[0]
        if vis.info.get("sunz_corrected"):
            LOG.debug("Sun zen correction already applied")
            return vis

        if hasattr(vis.info["area"], 'name'):
            area_name = vis.info["area"].name
        else:
            area_name = 'swath' + str(vis.shape)
        key = (vis.info["start_time"], area_name)
        tic = time.time()
        LOG.debug("Applying sun zen correction")
        if len(projectables) == 1:
            if key not in self.coszen:
                from pyorbital.astronomy import cos_zen
                LOG.debug("Computing sun zenith angles.")
                self.coszen[key] = np.ma.masked_outside(
                    cos_zen(vis.info["start_time"],
                            *vis.info["area"].get_lonlats()),
                    # about 88 degrees.
                    0.035,
                    1,
                    copy=False)
            coszen = self.coszen[key]
        else:
            coszen = np.cos(np.deg2rad(projectables[1]))

        if vis.shape != coszen.shape:
            # assume we were given lower resolution szen data than band data
            LOG.debug(
                "Interpolating coszen calculations for higher resolution band")
            factor = int(vis.shape[1] / coszen.shape[1])
            coszen = np.repeat(np.repeat(coszen, factor, axis=0),
                               factor,
                               axis=1)

        # sunz correction will be in place so we need a copy
        proj = vis.copy()
        proj = self._apply_correction(proj, coszen)
        vis.mask[coszen < 0] = True
        self.apply_modifier_info(vis, proj)
        LOG.debug(
            "Sun-zenith correction applied. Computation time: %5.1f (sec)",
            time.time() - tic)
        return proj
예제 #5
0
    def __call__(self, projectables, **info):
        """Generate the composite."""
        projectables = self.match_data_arrays(
            list(projectables) + list(info.get('optional_datasets', [])))
        vis = projectables[0]
        if vis.attrs.get("sunz_corrected"):
            logger.debug("Sun zen correction already applied")
            return vis

        area_name = hash(vis.attrs['area'])
        key = (vis.attrs["start_time"], area_name)
        tic = time.time()
        logger.debug("Applying sun zen correction")
        coszen = self.coszen.get(key)
        if coszen is None and not info.get('optional_datasets'):
            # we were not given SZA, generate SZA then calculate cos(SZA)
            from pyorbital.astronomy import cos_zen
            logger.debug("Computing sun zenith angles.")
            lons, lats = vis.attrs["area"].get_lonlats(chunks=vis.data.chunks)

            coords = {}
            if 'y' in vis.coords and 'x' in vis.coords:
                coords['y'] = vis['y']
                coords['x'] = vis['x']
            coszen = xr.DataArray(cos_zen(vis.attrs["start_time"], lons, lats),
                                  dims=['y', 'x'],
                                  coords=coords)
            if self.max_sza is not None:
                coszen = coszen.where(coszen >= self.max_sza_cos)
            self.coszen[key] = coszen
        elif coszen is None:
            # we were given the SZA, calculate the cos(SZA)
            coszen = np.cos(np.deg2rad(projectables[1]))
            self.coszen[key] = coszen

        proj = self._apply_correction(vis, coszen)
        proj.attrs = vis.attrs.copy()
        self.apply_modifier_info(vis, proj)
        logger.debug(
            "Sun-zenith correction applied. Computation time: %5.1f (sec)",
            time.time() - tic)
        return proj
예제 #6
0
파일: __init__.py 프로젝트: pytroll/satpy
    def __call__(self, projectables, **info):
        vis = projectables[0]
        if vis.info.get("sunz_corrected"):
            LOG.debug("Sun zen correction already applied")
            return vis

        if hasattr(vis.info["area"], 'name'):
            area_name = vis.info["area"].name
        else:
            area_name = 'swath' + str(vis.info["area"].lons.shape)
        key = (vis.info["start_time"], area_name)
        LOG.debug("Applying sun zen correction")
        if len(projectables) == 1:
            if key not in self.coszen:
                from pyorbital.astronomy import cos_zen
                LOG.debug("Computing sun zenith angles.")
                self.coszen[key] = np.ma.masked_outside(cos_zen(vis.info["start_time"],
                                                                *vis.info["area"].get_lonlats()),
                                                        # about 88 degrees.
                                                        0.035,
                                                        1,
                                                        copy=False)
            coszen = self.coszen[key]
        else:
            coszen = np.cos(np.deg2rad(projectables[1]))

        if vis.shape != coszen.shape:
            # assume we were given lower resolution szen data than band data
            LOG.debug(
                "Interpolating coszen calculations for higher resolution band")
            factor = int(vis.shape[1] / coszen.shape[1])
            coszen = np.repeat(
                np.repeat(coszen, factor, axis=0), factor, axis=1)

        # sunz correction will be in place so we need a copy
        proj = vis.copy()
        proj = sunzen_corr_cos(proj, coszen)
        vis.mask[coszen < 0] = True
        self.apply_modifier_info(vis, proj)
        return proj
예제 #7
0
    def __call__(self, projectables, **info):
        projectables = self.check_areas(projectables)
        vis = projectables[0]
        if vis.attrs.get("sunz_corrected"):
            LOG.debug("Sun zen correction already applied")
            return vis

        if hasattr(vis.attrs["area"], 'name'):
            area_name = vis.attrs["area"].name
        else:
            area_name = 'swath' + str(vis.shape)
        key = (vis.attrs["start_time"], area_name)
        tic = time.time()
        LOG.debug("Applying sun zen correction")
        if len(projectables) == 1:
            coszen = self.coszen.get(key)
            if coszen is None:
                from pyorbital.astronomy import cos_zen
                LOG.debug("Computing sun zenith angles.")
                lons, lats = vis.attrs["area"].get_lonlats_dask(CHUNK_SIZE)

                coszen = xr.DataArray(cos_zen(vis.attrs["start_time"],
                                              lons, lats),
                                      dims=['y', 'x'],
                                      coords=[vis['y'], vis['x']])
                coszen = coszen.where((coszen > 0.035) & (coszen < 1))
                self.coszen[key] = coszen
        else:
            coszen = xu.cos(xu.deg2rad(projectables[1]))
            self.coszen[key] = coszen

        proj = self._apply_correction(vis, coszen)
        proj.attrs = vis.attrs.copy()
        self.apply_modifier_info(vis, proj)
        LOG.debug(
            "Sun-zenith correction applied. Computation time: %5.1f (sec)",
            time.time() - tic)
        return proj
예제 #8
0
    def __call__(self, projectables, **info):
        projectables = self.check_areas(projectables)
        vis = projectables[0]
        if vis.attrs.get("sunz_corrected"):
            LOG.debug("Sun zen correction already applied")
            return vis

        if hasattr(vis.attrs["area"], 'name'):
            area_name = vis.attrs["area"].name
        else:
            area_name = 'swath' + str(vis.shape)
        key = (vis.attrs["start_time"], area_name)
        tic = time.time()
        LOG.debug("Applying sun zen correction")
        if len(projectables) == 1:
            coszen = self.coszen.get(key)
            if coszen is None:
                from pyorbital.astronomy import cos_zen
                LOG.debug("Computing sun zenith angles.")
                lons, lats = vis.attrs["area"].get_lonlats_dask(CHUNK_SIZE)

                coszen = xr.DataArray(cos_zen(vis.attrs["start_time"], lons,
                                              lats),
                                      dims=['y', 'x'],
                                      coords=[vis['y'], vis['x']])
                coszen = coszen.where((coszen > 0.035) & (coszen < 1))
                self.coszen[key] = coszen
        else:
            coszen = xu.cos(xu.deg2rad(projectables[1]))
            self.coszen[key] = coszen

        proj = self._apply_correction(vis, coszen)
        proj.attrs = vis.attrs.copy()
        self.apply_modifier_info(vis, proj)
        LOG.debug(
            "Sun-zenith correction applied. Computation time: %5.1f (sec)",
            time.time() - tic)
        return proj
예제 #9
0
파일: channel.py 프로젝트: junjie2008v/mpop
    def sunzen_corr(self,
                    time_slot,
                    lonlats=None,
                    limit=80.,
                    mode='cos',
                    sunmask=False):
        '''Perform Sun zenith angle correction for the channel at
        *time_slot* (datetime.datetime() object) and return the
        corrected channel.  The parameter *limit* can be used to set
        the maximum zenith angle for which the correction is
        calculated.  For larger angles, the correction is the same as
        at the *limit* (default: 80.0 degrees).  Coordinate values can
        be given as a 2-tuple or a two-element list *lonlats* of numpy
        arrays; if None, the coordinates will be read from the channel
        data.  Parameter *mode* is a placeholder for other possible
        illumination corrections. The name of the new channel will be
        *original_chan.name+'_SZC'*, eg. "VIS006_SZC".  This name is
        also stored to the info dictionary of the originating channel.
        '''

        if self.info.get('sun_zen_correction_applied'):
            LOG.debug("Sun zenith correction already applied, skipping")
            return self

        import mpop.tools

        try:
            from pyorbital import astronomy
        except ImportError:
            LOG.warning("Could not load pyorbital.astronomy")
            return None

        if lonlats is None or len(lonlats) != 2:
            # Read coordinates
            LOG.debug("No valid coordinates given, reading from the "
                      "channel data")
            lons, lats = self.area.get_lonlats()
        else:
            lons, lats = lonlats

        # Calculate Sun zenith angles and the cosine
        cos_zen = astronomy.cos_zen(time_slot, lons, lats)

        # Copy the channel
        new_ch = copy.deepcopy(self)

        # Set the name
        new_ch.name += '_SZC'

        if mode == 'cos':
            new_ch.data = mpop.tools.sunzen_corr_cos(new_ch.data,
                                                     cos_zen,
                                                     limit=limit)
        else:
            # Placeholder for other correction methods
            pass

        # Add information about the corrected version to original
        # channel
        self.info["sun_zen_corrected"] = self.name + '_SZC'

        if sunmask:
            if isinstance(sunmask, (float, int)):
                sunmask = sunmask
            else:
                sunmask = 90.
            cos_limit = np.cos(np.radians(sunmask))
            LOG.debug(
                "Masking out data where sun-zenith " +
                "is greater than %f deg", sunmask)
            LOG.debug("cos_limit = %f", cos_limit)
            # Mask out data where the sun elevation is below a threshold:
            new_ch.data = np.ma.masked_where(cos_zen < cos_limit,
                                             new_ch.data,
                                             copy=False)

        new_ch.info["sun_zen_correction_applied"] = True

        return new_ch
예제 #10
0
파일: channel.py 프로젝트: pytroll/mpop
    def sunzen_corr(self, time_slot, lonlats=None, limit=80., mode='cos',
                    sunmask=False):
        '''Perform Sun zenith angle correction for the channel at
        *time_slot* (datetime.datetime() object) and return the
        corrected channel.  The parameter *limit* can be used to set
        the maximum zenith angle for which the correction is
        calculated.  For larger angles, the correction is the same as
        at the *limit* (default: 80.0 degrees).  Coordinate values can
        be given as a 2-tuple or a two-element list *lonlats* of numpy
        arrays; if None, the coordinates will be read from the channel
        data.  Parameter *mode* is a placeholder for other possible
        illumination corrections. The name of the new channel will be
        *original_chan.name+'_SZC'*, eg. "VIS006_SZC".  This name is
        also stored to the info dictionary of the originating channel.
        '''

        if self.info.get('sun_zen_correction_applied'):
            LOG.debug("Sun zenith correction already applied, skipping")
            return self

        import mpop.tools

        try:
            from pyorbital import astronomy
        except ImportError:
            LOG.warning("Could not load pyorbital.astronomy")
            return None

        if lonlats is None or len(lonlats) != 2:
            # Read coordinates
            LOG.debug("No valid coordinates given, reading from the "
                      "channel data")
            lons, lats = self.area.get_lonlats()
        else:
            lons, lats = lonlats

        # Calculate Sun zenith angles and the cosine
        cos_zen = astronomy.cos_zen(time_slot, lons, lats)

        # Copy the channel
        new_ch = copy.deepcopy(self)

        # Set the name
        new_ch.name += '_SZC'

        if mode == 'cos':
            new_ch.data = mpop.tools.sunzen_corr_cos(new_ch.data,
                                                     cos_zen, limit=limit)
        else:
            # Placeholder for other correction methods
            pass

        # Add information about the corrected version to original
        # channel
        self.info["sun_zen_corrected"] = self.name + '_SZC'

        if sunmask:
            if isinstance(sunmask, (float, int)):
                sunmask = sunmask
            else:
                sunmask = 90.
            cos_limit = np.cos(np.radians(sunmask))
            LOG.debug("Masking out data where sun-zenith " +
                      "is greater than %f deg", sunmask)
            LOG.debug("cos_limit = %f", cos_limit)
            # Mask out data where the sun elevation is below a threshold:
            new_ch.data = np.ma.masked_where(
                cos_zen < cos_limit, new_ch.data, copy=False)

        new_ch.info["sun_zen_correction_applied"] = True

        return new_ch
예제 #11
0
def combine(p1, p2, area_of_interest):
    """Combine passes together.
    """

    try:
        return combination[p1, p2]
    except KeyError:
        pass

    area = area_of_interest.poly.area()

    def pscore(poly, coeff=1):
        if poly is None:
            return 0
        else:
            return poly.area() * coeff

    twi1 = get_twilight_poly(p1.uptime)
    twi2 = get_twilight_poly(p2.uptime)

    ip1, sip1 = p1.score.get(area_of_interest, (None, None))
    if sip1 is None:
        ip1 = p1.boundary.contour_poly.intersection(area_of_interest.poly)
        # FIXME: ip1 or ip2 could be None if the pass is entirely inside the
        # area (or vice versa)
        if ip1 is None:
            return 0

        ip1d = ip1.intersection(twi1)
        if ip1d is None:
            lon, lat = np.rad2deg(ip1.vertices[0, :])
            theta = astronomy.cos_zen(p1.uptime, lon, lat)
            if np.sign(theta) > 0:
                ip1d = ip1
                ip1n = None
            else:
                ip1n = ip1
        else:
            twi1.invert()
            ip1n = ip1.intersection(twi1)
            twi1.invert()

        ns1 = pscore(ip1n, p1.satellite.score.night / area)
        ds1 = pscore(ip1d, p1.satellite.score.day / area)
        sip1 = ns1 + ds1
        p1.score[area_of_interest] = (ip1, sip1)

    ip2, sip2 = p2.score.get(area_of_interest, (None, None))
    if sip2 is None:
        ip2 = p2.boundary.contour_poly.intersection(area_of_interest.poly)
        if ip2 is None:
            return 0

        ip2d = ip2.intersection(twi2)
        if ip2d is None:
            lon, lat = np.rad2deg(ip2.vertices[0, :])
            theta = astronomy.cos_zen(p2.uptime, lon, lat)
            if np.sign(theta) > 0:
                ip2d = ip2
                ip2n = None
            else:
                ip2n = ip2
        else:
            twi2.invert()
            ip2n = ip2.intersection(twi2)
            twi2.invert()

        ns2 = pscore(ip2n, p2.satellite.score.night / area)
        ds2 = pscore(ip2d, p2.satellite.score.day / area)
        sip2 = ns2 + ds2
        p2.score[area_of_interest] = (ip2, sip2)

    ip1p2 = ip1.intersection(ip2)

    if ip1p2 is None:
        sip1p2 = 0
    else:
        ip1p2da = ip1p2.intersection(twi1)
        twi1.invert()
        ip1p2na = ip1p2.intersection(twi1)
        twi1.invert()

        ip1p2db = ip1p2.intersection(twi2)
        twi2.invert()
        ip1p2nb = ip1p2.intersection(twi2)
        twi2.invert()

        ns12a = pscore(ip1p2na, p1.satellite.score.night / area)
        ds12a = pscore(ip1p2da, p1.satellite.score.day / area)
        ns12b = pscore(ip1p2nb, p2.satellite.score.night / area)
        ds12b = pscore(ip1p2db, p2.satellite.score.day / area)

        sip1p2a = ns12a + ds12a
        sip1p2b = ns12b + ds12b
        sip1p2 = (sip1p2a + sip1p2b) / 2.0

    if p2 > p1:
        tdiff = (p2.uptime - p1.uptime).seconds / 3600.
    else:
        tdiff = (p1.uptime - p2.uptime).seconds / 3600.

    res = fermia(tdiff) * (sip1 + sip2) - fermib(tdiff) * sip1p2
    combination[p1, p2] = res

    return res
예제 #12
0
def combine(p1, p2, area_of_interest, scores):
    """Combine passes together.
    """

    try:
        return combination[p1, p2]
    except KeyError:
        pass

    area = area_of_interest.poly.area()

    def pscore(poly, coeff=1):
        if poly is None:
            return 0
        else:
            return poly.area() * coeff

    twi1 = get_twilight_poly(p1.uptime)
    twi2 = get_twilight_poly(p2.uptime)

    ip1, sip1 = p1.score.get(area_of_interest, (None, None))
    if sip1 is None:
        ip1 = p1.boundary.contour_poly.intersection(area_of_interest.poly)
        # FIXME: ip1 or ip2 could be None if the pass is entirely inside the
        # area (or vice versa)
        if ip1 is None:
            return 0

        ip1d = ip1.intersection(twi1)
        if ip1d is None:
            lon, lat = np.rad2deg(ip1.vertices[0, :])
            theta = astronomy.cos_zen(p1.uptime,
                                      lon, lat)
            if np.sign(theta) > 0:
                ip1d = ip1
                ip1n = None
            else:
                ip1n = ip1
        else:
            twi1.invert()
            ip1n = ip1.intersection(twi1)
            twi1.invert()

        ns1 = pscore(ip1n, scores[p1.satellite][0] / area)
        ds1 = pscore(ip1d, scores[p1.satellite][1] / area)
        sip1 = ns1 + ds1
        p1.score[area_of_interest] = (ip1, sip1)

    ip2, sip2 = p2.score.get(area_of_interest, (None, None))
    if sip2 is None:
        ip2 = p2.boundary.contour_poly.intersection(area_of_interest.poly)
        if ip2 is None:
            return 0

        ip2d = ip2.intersection(twi2)
        if ip2d is None:
            lon, lat = np.rad2deg(ip2.vertices[0, :])
            theta = astronomy.cos_zen(p2.uptime,
                                      lon, lat)
            if np.sign(theta) > 0:
                ip2d = ip2
                ip2n = None
            else:
                ip2n = ip2
        else:
            twi2.invert()
            ip2n = ip2.intersection(twi2)
            twi2.invert()

        ns2 = pscore(ip2n, scores[p2.satellite][0] / area)
        ds2 = pscore(ip2d, scores[p2.satellite][1] / area)
        sip2 = ns2 + ds2
        p2.score[area_of_interest] = (ip2, sip2)

    ip1p2 = ip1.intersection(ip2)

    if ip1p2 is None:
        sip1p2 = 0
    else:
        ip1p2da = ip1p2.intersection(twi1)
        twi1.invert()
        ip1p2na = ip1p2.intersection(twi1)
        twi1.invert()

        ip1p2db = ip1p2.intersection(twi2)
        twi2.invert()
        ip1p2nb = ip1p2.intersection(twi2)
        twi2.invert()

        ns12a = pscore(ip1p2na, scores[p1.satellite][0] / area)
        ds12a = pscore(ip1p2da, scores[p1.satellite][1] / area)
        ns12b = pscore(ip1p2nb, scores[p2.satellite][0] / area)
        ds12b = pscore(ip1p2db, scores[p2.satellite][1] / area)

        sip1p2a = ns12a + ds12a
        sip1p2b = ns12b + ds12b
        sip1p2 = (sip1p2a + sip1p2b) / 2.0

    if p2 > p1:
        tdiff = (p2.uptime - p1.uptime).seconds / 3600.
    else:
        tdiff = (p1.uptime - p2.uptime).seconds / 3600.

    res = fermia(tdiff) * (sip1 + sip2) - fermib(tdiff) * sip1p2
    combination[p1, p2] = res

    return res