def _read_epoch_dataset(subject, epoch_dataset, start_time, stop_time, use_vmu = False, upscale_epoch = True, start_epoch_sec = 10, end_epoch_sec = 60):
	"""
	Read epoch dataset

	Parameters
	----------
	subject : string
		subject ID
	epoch_dataset : string
		name of HDF5 dataset that contains the epoch data

	Returns
	----------
	"""

	# check if epoch dataset is part of HDF5 group
	if epoch_dataset in get_datasets_from_group(group_name = subject, hdf5_file = ACTIGRAPH_HDF5_FILE):

		# get actigraph 10s epoch data
		epoch_data, _ , epoch_time_data = get_actigraph_epoch_data(subject, epoch_dataset = epoch_dataset, hdf5_file = ACTIGRAPH_HDF5_FILE)

		# if upscale_epoch is set to True, then upsample epoch counts to epoch_sec
		if upscale_epoch:
				
			# convert to 60s epoch data	
			epoch_data, epoch_time_data = get_actigraph_epoch_60_data(epoch_data, epoch_time_data, start_epoch_sec, end_epoch_sec)

		# if set to true, then calculate the vector magnitude of all axes
		if use_vmu:

			# calculate epoch VMU
			epoch_data = calculate_vector_magnitude(epoch_data[:,:3], minus_one = False, round_negative_to_zero = False)

		# create dataframe of actigraph acceleration 
		df_epoch_data = pd.DataFrame(epoch_data, index = epoch_time_data).loc[start_time:stop_time]
	
		return df_epoch_data

	else:
		logging.warning('Subject {} has no {} dataset'.format(subject, epoch_dataset))
		return None
def _get_hecht_grid_search_nw_vector(variables, data, reverse = True, s = 60, verbose = False):

	# unpack variables
	t, i, m = variables

	if verbose:
		logging.debug('Calculating t: {}, i: {}, m: {}'.format(*variables))

	# calculate the VMU
	data = calculate_vector_magnitude(data)

	# retrieve non-wear vector
	nw_vector = hecht_2009_triaxial_calculate_non_wear_time(data = data, epoch_sec = s, threshold = int(t), time_interval_mins = int(i), min_count = int(m))

	# upscale nw_vector
	nw_vector = nw_vector.repeat(s, axis = 0)

	if reverse:
		# reverse 0>1 and 1>0 (the reason we do this is that now the positive examples are the non wear times. )
		nw_vector = 1 - nw_vector

	return nw_vector, t, i, m
Exemple #3
0
def process_hecht_2009_triaxial(subject, save_hdf5, idx = 1, total = 1, epoch_dataset = 'epoch10'):
	"""
	Calculate the non-wear time from a data array that contains the vector magnitude (VMU) according to Hecht 2009 algorithm

	Paper:
	COPD. 2009 Apr;6(2):121-9. doi: 10.1080/15412550902755044.
	Methodology for using long-term accelerometry monitoring to describe daily activity patterns in COPD.
	Hecht A1, Ma S, Porszasz J, Casaburi R; COPD Clinical Research Network.

	Parameters
	---------
	subject : string
		subject ID
	save_hdf5 : os.path
		location of HDF5 file to save non wear data to
	idx : int (optional)
		index of counter, only useful when processing large batches and you want to monitor the status
	total: int (optional)
		total number of subjects to process, only useful when processing large batches and you want to monitor the status
	epoch_dataset : string (optional)
		name of dataset within an HDF5 group that contains the 10sec epoch data
	"""

	logging.info('{style} Processing subject: {} {}/{} {style}'.format(subject, idx, total, style = '='*10))
	
	"""
		ACTIGRAPH DATA
	"""

	# read actigraph acceleration time
	_, _, actigraph_time = get_actigraph_acc_data(subject, hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE)
	
	# get start and stop time
	start_time, stop_time = actigraph_time[0], actigraph_time[-1]

	"""
		EPOCH DATA
	"""

	# check if epoch dataset is part of HDF5 group
	if epoch_dataset in get_datasets_from_group(group_name = subject, hdf5_file = ACTIGRAPH_HDF5_FILE):

		# get actigraph 10s epoch data
		epoch_data, _ , epoch_time_data = get_actigraph_epoch_data(subject, epoch_dataset = epoch_dataset, hdf5_file = ACTIGRAPH_HDF5_FILE)

		# convert to 60s epoch data	
		epoch_60_data, epoch_60_time_data = get_actigraph_epoch_60_data(epoch_data, epoch_time_data)

		# calculate epoch 60 VMU
		epoch_60_vmu_data = calculate_vector_magnitude(epoch_60_data[:,:3], minus_one = False, round_negative_to_zero = False)


		"""
			GET NON WEAR VECTOR
		"""

		# create dataframe of actigraph acceleration 
		df_epoch_60_vmu = pd.DataFrame(epoch_60_vmu_data, index = epoch_60_time_data, columns = ['VMU']).loc[start_time:stop_time]

		# retrieve non-wear vector
		epoch_60_vmu_non_wear_vector = hecht_2009_triaxial_calculate_non_wear_time(data = df_epoch_60_vmu.values)

		# get the croped time array as int64 (cropped because we selected the previous dataframe to be between start and stop slice)
		epoch_60_time_data_cropped = np.array(df_epoch_60_vmu.index).astype('int64')
		# reshape
		epoch_60_time_data_cropped = epoch_60_time_data_cropped.reshape(len(epoch_60_time_data_cropped), 1)

		# add two arrays
		combined_data = np.hstack((epoch_60_time_data_cropped, epoch_60_vmu_non_wear_vector))
		
		"""
			SAVE TO HDF5 FILE
		"""
		save_data_to_group_hdf5(group = subject, data = combined_data, data_name = 'hecht_2009_3_axes_non_wear_data', overwrite = True, create_group_if_not_exists = True, hdf5_file = save_hdf5)

	else:
		logging.warning('Subject {} has no corresponding epoch data, skipping...'.format(subject))
Exemple #4
0
def process_plot_non_wear_algorithms(subject, plot_folder, epoch_dataset = 'epoch10', use_optimized_parameters = True, optimized_data_folder = os.path.join('files', 'grid-search', 'final')):
	"""
	Plot acceleration data from actigraph and actiwave including the 4 non wear methods and the true non wear time

	- plot actigraph XYZ
	- plot actiwave YXZ
	- plot actiwave heart rate
	- plot hecht, choi, troiano, v. Hees
	- plot true non wear time

	Parameters
	---------
	subject : string
		subject ID
	plot_folder : os.path
		folder location to save plots to
	epoch_dataset : string (optional)
		name of hdf5 dataset that contains 10s epoch data
	"""

	optimized_parameters = {}
	classification = 'f1'
	for nw_method in ['hecht', 'troiano', 'choi', 'hees']:
		
		# load data
		data = load_pickle('grid-search-results-{}'.format(nw_method), optimized_data_folder)
		# obtain top parameters
		top_results = sorted(data.items(), key = lambda item: item[1][classification], reverse = True)[0]
		optimized_parameters[nw_method] = top_results[0]
	
	"""
		GET ACTIGRAPH DATA
	"""
	actigraph_acc, _ , actigraph_time = get_actigraph_acc_data(subject, hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE)
	# get start and stop time
	start_time, stop_time = actigraph_time[0], actigraph_time[-1]

	"""
		GET ACTIWAVE DATA
	"""
	actiwave_acc, _ , actiwave_time = get_actiwave_acc_data(subject, hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE)
	actiwave_hr, actiwave_hr_time = get_actiwave_hr_data(subject, hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE)

	"""
		EPOCH DATA
	"""

	if epoch_dataset in get_datasets_from_group(group_name = subject, hdf5_file = ACTIGRAPH_HDF5_FILE):

		# get 10s epoch data from HDF5 file
		epoch_data, _ , epoch_time_data = get_actigraph_epoch_data(subject, epoch_dataset = epoch_dataset, hdf5_file = ACTIGRAPH_HDF5_FILE)

		# convert to 60s epoch data	
		epoch_60_data, epoch_60_time_data = get_actigraph_epoch_60_data(epoch_data, epoch_time_data)

		# calculate epoch 60 VMU
		epoch_60_vmu_data = calculate_vector_magnitude(epoch_60_data[:,:3], minus_one = False, round_negative_to_zero = False)


		"""
			GET NON WEAR TIME
		"""
		
		# true non wear time
		true_non_wear_time = read_dataset_from_group(group_name = subject, dataset = 'actigraph_true_non_wear', hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE) 
		# hecht 3-axes non wear time
		hecht_3_non_wear_time = read_dataset_from_group(group_name = subject, dataset = 'hecht_2009_3_axes_non_wear_data', hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE) 
		# troiano non wear time
		troiano_non_wear_time = read_dataset_from_group(group_name = subject, dataset = 'troiano_2007_non_wear_data', hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE)
		# choi non wear time
		choi_non_wear_time = read_dataset_from_group(group_name = subject, dataset = 'choi_2011_non_wear_data', hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE) 
		# hees non wear time
		hees_non_wear_time = read_dataset_from_group(group_name = subject, dataset = 'hees_2013_non_wear_data', hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE)
	
		# if set to True, then update the matrix column with non-wear data
		if use_optimized_parameters:
	
			"""
				READ 60S EPOCH DATA
			"""
			subject_epoch_data = read_dataset_from_group(group_name = subject, dataset = 'epoch60', hdf5_file = ACTIWAVE_ACTIGRAPH_MAPPING_HDF5_FILE).astype('float16')
			subject_epoch_data_vmu = calculate_vector_magnitude(subject_epoch_data, minus_one = False, round_negative_to_zero = False)

			"""
				HECHT
			"""
			# unpack variables
			t, i, m = optimized_parameters['hecht'].split('-')
			# use optimized parameters
			hecht_3_non_wear_time[:,1] = hecht_2009_triaxial_calculate_non_wear_time(data = subject_epoch_data_vmu, epoch_sec = 60, threshold = int(t), time_interval_mins = int(i), min_count = int(m))[:,0]
			
			"""
				TROIANO
			"""
			# unpack variables
			at, mpl, st, ss, vm = optimized_parameters['troiano'].split('-')
			# use optimized variables to calculate non wear vector
			troiano_non_wear_time[:,1] = troiano_2007_calculate_non_wear_time(subject_epoch_data, None, activity_threshold = int(at), min_period_len = int(mpl), spike_tolerance = int(st), spike_stoplevel = int(ss), use_vector_magnitude = eval(vm), print_output = False)[:,0]
			
			"""
				CHOI
			"""
			at, mpl, st, mwl, wst, vm = optimized_parameters['choi'].split('-')
			choi_non_wear_time[:,1] = choi_2011_calculate_non_wear_time(subject_epoch_data, None, activity_threshold = int(at), min_period_len = int(mpl), spike_tolerance = int(st),  min_window_len = int(mwl), window_spike_tolerance = int(wst), use_vector_magnitude = eval(vm), print_output = False)[:,0]

			"""
				HEES
			"""
			mw, wo, st, sa, vt, va = optimized_parameters['hees'].split('-')
			hees_non_wear_time = hees_2013_calculate_non_wear_time(actigraph_acc, hz = 100, min_non_wear_time_window = int(mw), window_overlap = int(wo), std_mg_threshold = float(st), std_min_num_axes = int(sa) , value_range_mg_threshold = float(vt), value_range_min_num_axes = int(va))
			
		"""
			CREATING THE DATAFRAMES
		"""

		# convert actigraph data to pandas dataframe
		df_actigraph_acc = pd.DataFrame(actigraph_acc, index = actigraph_time, columns = ['ACTIGRAPH Y', 'ACTIGRAPH X', 'ACTIGRAPH Z'])
		# convert actiwave data to pandas dataframe
		df_actiwave_acc = pd.DataFrame(actiwave_acc, index = actiwave_time, columns = ['ACTIWAVE Y', 'ACTIWAVE X', 'ACTIWAVE Z'])
		# convert actiwave hr to pandas dataframe
		df_actiwave_hr = pd.DataFrame(actiwave_hr, index = actiwave_hr_time, columns = ['ESTIMATED HR'])
		# convert 60s epoch vmu to dataframe
		df_epoch_60_vmu = pd.DataFrame(epoch_60_vmu_data, index = epoch_60_time_data, columns = ['EPOCH 60s VMU'])
		# slice based on start and stop time
		df_epoch_60_vmu = df_epoch_60_vmu.loc[start_time:stop_time]
		# create dataframe of true non wear time
		df_true_non_wear_time = pd.DataFrame(true_non_wear_time, index = actigraph_time, columns = ['TRUE NON WEAR TIME'])
		# create dataframe of hecht 3-axes non wear time
		df_hecht_3_non_wear_time = pd.DataFrame(hecht_3_non_wear_time[:,1], index = np.asarray(hecht_3_non_wear_time[:,0], dtype ='datetime64[ns]'), columns = ['HECHT-3 NON WEAR TIME'])
		# create dataframe of troiano non wear time
		df_troiano_non_wear_time = pd.DataFrame(troiano_non_wear_time[:,1], index = np.asarray(troiano_non_wear_time[:,0], dtype = 'datetime64[ns]'), columns = ['TROIANO NON WEAR TIME'])
		# create dataframe of choi non wear time
		df_choi_non_wear_time = pd.DataFrame(choi_non_wear_time[:,1], index = np.asarray(choi_non_wear_time[:,0], dtype = 'datetime64[ns]'), columns = ['CHOI NON WEAR TIME'])
		# create dataframe of hees non wear time
		df_hees_non_wear_time = pd.DataFrame(hees_non_wear_time, index = actigraph_time, columns = ['HEES NON WEAR TIME'])
		
		# merge all dataframes
		df_joined = df_actigraph_acc.join(df_true_non_wear_time, how='outer').join(df_actiwave_acc, how='outer').join(df_hecht_3_non_wear_time, how='outer') \
					.join(df_troiano_non_wear_time, how='outer').join(df_choi_non_wear_time, how='outer').join(df_hees_non_wear_time, how='outer').join(df_epoch_60_vmu, how='outer').join(df_actiwave_hr, how='outer')

		# call plot function
		plot_non_wear_algorithms(data = df_joined, subject = subject, plot_folder = plot_folder)
def find_candidate_non_wear_segments_from_raw(acc_data,
                                              std_threshold,
                                              hz,
                                              min_segment_length=1,
                                              sliding_window=1,
                                              use_vmu=False):
    """
	Find segements within the raw acceleration data that can potentially be non-wear time (finding the candidates)

	Parameters
	---------
	acc_data : np.array(samples, axes)
		numpy array with acceleration data (typically YXZ)
	std_threshold : int or float
		the standard deviation threshold in g
	hz : int
		sample frequency of the acceleration data (could be 32hz or 100hz for example)
	min_segment_length : int (optional)
		minimum length of the segment to be candidate for non-wear time (default 1 minutes, so any shorter segments will not be considered non-wear time)
	hz : int (optional)
		sample frequency of the data (necessary so we know how many data samples we have in a second window)
	sliding_window : int (optional)
		sliding window in minutes that will go over the acceleration data to find candidate non-wear segments
	"""

    # adjust the sliding window to match the samples per second (this is encoded in the samplign frequency)
    sliding_window *= hz * 60
    # adjust the minimum segment lenght to reflect minutes
    min_segment_length *= hz * 60

    # define new non wear time vector that we initiale to all 1s, so we only have the change when we have non wear time as it is encoded as 0
    non_wear_vector = np.ones((len(acc_data), 1), dtype=np.uint8)
    non_wear_vector_final = np.ones((len(acc_data), 1), dtype=np.uint8)

    # loop over slices of the data
    for i in range(0, len(acc_data), sliding_window):

        # slice the data
        data = acc_data[i:i + sliding_window]

        # calculate VMU if set to true
        if use_vmu:
            # calculate the VMU of XYZ
            data = calculate_vector_magnitude(data)

        # calculate the standard deviation of each column (YXZ)
        std = np.std(data, axis=0)

        # check if all of the standard deviations are below the standard deviation threshold
        if np.all(std <= std_threshold):

            # add the non-wear time encoding to the non-wear-vector for the correct time slices
            non_wear_vector[i:i + sliding_window] = 0

    # find all indexes of the numpy array that have been labeled non-wear time
    non_wear_indexes = np.where(non_wear_vector == 0)[0]

    # find the min and max of those ranges, and increase incrementally to find the edges of the non-wear time
    for row in find_consecutive_index_ranges(non_wear_indexes):

        # check if not empty
        if row.size != 0:

            # define the start and end of the index range
            start_slice, end_slice = np.min(row), np.max(row)

            # backwards search to find the edge of non-wear time vector
            start_slice = backward_search_non_wear_time(
                data=acc_data,
                start_slice=start_slice,
                end_slice=end_slice,
                std_max=std_threshold,
                hz=hz)
            # forward search to find the edge of non-wear time vector
            end_slice = forward_search_non_wear_time(data=acc_data,
                                                     start_slice=start_slice,
                                                     end_slice=end_slice,
                                                     std_max=std_threshold,
                                                     hz=hz)

            # calculate the length of the slice (or segment)
            length_slice = end_slice - start_slice

            # minimum length of the non-wear time
            if length_slice >= min_segment_length:

                # update numpy array by setting the start and end of the slice to zero (this is a non-wear candidate)
                non_wear_vector_final[start_slice:end_slice] = 0

    # return non wear vector with 0= non-wear and 1 = wear
    return non_wear_vector_final
def troiano_2007_calculate_non_wear_time(data,
                                         time,
                                         activity_threshold=0,
                                         min_period_len=60,
                                         spike_tolerance=2,
                                         spike_stoplevel=100,
                                         use_vector_magnitude=False,
                                         print_output=False):
    """
	Troiano 2007 non-wear algorithm
		detects non wear time from 60s epoch counts
		Nonwear was defined by an interval of at least 60 consecutive minutes of zero activity intensity counts, with allowance for 1–2 min of counts between 0 and 100
	Paper:
		Physical Activity in the United States Measured by Accelerometer
	DOI:
		10.1249/mss.0b013e31815a51b3

	Decription from original Troiano SAS CODE
	* A non-wear period starts at a minute with the intensity count of zero. Minutes with intensity count=0 or*;
	* up to 2 consecutive minutes with intensity counts between 1 and 100 are considered to be valid non-wear *;
	* minutes. A non-wear period is established when the specified length of consecutive non-wear minutes is  *;
	* reached. The non-wear period stops when any of the following conditions is met:                         *;
	*  - one minute with intensity count >100                                                                 *;
	*  - one minute with a missing intensity count                                                            *;
	*  - 3 consecutive minutes with intensity counts between 1 and 100                                        *;
	*  - the last minute of the day   


	Parameters
	------------
	data: np.array((n_samples, 3 axes))
		numpy array with 60s epoch data for axis1, axis2, and axis3 (respectively X,Y, and Z axis)
	time : np.array((n_samples, 1 axis))
		numpy array with timestamps for each epoch, note that 1 epoch is 60s
	activity_threshold : int (optional)
		The activity threshold is the value of the count that is considered "zero", since we are searching for a sequence of zero counts. Default threshold is 0
	min_period_len : int (optional)
		The minimum length of the consecutive zeros that can be considered valid non wear time. Default value is 60 (since we have 60s epoch data, this equals 60 mins)
	spike_tolerance : int (optional)
		Any count that is above the activity threshold is considered a spike. The tolerence defines the number of spikes that are acceptable within a sequence of zeros. The default is 2, meaning that we allow for 2 spikes in the data, i.e. aritifical movement
	spike_tolerenance_consecutive: Boolean (optional)
		Defines if spikes within the data need to be concecutive, thus following each other, or that the spikes can be anywhere within the non-wear sequence. Default is True, meaning that, if the tolerence is 2, two spikes need to follow each other.
	spike_stoplevel : int (Optional)
		any activity above the spike stoplevel end the non wear sequence, default to 100.
	use_vector_magnitude: Boolean (optional)
		if set to true, then use the vector magniturde of X,Y, and Z axis, otherwise, use X-axis only. Default False
	print_output : Boolean (optional)
		if set to True, then print the output of the non wear sequence, start index, end index, duration, start time, end time and epoch values. Default is False

	Returns
	---------
	non_wear_vector : np.array((n_samples, 1))
		numpy array with non wear time encoded as 0, and wear time encoded as 1.
	"""

    # check if data contains at least min_period_len of data
    if len(data) < min_period_len:
        logging.error(
            'Epoch data contains {} samples, which is less than the {} minimum required samples'
            .format(len(data), min_period_len))

    # create non wear vector as numpy array with ones. now we only need to add the zeros which are the non-wear time segments
    non_wear_vector = np.ones((len(data), 1), dtype=np.uint8)
    """
		ADJUST THE COUNTS IF NECESSARY
	"""

    # if use vector magnitude is set to True, then calculate the vector magnitude of axis 1, 2, and 3, which are X, Y, and Z
    if use_vector_magnitude:
        # calculate vectore
        data = calculate_vector_magnitude(data,
                                          minus_one=False,
                                          round_negative_to_zero=False)
    else:
        # if not set to true, then use axis 1, which is the X-axis, located at index 0
        data = data[:, 0]
    """
		VARIABLES USED TO KEEP TRACK OF NON WEAR PERIODS
	"""

    # indicator for resetting and starting over
    reset = False
    # indicator for stopping the non-wear period
    stopped = False
    # indicator for starting to count the non-wear period
    start = False
    # starting minute for the non-wear period
    strt_nw = 0
    # ending minute for the non-wear period
    end_nw = 0
    # counter for the number of minutes with intensity between 1 and 100
    cnt_non_zero = 0
    # keep track of non wear sequences
    ranges = []
    """
		FIND NON WEAR PERIODS IN DATA
	"""

    # loop over the data
    for paxn in range(0, len(data)):

        # get the value
        paxinten = data[paxn]

        # reset counters if reset or stopped
        if reset or stopped:

            strt_nw = 0
            end_nw = 0
            start = False
            reset = False
            stopped = False
            cnt_non_zero = 0

        # the non-wear period starts with a zero count
        if paxinten <= activity_threshold and start == False:
            # assign the starting minute of non-wear
            strt_nw = paxn
            # set start boolean to true so we know that we started the period
            start = True

        # only do something when the non-wear period has started
        if start:

            # keep track of the number of minutes with intensity between 1-100
            if activity_threshold < paxinten <= spike_stoplevel:
                # increase the spike counter
                cnt_non_zero += 1

            # before reaching the 3 consecutive minutes of 1-100 intensity, if encounter one minute with zero intensity, reset the counter
            # this means that 0, 0, 0, 10, 0, 0, 10, 10, 0, 0, 0, 10, 10, 0, 0, 0 is valid
            # and 0, 0, 0, 10, 10, 10, is invalid because we have 3 consecutive spikes between 0-100
            if paxinten <= activity_threshold:
                cnt_non_zero = 0

            # A non-wear period ends with 3 consecutive minutes of 1-100 intensity, or one minute with >100 intensity or with the last sample of the data
            if paxinten > spike_stoplevel or cnt_non_zero > spike_tolerance or paxn == len(
                    data) - 1:

                # define the end of the period
                end_nw = paxn

                # check if the sequence is sufficient in length
                if len(data[strt_nw:end_nw]) < min_period_len:
                    # length is not sufficient
                    reset = True
                else:
                    # length is sufficient, save the period
                    ranges.append([strt_nw, end_nw])
                    stopped = True

    # convert ranges into non-wear sequence vector
    for row in ranges:

        # if set to True, then print output to console/log
        if print_output:
            logging.debug(
                'start index: {}, end index: {}, duration : {}'.format(
                    row[0], row[1], row[1] - row[0]))
            logging.debug('start time: {}, end time: {}'.format(
                time[row[0]], time[row[1]]))
            logging.debug('Epoch values \n{}'.format(data[row[0]:row[1]].T))

        # set the non wear vector according to start and end
        non_wear_vector[row[0]:row[1]] = 0

    return non_wear_vector
def choi_2011_calculate_non_wear_time(data,
                                      time,
                                      activity_threshold=0,
                                      min_period_len=90,
                                      spike_tolerance=2,
                                      min_window_len=30,
                                      window_spike_tolerance=0,
                                      use_vector_magnitude=False,
                                      print_output=False):
    """	
	Estimate non-wear time based on Choi 2011 paper:

	Med Sci Sports Exerc. 2011 Feb;43(2):357-64. doi: 10.1249/MSS.0b013e3181ed61a3.
	Validation of accelerometer wear and nonwear time classification algorithm.
	Choi L1, Liu Z, Matthews CE, Buchowski MS.

	Description from the paper:
	1-min time intervals with consecutive zero counts for at least 90-min time window (window 1), allowing a short time intervals with nonzero counts lasting up to 2 min (allowance interval) 
	if no counts are detected during both the 30 min (window 2) of upstream and downstream from that interval; any nonzero counts except the allowed short interval are considered as wearing

	Parameters
	------------
	data: np.array((n_samples, 3 axes))
		numpy array with 60s epoch data for axis1, axis2, and axis3 (respectively X,Y, and Z axis)
	time : np.array((n_samples, 1 axis))
		numpy array with timestamps for each epoch, note that 1 epoch is 60s
	activity_threshold : int (optional)
		The activity threshold is the value of the count that is considered "zero", since we are searching for a sequence of zero counts. Default threshold is 0
	min_period_len : int (optional)
		The minimum length of the consecutive zeros that can be considered valid non wear time. Default value is 90 (since we have 60s epoch data, this equals 90 mins)
	spike_tolerance : int (optional)
		Any count that is above the activity threshold is considered a spike. The tolerence defines the number of spikes that are acceptable within a sequence of zeros. The default is 2, meaning that we allow for 2 spikes in the data, i.e. aritifical movement
	min_window_len : int (optional)
		minimum length of upstream or downstream time window (referred to as window2 in the paper) for consecutive zero counts required before and after the artifactual movement interval to be considered a nonwear time interval.
	use_vector_magnitude: Boolean (optional)
		if set to true, then use the vector magniturde of X,Y, and Z axis, otherwise, use X-axis only. Default False
	print_output : Boolean (optional)
		if set to True, then print the output of the non wear sequence, start index, end index, duration, start time, end time and epoch values. Default is False

	Returns
	---------
	non_wear_vector : np.array((n_samples, 1))
		numpy array with non wear time encoded as 0, and wear time encoded as 1.
	"""

    # check if data contains at least min_period_len of data
    if len(data) < min_period_len:
        logging.error(
            'Epoch data contains {} samples, which is less than the {} minimum required samples'
            .format(len(data), min_period_len))

    # create non wear vector as numpy array with ones. now we only need to add the zeros which are the non-wear time segments
    non_wear_vector = np.ones((len(data), 1), dtype=np.int16)
    """
		ADJUST THE COUNTS IF NECESSARY
	"""

    # if use vector magnitude is set to True, then calculate the vector magnitude of axis 1, 2, and 3, which are X, Y, and Z
    if use_vector_magnitude:
        # calculate vectore
        data = calculate_vector_magnitude(data,
                                          minus_one=False,
                                          round_negative_to_zero=False)
    else:
        # if not set to true, then use axis 1, which is the X-axis, located at index 0
        data = data[:, 0]
    """
		VARIABLES USED TO KEEP TRACK OF NON WEAR PERIODS
	"""

    # indicator for resetting and starting over
    reset = False
    # indicator for stopping the non-wear period
    stopped = False
    # indicator for starting to count the non-wear period
    start = False
    # second window validation
    window_2_invalid = False
    # starting minute for the non-wear period
    strt_nw = 0
    # ending minute for the non-wear period
    end_nw = 0
    # counter for the number of minutes with intensity between 1 and 100
    cnt_non_zero = 0
    # keep track of non wear sequences
    ranges = []
    """
		FIND NON WEAR PERIODS IN DATA
	"""

    # loop over the data
    for paxn in range(0, len(data)):

        # get the value
        paxinten = data[paxn]

        # reset counters if reset or stopped
        if reset or stopped:

            strt_nw = 0
            end_nw = 0
            start = False
            reset = False
            stopped = False
            window_2_invalid = False
            cnt_non_zero = 0

        # the non-wear period starts with a zero count
        if paxinten == 0 and start == False:

            # assign the starting minute of non-wear
            strt_nw = paxn
            # set start boolean to true so we know that we started the period
            start = True

        # only do something when the non-wear period has started
        if start:

            # keep track of the number of minutes with intensity that is not a 'zero' count
            if paxinten > activity_threshold:

                # increase the spike counter
                cnt_non_zero += 1

            # when there is a non-zero count, check the upstream and downstream window for counts
            # only when the upstream and downstream window have zero counts, then it is a valid non wear sequence
            if paxinten > 0:

                # check upstream window if there are counts, note that we skip the count right after the spike, since we allow for 2 minutes of spikes
                upstream = data[paxn + spike_tolerance:paxn + min_window_len +
                                1]

                # check if upstream has non zero counts, if so, then the window is invalid
                if (upstream > 0).sum() > window_spike_tolerance:
                    window_2_invalid = True

                # check downstream window if there are counts, again, we skip the count right before since we allow for 2 minutes of spikes
                downstream = data[paxn - min_window_len if paxn -
                                  min_window_len > 0 else 0:paxn - 1]

                # check if downstream has non zero counts, if so, then the window is invalid
                if (downstream > 0).sum() > window_spike_tolerance:
                    window_2_invalid = True

                # if the second window is invalid, we need to reset the sequence for the next run
                if window_2_invalid:
                    reset = True

            # reset counter if value is "zero" again
            # if paxinten == 0:
            # 	cnt_non_zero = 0

            if paxinten <= activity_threshold:
                cnt_non_zero = 0

            # the sequence ends when there are 3 consecutive spikes, or an invalid second window (upstream or downstream), or the last value of the sequence
            if cnt_non_zero == 3 or window_2_invalid or paxn == len(data - 1):

                # define the end of the period
                end_nw = paxn

                # check if the sequence is sufficient in length
                if len(data[strt_nw:end_nw]) < min_period_len:
                    # lenght is not sufficient, so reset values in next run
                    reset = True
                else:
                    # length of sequence is sufficient, set stopped to True so we save the sequence start and end later on
                    stopped = True

            # if stopped is True, the sequence stopped and is valid to include in the ranges
            if stopped:
                # add ranges start and end non wear time
                ranges.append([strt_nw, end_nw])

    # convert ranges into non-wear sequence vector
    for row in ranges:

        # if set to True, then print output to console/log
        if print_output:
            logging.debug(
                'start index: {}, end index: {}, duration : {}'.format(
                    row[0], row[1], row[1] - row[0]))
            logging.debug('start time: {}, end time: {}'.format(
                time[row[0]], time[row[1]]))
            logging.debug('Epoch values \n{}'.format(data[row[0]:row[1]].T))

        # set the non wear vector according to start and end
        non_wear_vector[row[0]:row[1]] = 0

    return non_wear_vector